Showing posts with label Sharepoint. Show all posts
Showing posts with label Sharepoint. Show all posts

Tuesday, August 8, 2023

Camlex and Camlex.Client 5.4.2 released

New version 5.4.2 of Camlex library has been released. Starting with this version it became possible to generate CAML queries with string operators BeginsWith and Contains for ContentTypeId field type e.g. the following C# code:

Camlex.Query().Where(x => ((DataTypes.ContentTypeId)x["ContentTypeId"]).StartsWith("0x123")).ToString(true);

will generate the following CAML query:

<Query>
  <Where>
    <BeginsWith>
      <FieldRef Name="ContentTypeId" />
      <Value Type="ContentTypeId">0x123</Value>
    </BeginsWith>
  </Where>
</Query>

This is useful since when you add some site content type to SharePoint list under the hood SharePoint creates inherited content type (which has ContentTypeId which starts with ContentTypeId of parent site content type with appended 00 symbols and guid without dashes) and exactly this inherited content type is then used for list items created in this list. In order to fetch all items created with original site content type we may use CAML query with BeginsWith operator and ContentTypeIdof parent site content type.

As usual new version is available via Nuget.

Wednesday, November 16, 2022

Camlex and Camlex.Client 5.4.1 released

I'm glad to announce that today new version 5.4.1 of Camlex/Camlex.Client libraries were released. This is minor release which contains fix for reverse engineering of binary operations (Geq, Gt, Leq, Lt) for text values. Reverse engineering is used in free online service http://camlex-online.org where Sharepoint developers which are new to Camlex can automatically convert classic CAML query to C# syntax for Camlex. Thus it will simplify migration of existing code to Camlex.

Credits for this release go to Ivan Russo which contributed to Camlex (thanks a lot Ivan). If you are Sharepoint developer and have idea how to improve Camlex feel free to create PR :).

Wednesday, August 17, 2022

Get Sharepoint data in browser console via Rest API without additional tools

Sometimes during troubleshooting you need to quickly get some data from Sharepoint, e.g. id of current site collection. There are many ways how to do that with additional tools e.g. from PowerShell and PnP, SPEditor Chrome extension and it's pnpjs console, etc. But it requires installation of these tools and their knowledge (of course if you work with Sharepoint it will be better if you will know these tools :) ).

One way how you may get this data without extra tools is to use SP Rest API directly from browser console. E.g. for getting site collection details we may fetch /_api/site endpoint and output JSON response to console:

fetch("https://{mytenant}.sharepoint.com/sites/test/_api/site", {headers: {"accept": "application/json; odata=verbose"}}).then(response => response.json().then(txt => console.log(JSON.stringify(txt))))

(here instead of {mytenant} you should use your tenant name. Note however that this approach will also work in on-prem)

It will output a lot of information about current site collection to the console:

