Tuesday, March 30, 2021

Create new site collections in Sharepoint Online using app-only permissions with certificate-based authentication (without appinv.aspx and client secret)

In the past if we needed to create site collections in Sharepoint Online we registered new SP app on appregnew.aspx page and granted Full control permissions on tenant level using appinv.aspx (alternatively instead of appregnew.aspx we could register new AAD app in Azure portal and then use it's clientId on appinv.aspx). But nowadays it is not preferred way to achieve this goal. In this article I will show ho to create new site collections using app-only permissions with certificate-based authentication (without appinv.aspx and client secret).

First of all we need to register new AAD app in Azure portal and grant Sites.FullControl.All API permissions (after granting permissions admin consent will be also needed):


Now for testing purposes let's try to create Modern Communication site using clientId and clientSecret:

using (var ctx = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext("https://{tenant}-admin.sharepoint.com", "{clientId}", "{clientSecret}"))
{
    ctx.RequestTimeout = Timeout.Infinite;
    var tenant = new Tenant(ctx);
    var properties = new SiteCreationProperties
    {
        Url = "https://{tenant}.sharepoint.com/sites/{siteUrl}",
        Lcid = 1033,
        TimeZoneId = 59,
        Owner = "{username}@{tenant}.onmicrosoft.com",
        Title = "{siteTitle}",
        Template = "SITEPAGEPUBLISHING#0",
        StorageMaximumLevel = 0,
        StorageWarningLevel = 0
    };

    var op = tenant.CreateSite(properties);
    ctx.Load(tenant);
    ctx.Load(op);
    ctx.ExecuteQueryRetry();

    ctx.Load(op, i => i.IsComplete);
    ctx.ExecuteQueryRetry();

    while (!op.IsComplete)
    {
        Thread.Sleep(30000);
        op.RefreshLoad();
        ctx.ExecuteQueryRetry();
        Console.Write(".");
    }
    Console.WriteLine("Site is created");
}

If we will try to run this code we will get Microsoft.SharePoint.Client.ServerUnauthorizedAccessException:

Access denied. You do not have permission to perform this action or access this resource.

Now let's try to create self-signed certificate like described here: Granting access via Azure AD App-Only. Then upload public key (.cer file) to our AAD app:


After that let's change C# example shown above to use certificate private key (.pfx file) and password for authentication. Code for creating site collection will remain the same:

using (var ctx = new OfficeDevPnP.Core.AuthenticationManager().GetAzureADAppOnlyAuthenticatedContext(
    "https://{tenant}-admin.sharepoint.com",
    "{clientId}",
    "{tenant}.onmicrosoft.com",
    @"C:\{certFileName}.pfx",
    "{certPassword}"))
{
    ctx.RequestTimeout = Timeout.Infinite;
    var tenant = new Tenant(ctx);
    var properties = new SiteCreationProperties
    {
        Url = "https://{tenant}.sharepoint.com/sites/{siteUrl}",
        Lcid = 1033,
        TimeZoneId = 59,
        Owner = "{username}@{tenant}.onmicrosoft.com",
        Title = "{siteTitle}",
        Template = "SITEPAGEPUBLISHING#0",
        StorageMaximumLevel = 0,
        StorageWarningLevel = 0
    };

    var op = tenant.CreateSite(properties);
    ctx.Load(tenant);
    ctx.Load(op);
    ctx.ExecuteQueryRetry();

    ctx.Load(op, i => i.IsComplete);
    ctx.ExecuteQueryRetry();

    while (!op.IsComplete)
    {
        Thread.Sleep(30000);
        op.RefreshLoad();
        ctx.ExecuteQueryRetry();
        Console.Write(".");
    }
    Console.WriteLine("Site is created");
}

This code will work and site collection will be successfully created. This is how you may create site collections in Sharepoint Online using app-only permissions and certificate-based authentication.

Tuesday, March 16, 2021

Attach onClick handler on div dynamically in runtime using css class selector in React and Fluent UI

Sometimes in React we may need to attach javascript handler on html element using "old" way which closer to DOM manipulation which we did in pure javascript code. I.e. if you can't get component reference because of some reason and the only thing you have is css class of html element in the DOM you may still need to work with it via DOM manipulations.

As example we will use Search element from Fluent UI. It renders magnifier icon internally and it is quite hard to get component ref on it. Highlighted magnifier is rendered as div with "ms-SearchBox-iconContainer" css class:

Imagine that we want to attach onClick handler on magnifier icon so users will be able to click it and get search results. Here is how it can be achivied:

private _onClick(self: Search) {
  // search logic
}

public componentDidMount() {
  let node = document.querySelector(".ms-SearchBox-iconContainer");
  if (node) {
    node.addEventListener("click", e => this._onClick(this));
  }
}

public componentWillUnmount() {
  let node = document.querySelector(".ms-SearchBox-iconContainer");
  if (node) {
    node.removeEventListener("onClick", e => this._onClick(this));
  }
}

So we add handler in componentDidMount and remove it componentWillUnmount. Inside these methods we use document.querySelector() for finding actual div to which we need to attach onClick handler. Note that we pass this as parameter of _onClick since this inside this method will point to div html element but not to search component itself.

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.