{
    "d": {
        "__metadata": {
            "id": "https://{mytenant}.sharepoint.com/sites/test/_api/site",
            "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site",
            "type": "SP.Site"
        },
        "Audit": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Audit"
            }
        },
        "CustomScriptSafeDomains": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/CustomScriptSafeDomains"
            }
        },
        "EventReceivers": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/EventReceivers"
            }
        },
        "Features": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Features"
            }
        },
        "HubSiteSynchronizableVisitorGroup": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/HubSiteSynchronizableVisitorGroup"
            }
        },
        "Owner": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/Owner"
            }
        },
        "RecycleBin": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/RecycleBin"
            }
        },
        "RootWeb": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/RootWeb"
            }
        },
        "SecondaryContact": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/SecondaryContact"
            }
        },
        "UserCustomActions": {
            "__deferred": {
                "uri": "https://{mytenant}.sharepoint.com/sites/test/_api/site/UserCustomActions"
            }
        },
        "AllowCreateDeclarativeWorkflow": false,
        "AllowDesigner": true,
        "AllowMasterPageEditing": false,
        "AllowRevertFromTemplate": false,
        "AllowSaveDeclarativeWorkflowAsTemplate": false,
        "AllowSavePublishDeclarativeWorkflow": false,
        "AllowSelfServiceUpgrade": true,
        "AllowSelfServiceUpgradeEvaluation": true,
        "AuditLogTrimmingRetention": 90,
        "ChannelGroupId": "00000000-0000-0000-0000-000000000000",
        "Classification": "",
        "CompatibilityLevel": 15,
        "CurrentChangeToken": {
            "__metadata": {
                "type": "SP.ChangeToken"
            },
            "StringValue": "..."
        },
        "DisableAppViews": false,
        "DisableCompanyWideSharingLinks": false,
        "DisableFlows": false,
        "ExternalSharingTipsEnabled": false,
        "GeoLocation": "EUR",
        "GroupId": "00000000-0000-0000-0000-000000000000",
        "HubSiteId": "00000000-0000-0000-0000-000000000000",
        "Id": "32d406dc-dc97-46dd-b01c-e6346419ceb7",
        "SensitivityLabelId": null,
        "SensitivityLabel": "00000000-0000-0000-0000-000000000000",
        "IsHubSite": false,
        "LockIssue": null,
        "MaxItemsPerThrottledOperation": 5000,
        "MediaTranscriptionDisabled": false,
        "NeedsB2BUpgrade": false,
        "ResourcePath": {
            "__metadata": {
                "type": "SP.ResourcePath"
            },
            "DecodedUrl": "https://{mytenant}.sharepoint.com/sites/test"
        },
        "PrimaryUri": "https://{mytenant}.sharepoint.com/sites/test",
        "ReadOnly": false,
        "RequiredDesignerVersion": "15.0.0.0",
        "SandboxedCodeActivationCapability": 2,
        "ServerRelativeUrl": "/sites/test",
        "ShareByEmailEnabled": false,
        "ShareByLinkEnabled": false,
        "ShowUrlStructure": false,
        "TrimAuditLog": true,
        "UIVersionConfigurationEnabled": false,
        "UpgradeReminderDate": "1899-12-30T00:00:00",
        "UpgradeScheduled": false,
        "UpgradeScheduledDate": "1753-01-01T00:00:00",
        "Upgrading": false,
        "Url": "https://{mytenant}.sharepoint.com/sites/test",
        "WriteLocked": false
    }
}

sUsing the same approach you may call another Rest API end points directly from browser console. It may save your time during troubleshooting. Hope this information will help someone.

Tuesday, March 15, 2022

Handle errors when load images on a web page

Regular html images (<img> tag) have quite powerfull mechanism of error handling and practice shows that it is not that well-known. E.g. if we have image on some web page (it may be any html web page regardless of underlying technology which rendered it) defined like that:

<img src="http://example.com/image.png" />

Now suppose that browser couldn't fetch this image because of some reason: e.g. image could not be found in specified url location (404 Not Found) or current user doesn't have permissions to this location (403 Forbidden). How to handle such situations and add graceful fallback logic (e.g. show some predefined image placeholder or use more advanced technique to fetch the image)?

There is standard html mechanism which allows to handle errors which may occur during loading of the images. Within img tag we may define error handler like that:

<img src="http://example.com/image.png" onerror="imageOnError()" />

In this case imageOnError() function will be called when error will occur during loading of the image. Knowing this technique we may implement graceful fallback for some scenarios when initial image was not successfully loaded because of some reason. E.g. some time ago I wrote an article where showed how to fetch image from Sharepoint site collection where current user doesn't have access: Return image stored in Sharepoint Online doclib from Azure function and show it in SPFx web part. If we will combine these 2 posts we may implement logic which by default shows images from predefined location (e.g. from Sharepoint doclib). But if current user doesn't have access to this doclib we may use onerror handler and fetch image via Azure function and app permissions and then show it in base64 encoded format. This is only one example how described technique may help to achieve more user friendly experience and provide functionality for end users which otherwise wouldn't be available.

Thursday, December 16, 2021

Camlex 5.4 release: switch to MIT license

Hello Sharepoint developers who use Camlex in the work. I'm glad to announce that starting with version 5.4 Camlex will use MIT license. Before that Camlex was distributed under Ms-Pl license but nowdays MIT became standard for open source projects as most permissive license (e.g. PnP.Framework also uses MIT license). In order to be inline with the trend I changed Camlex license to MIT. New nuget packages with 5.4 version for basic object model version and CSOM version are already available for download.

Thursday, August 26, 2021

Camlex.Client 5.0 released: support for .NET 5.0 and .NETStandard 2.0 target frameworks

Good news for Sharepoint developers who use Camlex library: today new version of Camlex.Client 5.0.0 has been released. In this version native support for .NET 5.0 and .NET Standard 2.0 target frameworks was added. .NET Framework 4.5 is also still there for backward compatibility.

With this new version you may use Camlex.Client in your .NET 5.0 or .NET Core 3.1/2.1 in native mode. I.e. there won't be warning anymore that Camlex is referenced using .NET Framework compatibility mode. Each Camlex.Client nuget package now has assemblies for 3 target frameworks:

  • .NET Framework 4.5
  • .NET Standard 2.0
  • .NET 5.0

which should cover most of development needs. The following table shows examples which application will use which Camlex.Client version depending on its target framework:

App's target frameworkCamlex.Client target framework
.NET Framework 4.5.NET Framework 4.5
.NET Framework 4.6.1.NET Framework 4.5
.NET Framework 4.7.2.NET Framework 4.5
.NET Core 2.1.NET Standard 2.0
.NET Core 3.1.NET Standard 2.0
.NET 5.0.NET 5.0

All Camlex.Client nuget packages has been updated with this change:

  • Camlex.Client.dll for SP Online
  • Camlex.Client.2013 for SP2013 
  • Camlex.Client.2016 for SP2013
  • Camlex.Client.2019 for SP2013

Tuesday, May 25, 2021

Camlex 5.3 and Camlex.Client 4.2 released: support for StorageTZ attribute for DateTime values

Today new versions of Camlex library (both for server side and CSOM) have been released: Camlex 5.3 and Camlex.Client 4.2. For client object model separate packages are available for Sharepoint online and on-premise:

Package Version Description
Camlex.NET.dll 5.3.0 Server object model (on-prem)
Camlex.Client.dll 4.2.0 Client object model (SP online)
Camlex.Client.2013 4.2.0 Client object model (SP 2013 on-prem)
Camlex.Client.2016 4.2.0 Client object model (SP 2016 on-prem)
Camlex.Client.2019 4.2.0 Client object model (SP 2019 on-prem)

In this release possibility to specify StorageTZ attribute for DateTime values was added. Main credits for this release go to Ivan Russo who implemented basic part of the new feature. So now when you create CAML query for DateTime values you may pass "true" into IncludeTimeValue() method which then will add StorageTZ="True" attribute for Value tag:

var now = new DateTime(2021, 5, 18, 17, 31, 18);
string caml = Camlex.Query().Where(x => (DateTime)x["Created"] > now.IncludeTimeValue(true)).ToString();

will generate the following CAML:

<Where>
  <Gt>
      <FieldRef Name="Created" />
      <Value Type="DateTime" IncludeTimeValue="True" StorageTZ="True">2021-05-18T17:31:18Z</Value>
  </Gt>
</Where>

Thank you for using Camlex and as usual, if you have idea for its further improvement you may post it here.

Wednesday, March 10, 2021

Get Sharepoint site collection id (SPSite.ID) and web id (SPWeb.ID) from followed sites returned from REST API

In Sharepoint we can fetch all followed sites for the current user by the following REST API endpoint:

http://example.com/_api/social.following/my/Followed(types=4)

(instead of http://example.com you should use url of your SP site). It will return collection of site objects which will contain such properties as name, url, etc. However often we need to know also site collection id (SPSite.ID) and web id (SPWeb.ID). We can of course go through all returned sites and fetch their ids by separate JSOM/CSOM calls but it will affect performance (it is classic n+1 problem when we at first get list of items (1st call) and then for each item in the list make separate API call (n calls)).

Fortunately it is possible to get these ids right from REST API response. There is one strange field called "id" which looks like this:

"Id": "8.b4af2aa5fb834daa87aa9fb4155abd7d.b14bd3b3d6084dadb0fc7b79679fc767.
b4af2aa5fb834daa87aa9fb4155abd7d.00000000000000000000000000000000",

So there are several strings divided by dot. If we will check site and web id we will see that second string looks like SPSite.ID and 3rd string like SPWeb.ID:


The only difference is that they don't contain dashes. But it is quite easy do add them by ourselves in the code:

id.substr(0, 8) + "-" + id.substr(8, 4) + "-" + id.substr(12, 4) + "-" + id.substr(16, 4) + "-" + id.substr(20)

Using this approach we can get ids of Sharepoint site collections and web sites directly from REST API response.

Wednesday, February 17, 2021

Get list items from Sharepoint using lists.asmx web service

In one of my previous articles I showed how to get authentication cookies (FedAuth) from Sharepoint FBA using authentication.asmx web service (see Authenticate in Sharepoint on-prem FBA site via OTB /_vti_bin/Authentication.asmx web service). In this post I will show how to use these cookies and get list items from Sharepoint list using standard lists.asmx web service.

It can be done using the following code:

Cookie authCookies = ...;
string listTitle = ...;
string soapBody =
	"<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>" +
	"  <soap:Body>" +
	"    <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'>" +
	"      <listName>" + listTitle + "</listName>" +
	"    </GetListItems>" +
	"  </soap:Body>" +
	"</soap:Envelope>";
var cookies = new CookieContainer();
cookies.Add(authCookies);
var handler = new HttpClientHandler();
handler.CookieContainer = cookies;
using (var httpClient = new HttpClient(handler))
{
	var req = new HttpRequestMessage(HttpMethod.Post, "http://example.com/_vti_bin/lists.asmx")
	{
		Content = new StringContent(soapBody, Encoding.UTF8, "text/xml")
	};
	var res = httpClient.SendAsync(req).GetAwaiter().GetResult();
	string result
	if (res.StatusCode == HttpStatusCode.OK)
	{
		result = res.Content.ReadAsStringAsync().Result;
	}
}

Here we use auth cookies from previous article. We again create special SOAP body where pass list title. In this example we will get all list items but it is also possible to fetch list items which match CAML query - for that you will need to use quert, viewFieldsm rowLimit and queryOptions tags in SOAP body. For more information about these properties refer to documentation of lists.asmx web service. Id everything was done correctly it will return you list items from specified lists in SOAP format.

Tuesday, January 26, 2021

Authenticate in Sharepoint on-prem FBA site via OTB /_vti_bin/Authentication.asmx web service

Sharepoint contains number of OTB web services which are located inside _vti_bin virtual directory. In this article we will check one of them: authentication.asmx. This is quite interesting web service which allows to authenticate your app in Sharepoint FBA site. I.e. allows to send username and password and get FedAuth authentication cookies if provided credentials are valid (the same cookies which are used by Sharepoint FBA site when you successfully logged in via it’s login page).

Note that you may use this web service in server-side apps or mobile apps but not in client side JavaScript-based solution because FedAuth cookies have HttpOnly flag and thus can’t be used in JavaScript.

In order to use this web service we need to send special SOAP body in HTTP POST request. In order to get example of this SOAP open http://example.com/_vti_bin/authentication.asmx in browser (where instead of http://example.com you should use url of your site). It will show list of available web methods:

  • Login
  • Mode

Click on Login and you will get needed SOAP examples (in this article we will use SOAP 1.1):

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Login xmlns="http://schemas.microsoft.com/sharepoint/soap/">
      <username>string</username>
      <password>string</password>
    </Login>
  </soap:Body>
</soap:Envelope>

In this SOAP body we need to set actual username and password in appropriate xml tags.

Here is example which shows how to send this SOAP body to authentication.asmx web service and get authentication cookies:

var cookies = new CookieContainer();
var handler = new HttpClientHandler();
handler.CookieContainer = cookies;
using (var httpClient = new HttpClient(handler))
{
    string soapBody =
    "<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>" +
    "  <soap:Body>" +
    "    <Login xmlns='http://schemas.microsoft.com/sharepoint/soap/'>" +
    "      <username>" + username + "</username>" +
    "      <password>" + password + "</password>" +
    "    </Login>" +
    "  </soap:Body>" +
    "</soap:Envelope>";
    var req = new HttpRequestMessage(HttpMethod.Post, "http://example.com/_vti_bin/authentication.asmx");
    req.Content = new StringContent(soapBody, Encoding.UTF8, "text/xml")
    var res = httpClient.SendAsync(req).GetAwaiter().GetResult();
    if (res.StatusCode == HttpStatusCode.OK)
    {
        var response = res.Content.ReadAsStringAsync().Result;
        var cookie = cookies.GetCookies(new Uri("http://example.com")).Cast<Cookie>().FirstOrDefault(c => c.Name == "FedAuth");
    }
}

In this example we first create HTTP POST request to authentication.asmx using SOAP body retrieved above and then if authentication was successful read FedAuth cookies returned from server. After that we may use these cookies for making authenticated calls to other web services. In one of the future articles I will show how to do that.

Thursday, November 5, 2020

Call WebExtensions.WebExistsFullUrl method from OfficeDevPnP library for different site collection

Few years ago I wrote about one problem in OfficeDevPnP method WebExtensions.WebExistsFullUrl: shortly when it was called with url which belongs to non-existent site collection from different managed path it returned true. Full description of the problem is available here: How to check does site collection exist by absolute url using CSOM in Sharepoint. Also I posted issue to PnP-Sites-Core in github: WebExtensions.WebExistsFullUrl returns true for non-existent sites from different managed path in Sharepoint 2013 on-premise. I checked this issue with the latest PnP version (which is 3.26.2010 at the moment of writing of this article) and found that this problem is fixed now by MS. I.e. code of WebExtensions.WebExistsFullUrl method is still the same:

public static bool WebExistsFullUrl(ClientRuntimeContext context, string webFullUrl)
{
    bool exists = false;
    try
    {
        using (ClientContext testContext = context.Clone(webFullUrl))
        {
            testContext.Load(testContext.Web, w => w.Title);
            testContext.ExecuteQueryRetry();
            exists = true;
        }
    }
    catch (Exception ex)
    {
        if (IsUnableToAccessSiteException(ex) || IsCannotGetSiteException(ex))
        {
            // Site exists, but you don't have access .. not sure if this is really valid
            // (I guess if checking if URL is already taken, e.g. want to create a new site
            // then this makes sense).
            exists = true;
        }
    }
    return exists;
}

but now when we try to clone ClientContext in this line:

ClientContext testContext = context.Clone(webFullUrl))

and webFullUrl corresponds to non-existent site collection from another managed path (e.g. if context was created from the root site collection http://example.com and we pass webFullUrl = http://example.com/sites/som-non-existent-url) it throws System.Exception (System.Net.WebException) now:

The remote server returned an error: (404) Not Found

while in earlier CSOM version it created ClientContext for the root site in this case. And as result WebExtensions.WebExistsFullUrl returns false as expected. So fortunately issue was fixed and we can call this method for checking existence of different site collections.

Thursday, October 1, 2020

How to enable Android App links in web-facing Sharepoint on-prem site

In previous article I showed how to enable universal links for iOS on Sharepoint on-prem site – see How to enable iOS universal links in web-facing Sharepoint on-prem site. In this post I will describe how to enable Android App links for web-facing Sharepoint on-prem site.

For Android App links the process is different. First of all we need to prepare special assetlinks.json file with predefined content:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "...",
    "package_name": "...",
    "sha256_cert_fingerprints": ["..."]
  }
}]

I won’t describe here details about content of assetlinks.json file – you may find this information on Android documentation sites. Next step is to place assetlinks.json file into .well-known subfolder of the root folder of our site. On Sharepoint we can create this folder in site’s IIS folder C:\inetpub\wwwroot\wss\VirtualDirectories\{SiteName}.

After that we need to go to IIS manager > choose Sharepoint site > right click .well-known sub folder and click “Convert to Application”:

When .well-known subfolder is ready and configured in IIS manager we may copy assetlinks.json file there.

Now we need to make assetlinks.json file available for anonymous access (otherwise it won’t be available for Android infrastructure) and ensure that server adds Content-Type: "application/json" http header in response. In order to do that add the following web.config file to .well-known subfolder:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <staticContent>
        <clear />
        <mimeMap fileExtension=".json" mimeType="application/json" />
      </staticContent>
        <handlers>
            <remove name="JSONHandlerFactory" />
        </handlers>
    </system.webServer>
</configuration>

After that Android App links will be available on your web-facing Sharepoint on-prem site.

Tuesday, September 22, 2020

How to enable iOS universal links in web-facing Sharepoint on-prem site

Universal links allow to open mobile app when user clicks it inside the site. I.e. if user has appropriate mobile app it will be opened instead of regular browser page which will give better user experience on mobile devices. In this post I will show how to configure universal links for iOS in web-facing Sharepoint on-premise site. At the same time I won’t write about all steps like building mobile app itself, getting its app id, etc – please refer to appropriate resources related with mobile apps development for that.

In order to enable universal links for iOS on your web-facing Sharepoint on-prem site the following steps have to be done:

  1. create AASA (Apple app site association) file. File name should be exactly “apple-app-site-association” without extension. It should contain data in json format with predefined structure (see below)
  2. put it to the root folder of web-facing site
  3. ensure that AASA file is available for anonymous users (robots) with Content-Type = “application/json”

This is how AASA file may look like:

{
  "applinks": {
    "apps": [],
    "details": [ { "appID": "{appId}", "paths": ["*"] } ]
  }
}

(use you app id instead of {appId} placeholder). When file is ready we need to copy it to the IIS folder of our Sharepoint site. Next step is to provide anonymous access and set correct content type. It can be done by modifying web.config of Sharepoint site – we need to add new location element here under configuration tag (usually in web.config of Sharepoint site there are many others location elements – so just put them together):

 <location path="apple-app-site-association">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <staticContent>
        <mimeMap fileExtension="." mimeType="application/json" />
      </staticContent>
    </system.webServer>
  </location>

It will make AASA file available for anonymous users and will set content type to “application/json”.

The last step is to verify that everything is configured properly. It can be done on AASA validator. Result should look like this:

At the end you should have universal links enabled for your web-facing Sharepoint on-prem site.

Update 2020-10-01: guide for enabling Android App links on Sharepoint site is available here: How to enable Android App links in web-facing Sharepoint on-prem site.

Wednesday, September 2, 2020

Redirect traffic from single site collection in Sharepoint on-prem via URL rewrite IIS module

Suppose that you have Sharepoint on-prem web application with multiple site collections which have own urls and need to perform maintenance work on one of these site collections so others will remain working. In order to do that you may temporary redirect traffic from this site collection to external page (e.g. to maintenance page hosted outside of this site collection). Technically it can be done by adding URL rewrite rule to Sharepoint web app in IIS. Rule will look like this:

This rule will redirect traffic from site collection with url /test to external site http://example.com. This is the same rule in xml which is added to web.config of Sharepoint web app:

<rewrite>
  <rules>
	<rule name="MyRule" enabled="true" stopProcessing="true">
	  <match url="test/*" />
	  <action type="Redirect" url="http://example.com" appendQueryString="false" redirectType="Found" />
	</rule>
  </rules>
</rewrite>

Note that if you perform temporary maintenance work on site collection then Redirect type should be set to Found (302) like shown above and not to Permanent (301). In case of permanent redirect browser will cache response and will redirect users from this site collection even after maintenance will be completed and rule will be removed from the site.

Friday, August 14, 2020

Camlex 5.2 and Camlex.Client 4.1: support for Membership and OffsetDays

Good news for Sharepoint developers who use Camlex library: new versions are released both for server and client object models. For client object model separate packages are available for Sharepoint online and on-premise: https://www.nuget.org/account/Packages:

Package Version Description
Camlex.NET.dll 5.2.0 Server object model (on-prem)
Camlex.Client.dll 4.1.0 Client object model (SP online)
Camlex.Client.2013 4.1.0 Client object model (SP 2013 on-prem)
Camlex.Client.2016 4.1.0 Client object model (SP 2016 on-prem)
Camlex.Client.2019 4.1.0 Client object model (SP 2019 on-prem)

In this release 2 new features were added: support for Membership element (many developers requested it so finally it is out) and OffsetDays. Main credits for this release go to Ivan Russo who made actual work with very good quality so I only needed validate PR, accept it and add few more unit tests. Then I merged new features to the client branch which keeps source code for client object model version (nowdays it is done manually as there are quite many changes between server and client object model versions).

1. Membership element support

Membership element allows to create CAML queries using different membership conditions. The following examples show how it can be done now with Camlex:

// SPWeb.AllUsers
var caml = Camlex.Query().Where(x => Camlex.Membership(
	x["Field"], new Camlex.SPWebAllUsers())).ToString();
var expected =
	"  " +
	"    " +
	"      " +
	"    " +
	"  ";

// SPGroup
var caml = Camlex.Query().Where(x => Camlex.Membership(
	x["Field"], new Camlex.SPGroup(3))).ToString();
var expected =
	"  " +
	"    " +
	"      " +
	"    " +
	"  ";

// SPWeb.Groups
var caml = Camlex.Query().Where(x => Camlex.Membership(
	x["Field"], new Camlex.SPWebGroups())).ToString();
var expected =
	"  " +
	"    " +
	"      " +
	"    " +
	"  ";

// CurrentUserGroups
var caml = Camlex.Query().Where(x => Camlex.Membership(
			x["Field"], new Camlex.CurrentUserGroups())).ToString();
var expected =
	"  " +
	"    " +
	"      " +
	"    " +
	"  ";

// SPWeb.Users
var caml = Camlex.Query().Where(x => Camlex.Membership(
	x["Field"], new Camlex.SPWebUsers())).ToString();
var expected =
	"  " +
	"    " +
	"      " +
	"    " +
	"  ";

2. OffsetDays support

OffsetDays attribute is used with Today element and allows to add or subtract number of days from today for constructing dynamic CAML queries based on current datetime offset. The following example shows how it can be done with Camlex:

string caml = Camlex.Query().Where(x =>
	x["Created"] > ((DataTypes.DateTime)Camlex.Today).OffsetDays(-1)).ToString();
const string expected =
	"  " +
	"    " +
	"        " +
	"        " +
	"           " +
	"        " +
	"    " +
	"  ";

Thank you for using Camlex and stay tuned for the further improvements.

Tuesday, May 12, 2020

How to run handler on ribbon open event and access ribbon elements via javascript in Sharepoint

Sometimes we need to run code which will be executed when Sharepoint ribbon is opened. In many cases ribbon is closed by default and you can’t access its elements in regular document.ready handler because ribbon elements are not created in the DOM yet. In this case you need to subscribe somehow on ribbon.open even and access elements there. This is how it can be done:

ExecuteOrDelayUntilScriptLoaded(function() {
 SP.Ribbon.PageManager.get_instance().add_ribbonInited(function(){
  console.log("Ribbon is opened");
 });
}, "sp.ribbon.js");

Interesting that even inside this handler you can’t access elements via jQuery selector because it doesn’t find these dynamically added elements. So e.g. the following code which tries to access New document tab in doclib will return 0:

$("#Ribbon.Documents.New").length

In order to access these dynamically added ribbon elements we need to use pure javascript call document.getElementById. Here is the full code:

ExecuteOrDelayUntilScriptLoaded(function() {
 SP.Ribbon.PageManager.get_instance().add_ribbonInited(function(){
  var el = document.getElementById("Ribbon.Documents.New");
  if (el) {
   console.log("New document tab is found");
  }
 });
}, "sp.ribbon.js");

Using this approach you will be able to access and manipulate ribbon elements via javascript.

Sunday, April 5, 2020

Fix problem “Security token service cannot be activated” in Sharepoint farm after inplace upgrade Windows Server 2012 to R2

If you performed inplace upgrade of Windows Server 2012 to Windows Server 2012 R2 with Sharepoint Server running you may face with the following error after upgrade will be completed: when you will try to open any Sharepoint web application the following exception will be shown:

WebHost failed to process a request.
  Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/12547953
  Exception: System.ServiceModel.ServiceActivationException: The service '/SecurityTokenServiceApplication/securitytoken.svc' cannot be activated due to an exception during compilation.  The exception message is: Exception has been thrown by the target of an invocation.. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: certificate
    at System.IdentityModel.Tokens.X509SecurityToken..ctor(X509Certificate2 certificate, String id, Boolean clone, Boolean disposable)
    at System.IdentityModel.Tokens.X509SecurityToken..ctor(X509Certificate2 certificate)

…

The error says that certificate for Secure token service is not specified. In order to fix this error you need to replace certificate for STS:

  1. Open IIS manager > Server certificates > Create Self-Signed Certificate
  2. After that export created certificate to local folder:

Next run the following PowerShell script which will update certificate for STS:

$pfxPath = "path to pfx"
$pfxPass = "certificate password"
$stsCertificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $pfxPath, $pfxPass, 20
Set-SPSecurityTokenServiceConfig -ImportSigningCertificate $stsCertificate
certutil -addstore -enterprise -f -v root $stsCertificate
iisreset
net stop SPTimerV4
net start SPTimerV4

After that open Sharepoint web app again.

Wednesday, March 18, 2020

How to provision modern Sharepoint page with custom SPFx web part via PnP template

If you developed custom SPFx web part and tested it on dev env you will most probably want to automate it’s provisioning to customers environments. In this article I will show how to do that via PnP template.

When you need to create PnP template the simplest option is to export it form existing site and copy those components which you need. This is much faster than trying to remember whole PnP schema.

So first of all we need to build sppkg in release mode (with “--ship” parameter) and upload it to App catalog:

gulp clean
gulp bundle --ship
gulp package-solution --ship

After that add your web part to some modern page – we will use test.aspx for example here. Now you have modern site running with your custom web part.

Next step is to export PnP template from your site:

Get-PnPProvisioningTemplate -Out template.xml

When export will be done edit template.xml and copy section pnp:ClientSidePages which may look like this (of course if you have several SPFx web parts there will be several pnp:CanvasControl instances):

<pnp:ClientSidePages>
 <pnp:ClientSidePage PageName="test.aspx" EnableComments="false" Publish="true">
   <pnp:Sections>
  <pnp:Section>
    <pnp:Controls>
   <pnp:CanvasControl WebPartType="Custom" JsonControlData="..." ControlId="..." Order="1" Column="1" />
    </pnp:Controls>
  </pnp:Section>
   </pnp:Sections>
 </pnp:ClientSidePage>
</pnp:ClientSidePages>

and paste it to PnP template which you are going to use for provisioning on customers environments. When this template will be applied to the site it will have modern page with your custom SPFx web part.

Thursday, March 12, 2020

Disable redirection from user details page UserDisp.aspx to MySite Person.aspx in Sharepoint

As you probably know Sharepoint has built in Created By and Modified By fields which are automatically added to all list items and documents which added to Sharepoint. Under the hood it is done by defining these fields in base content type derived by all other content types. If you add these fields to list views they will be clickable – it will be possible to click on user name and go to user details page UserDisp.aspx which will show user’s attributes (synced with AD if you use Windows authentication).

However in some cases you may notice that instead of UserDisp.aspx Sharepoint redirects to user profile page Person.aspx in MySites web application. If you are not planning to use user profiles and MySites in your Sharepoint site then this behavior may be unwanted and it may be needed to disable this automatic redirection.

Technically redirect is made by MySiteRedirection.ascx user control which is added by MySite farm scope feature (FeatureId = 69cc9662-d373-47fc-9449-f18d11ff732c) which has the following elements.xml file:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Control Id="ProfileRedirection" Sequence="100" ControlSrc="~/_controltemplates/mysiteredirection.ascx"/>
    <Control Id="MobileSiteNavigationLink1" Sequence="100"
        ControlClass="Microsoft.SharePoint.Portal.MobileControls.MobileMySitePageNavigation"
        ControlAssembly="Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
    </Control>
</Elements>

In order to disable redirection we need to deactivate this feature:

Disable-SPFeature -Id 69cc9662-d373-47fc-9449-f18d11ff732c

After that after clicking user names in Created By/Modified By columns (and all other clickable columns of User or group type) user will be redirected to regular UserDisp.aspx page

Friday, December 6, 2019

Provision language specific embedded resources with Sharepoint project type in Visual Studio

In my previous posts I showed how to provision embedded resources automatically with wsp provisioning:

Provision and automatic update of embedded resources to Resources folder in Sharepoint hive

Provision and automatic update of embedded resources to App_LocalResources folder under Sharepoint Template/Layouts or Template/ControlTemplates sub folders

In this post I will describe how to include language specific embedded resources to wsp using standard Sharepoint project type in Visual Studio.

Let’s create new empty Sharerpoint project, add Resources mapped folder, add Test.resx file there and set it as Embedded resource:

After that let’s use mentioned solution from this post in order to include Test.resx to wsp package (by default Visual Studio adds only those resx files which are set as Content). If we will publish wsp package now it will look like this:

Now let’s add language specific resource Test.ru-RU.resx:

and also add it to wsp using the same technique as for default resource file Test.resx. Now our wsp will contain both resx files which will be provisioned to {hive}/Resources folder:

But this is not enough. When we add language specific embedded resource to solution Visual Studio produces additional assembly xx-XX\{AssemblyName}.resources.dll in output folder (in our example ru-RU\SharePointProject5.resources.dll). This additional assembly should be installed to the GAC together with basic assembly – otherwise code will use only default resources from Test.resx. In order to do it we need to add this assembly to Additional assembles list in Package > Advanced > Additional assembles > Add existing assembly:

(Don’t forget to add language identifier in Location field before assembly name – i.e. ru-RU\SharePointProject5.resources.dll, but not just SharePointProject5.resources.dll. If you will have several additional languages there will be several *.resources.dll assemblies for each of them. By adding language identifier we instruct Visual Studio to put them to appropriate subfolders inside wsp package).

After that both language specific resx file and additional resources.dll assembly will be added to wsp and will be installed automatically during wsp provisioning: