Quantcast
Channel: Vardhaman Deshpande
Viewing all 134 articles
Browse latest View live

Use Flow HTTP Webhook to call Azure Function - Send notification email after PnP provisioning

$
0
0
When using Site Designs and PnP for applying provisioning templates to sites, the order of events is something like this:

  1. When a Communication site or an Office 365 Group connected Team site is created, a Site Design gets applied to it.
  2. The Site Design contains a triggerFlow action which starts a Microsoft Flow (configured to be triggered by an http request).
  3. The flow adds an item to an Azure storage queue which triggers and Azure Function.
  4. The Azure Function contains the PnP template and the code to apply the template to the newly created site.

This approach is described in the guidance here: Calling the PnP provisioning engine from a site script

Now the problem with this sequence of actions is that (as of this time) they are all Asynchronous events i.e. "fire and forget". When the Site Design executes the triggerFlow action, it does not wait for the Flow to complete before showing the success screen to the user. Similarly, after the Flow adds an item to the storage queue, quite understandably, it does not wait for the triggered Azure Function to finish executing before completing its run.

In this sequence of async events, if we want to perform an action like sending a notification email after the provisioning is complete, we have the following options:


Multiple Flows:

Use an output storage queue and in the Azure Function, add an item to it when the provisioning completes. Have another Flow triggered by the output queue, which sends the email. I am not a big fan of this approach as this means managing an output queue as well as a second Flow just for sending the email notification.


Polling:

If we want to avoid creating a second Flow, another option is to use a Do Until action and poll for when messages arrive on the queue. The main drawback of this approach is that we must keep track of different instances of the Flow. If multiple users are creating sites and there are multiple messages arriving on the queue, we want to send the email only after the message of our own site arrives. This is doable using the Flow instance id but a bit too complex for my liking :)

The solution: HTTP Webhook

Fortunately, there is a nice way to handle this. Using the HTTP Webhook action, we can basically wait for the provisioning to complete before returning the control back to the Flow. Since we are in a serverless world here, the Flow will essentially pause here and "wake up" when we want it to.

To use this approach, we have to make a slight modification to the approach suggested in the Microsoft documentation. In this approach, instead of firing the Azure Function with a queue trigger, we will fire it using the HTTP Webhook action. The Function will be configured to execute on HTTP trigger i.e. when an HTTP request is made to an endpoint.

Here is an overview of actions for the Flow:


The Flow starts when the Site Design executes the triggerFlow action:


Now this is where the magic happens. The HTTP Webhook action makes a request to the Azure Function. It passes the call back url as a parameter and then waits. Whenever the Azure Function finishes executing, it is expected to make a request to the call back url. When a request will be received in the call back url, the HTTP Webhook action will finish and the Flow will resume with the data passed back from the Azure Function.


Here is the code for the Azure Function:


Now, it is only matter of parsing the data returned from the Azure function and sending the email:



This way, we can perform actions in the Flow after the PnP template has been successfully applied to the site.

Hope this was helpful!


SharePoint Framework: Calling AAD secured Azure Function on behalf of a user

$
0
0
Recently, after long last, the support for easily calling Azure AD secured custom APIs was released in the latest version of the SharePoint Framework (v1.4.1). Here are the details if you want to learn more: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aadhttpclient

In this post, let's have a look at how to secure our custom API (Azure Function) with Azure AD and then call the custom API from within a SharePoint Framework web part on behalf of the currently logged in user. We will be able to get the current user identity/claims in the custom API to know which user made the call to the Azure Function.



So let's quickly jump into how we are going to achieve this:

1) Create an Azure AD app registration


Go to Azure AD > App registrations > New application registration and create a new app registration. I am calling my app "User Details Custom API for SPFx"



By default the app registration will have the Sign in and read user profile scope on the Windows Azure Active Directory API. For the purpose of this post, those permissions are enough for us. We don't need to modify anything here right now.

Make a note of the Application ID of the app registration. This is the Client ID which we will use later.

As a subscription admin, Grant permissions to the App registration for all users so that each user does not have to do this individually:


2) Create an Azure Function App and configure it


Go to App Services > Add > Function App and create a new Azure Function App which we will use to host our Azure Function


Once the Function App is created, we need to secure it with our Azure AD app registration.

Go to Function App > Platform Features > Authentication / Authorization


In the Authentication / Authorization pane, for "Action to take when request is not authenticated" select "Log in with Azure Active Directory".

For Authentication Provider, click on Azure AD > Advanced and for Client ID, paste the Client ID (Application ID) of our Azure AD app registration we created earlier.


Click Ok and Save the Configuration.

Go to Function App > Platform Features > CORS and add the SharePoint Online domain from which your SPFx webpart will make a call to the Azure Function.


Click on Save.

3) Create the Azure Function


Now let's actually create the Azure Function which we will deploy to the Function App as our custom API.

Using Visual Studio 2017, I have created a precompiled .NET Framework Azure Function project. As we are going to use this function as an API, I have selected an Http Triggered function:

Notice that the Authorization level for the function itself is set to Anonymous. This is because we are using Azure AD at the Function App level to secure it.

All this function does is returns the claims of the current authenticated user in JSON format.

Publish the function to the Azure Function App we created earlier.

4) Create the SharePoint Framework web part


Before going ahead with this step, make sure you have the latest version of the SharePoint Framework yeoman generator (1.4.1 at the time of this writing) This page should contain all the current versions of the generator, npm and node supported by the SharePoint Framework: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment

Once you have all the latest packages, create the SPFx web part with yo @microsoft/sharepoint


To keep it simple, I am making it a tenant scoped solution with no JavaScript framework.

5) Request permissions for the SPFx web part


Once the SPFx web part solution is created, navigate to the config/package-solution.json file and add the webApiPermissionRequests property:

The resource will be the name of the Azure AD app registration we used to secure our Azure Function and the scope will be user_impersonation as we want to make a call on behalf of the current user.

6) Using AadHttpClient to call the custom Azure Function


Now in your SPFx webpart, include the following imports:

and the code to use the AadHttpClient to make a request to your custom Azure Function:

To display the table properly, add the following to the .scss file of your web part:


7) Installing and running the SPFx package 


Since we are going to run the SPFx solution in debug mode, we will run the commands without the --ship or --production switch. This will enable us to debug the solution locally.

Run the following commands to build and package your solution:

gulp build

gulp bundle

gulp package-solution

Start local debugging with

gulp serve --nobrowser

Now, upload the .sppkg file from the sharepoint/solution folder to the App Catalog:


Select the checkbox and Click on Deploy.

8) Granting permissions using the SPO Admin API management page


After deploying the package to the app catalog, as a SharePoint Administrator, navigate to the new SharePoint Online Admin centre and go to the API management section. You will notice the permissions we requested from the solution package can be approved from here. 

Approve the permissions before moving to the next step



9) Add web part to page

 

Now go to any modern page and add your web part to it. Since we have deployed a tenant scoped solution, no need to install it individually on each site.

This is probably my least favourite part of the process. You will need to enable pop-ups so that SPFx and the underlying ADAL.js can authenticate the current user with the custom API.


Once you have enabled the popup and refreshed the page, the web part should start displaying correctly!


The current user identity and claims coming through will be what we have sent from the Azure Function!

Hope this helps :) As always, the code from this post is available on GitHub: https://github.com/vman/spfx-azure-function-custom-api

Working with SharePoint Online Hub sites using CSOM

$
0
0
With SharePoint Online Hub sites launched for Targeted release tenants, here is some quick code I put together to work with them using CSOM:

1) Register a Hub site, Connect a site to a Hub site, Disconnect a site from a Hub site and Unregister a Hub site:



2) Grant and Revoke specific users rights to connect sites to a Hub site:


When a Hub site is registered, it is public by default. Any user is able to connect their site to the hub site. If you want only a specific set of users to be able to connect their site to the Hub site, you can grant "Join" rights to these users:

Hope this helps!

Azure Functions: Add a message to a storage queue after a delay

$
0
0
I was looking at a specific problem today dealing with Azure Functions and adding messages to storage queues: I needed my Function to add a message to an output queue in order to trigger another function, but the challenge was, the message should not get added immediately and the second queue trigger function should not fire immediately as well.

We needed a delay between the completion of the first function and execution the second queue triggered function.

A not-so-great way of achieving this would be to add a Thread.Sleep before the message is added to the output queue but we really wanted to avoid that as it would keep the function running and would not be a truly "serverless" way of doing things.

I had a look at the visibilityTimeout setting in the host.json file but turns out it is meant for configuring the delay after which a retry is made when a function fails: https://docs.microsoft.com/en-us/azure/azure-functions/functions-host-json#queues

Fortunately, Jeff Hollan on twitter suggested a nice solution for this which utilised the initialVisibilityDelay property of the CloudQueue.AddMessage function. The great thing is this works seamlessly with Azure Function output bindings so we don't have to use the SDK ourselves.

Here is a sample of how my final code looks and it works pretty great!

Hope you find this useful!

SPFx: Calling Microsoft Graph API from an AAD secured Azure Function on behalf of a user

$
0
0
This post is part of a series where we explore consuming Azure AD secured Azure Functions from SharePoint Framework components. Articles in the series:

1) SharePoint Framework: Calling AAD secured Azure Function on behalf of a user
2) Calling Microsoft Graph API from an AAD secured Azure Function on behalf of a user (this post)
3) SharePoint Framework: Calling back to SharePoint from an AAD secured Azure Function on behalf of a user

The functionality shown in the post is preview only and not be used in production at this time.

In the previous post, we were successfully able to call an AAD secured Azure Function from a SharePoint Framework web part.

Now once we are in the Function, lets see how to make a call to the Microsoft Graph on behalf of the logged in user. We will only focus on the Azure Function here, to fully understand the set up and auth process, I recommend you check out the previous post.



Updates to the Azure AD app registration:


In order to call the Microsoft Graph, we will need to add a Client Secret to the Azure AD app registration.



We don't need to add any new permission scopes for the purposes of this post as we are going to make a call to the /v1.0/me endpoint which requires the User.Read permission. According to the SPFx docs, if we exchange the SPFx generated token for a MS Graph token, it will automatically have the User.Read.All permission scope.

If you want to do anything beyond this with the Microsoft Graph, you will need to add the relevant permissions scope and grant permissions to it. In the next post, we will see how to do this by granting permissions to the "Office 365 SharePoint Online" permissions scope.

Here is what we are going to do in this post:

When the Azure Function executes, we already have an access token sent by the SharePoint Framework AadHttpClient in the Authorization header. This access token has the "user_impersonation" scope which only allows it to access the Azure Function. It cannot be directly used to call the Microsoft Graph.

In order to obtain new access token that will work with the Microsoft Graph, we will have to request it using the existing access token. Once we have the new token, we are able to make a call to the Microsoft Graph:

Once this function is called from our SharePoint Framework web part, we are able to get data back from the graph:


Thanks for reading!

SPFx: Calling back to SharePoint from an AAD secured Azure Function on behalf of a user

$
0
0
This post is part of a series where we explore consuming Azure AD secured Azure Functions from SharePoint Framework components. Articles in the series:

1) SharePoint Framework: Calling AAD secured Azure Function on behalf of a user
2) SharePoint Framework: Calling Microsoft Graph API from an AAD secured Azure Function on behalf of a user
3) Calling back to SharePoint from an AAD secured Azure Function on behalf of a user (this post)

The functionality shown in the post is preview only and not be used in production at this time.

In the previous post, we were successfully able to call the Microsoft Graph API from an AAD secured Azure Function and return data back to the SharePoint Framework web part.

Now in this post, lets see how we can make a call back to SharePoint on behalf of the logged in user from the Azure Function. We will only focus on the code in the Azure Function here, to fully understand the set up and auth process, I recommend you check out the previous posts in the series.


Updates to the Azure AD app registration:


In order to make a call back to SharePoint, we will need to add a Client Secret to the Azure AD app registration. Skip this step if you have already done this as part of the previous post.



We will also need to add the Office 365 SharePoint Online permissions scope to the Azure AD app registration. For this post, I am selecting the "Read and write items in all Site Collections" delegated scope. You can select the scope according to the operations you want to perform in SharePoint


Don't forget to grant the permissions again as a subscription admin. This is so that each user does not have to do this individually:


Here is what we are going to do in the code:

When the Azure Function executes, we already have an access token sent by the SharePoint Framework AadHttpClient in the Authorization header. This access token has the "user_impersonation" scope which only allows it to access the Azure Function. It cannot be directly used to call back to SharePoint.

In order to obtain new access token that will work with SharePoint, we will have to request it using the existing access token.

Once this function is called from the SharePoint Framework, we are able to get the data back to the web part:


Thanks for reading!

Get/Set SharePoint Online Tenant properties with CSOM

$
0
0
SharePoint Online Tenant properties are key/value pairs which can be used to set custom configuration settings on the tenant. These properties can then be consumed by SharePoint Framework components or any other type of customisation.

Here is some quick code I have put together to work with SharePoint Online tenant properties using CSOM.

The important thing to note here is that when setting the properties, we can only use the context of an App Catalog site (either tenant level or site collection level)

When getting the properties, the context of any site can be used.

Set SharePoint Online Tenant Properties in the App Catalog using CSOM:



Get SharePoint Online Tenant Properties using CSOM:


Note: While working with this code, I noticed SharePoint Online Tenant properties are stored as a web property bag entry in the root site of the App Catalog site collection. The properties are serialized to a JSON string and stored in the property bag entry with the key "storageentitiesindex":

(click to zoom)

Using Managed Service Identity with Key Vault from a .NET Azure Function

$
0
0
So Managed Service Identity along with Azure Functions support went GA recently. If you want to read the announcement and also want to get an overview of MSI, head over here: https://blogs.msdn.microsoft.com/appserviceteam/2018/06/26/announcing-general-availability-and-sovereign-cloud-support-of-managed-service-identity-for-app-service-and-azure-functions/

In this post, lets have a look at how easy it is to configure Managed Service Identity for an Azure Function and how it can be used together with Key Vault to secure sensitive information like Client Ids, Client Secrets and Passwords.

For example, when building an Azure Function which will interact with some data in SharePoint Online, we need a way to authenticate the Function with SharePoint. Two common methods used for authentication are 1) By creating an Add-In registration in SharePoint (appregnew.aspx) and 2) By using Azure AD authentication by creating an App Registration in Azure AD.

In both cases, we need to secure the ClientID and ClientSecret for the registration in such a way that only our calling code has access and any non-admin user browsing the Azure Function in the portal is prevented from seeing the sensitive data.  So let's see how we can do that using Managed Service Identity:

1) They very first thing you need to do is make sure that Managed Service Identity is configured for your Function App. You can do this simply by going to Function App Settings -> Managed Service Identity and ensuring that it is turned ON.


2) Create a Key Vault (or go to an existing one) and create two Secrets with names "ClientID" and "ClientSecret". You can also create additional Secrets relevant to your solution here.



3) Now we want our Azure Function App to have permissions to access the Key Vault. To do this, go to Access Policies -> Add New


4) Select the Function App as the principal and make sure under Secret permissions, it has at least the "Get" permission:


5) That's it in terms of the config! Now let's move on to the code to access the secured Client ID and Secret:

Make sure your Azure Function has the following NuGet packages:

Microsoft.Azure.KeyVault
Microsoft.Azure.Services.AppAuthentication

And here is an HTTP triggered .NET pre-compiled function which fetches the ClientId and ClientSecret from the Key Vault:

That's it! I was really surprised how easy MSI support for Azure Functions makes securing sensitive data. This way, any keys, secrets or passwords used by our solutions can be secured and retrieved without worrying about them getting in the wrong hands!

Quick note on pricing for MSI and Key Vault:

Sync SharePoint User Profiles using Azure Durable Functions

$
0
0
Recently, a client had asked us to synchronise user properties from their Azure AD profile to custom properties in their SharePoint UserProfile. This had to be a scheduled process as the data had to be kept up to date as well as it had to cater for any new profiles created in Azure AD/SharePoint.

We decided to use Azure Functions for this given the ease of configuring a timer triggered function (to run on schedule) and also the fact that functions run on a consumption based billing plan. This means that the client would get charged only for when the function executes (oh and also, the first million executions are free every month)

The main challenge we had to overcome was the limitation that an Azure Function has a default timeout of 5 minutes (which can be increased up to 10 minutes at the time of this writing) This means that if we were using a single Azure Function to update SharePoint UserProfile Properties for thousands of users, we were going to hit the timeout sooner or later. 

Fortunately, Durable Functions went GA recently which means that we have a way of managing state in the traditionally "state-less" Azure Functions. With durable functions, we can create an "activity" function to update the SharePoint User Profile properties for a single user. This function can be called in a loop for each user from an "orchestrator" function. Each run of the activity function is treated as a single execution and can be finished in the 5 minute default timeout.

So let's see how this can be done! We are using precompiled C# functions and Visual Studio 2017 to achieve this. Also make sure to have the Durable Functions nuget package installed in your Azure Functions project:
https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/

The Durable Function workflow can be categorised into three different types of functions:


1) Client Function


These are standard Azure Functions which can be triggered by external events like timers, HTTP requests, queues etc. The only difference being they have an OrchestrationClient binding which is required to start orchestrations.

In our case, the Client Function is a simple timer triggered function which uses the OrchestrationClient to start a new Orchestration Function with the name O_SyncProfileProperties


2) Orchestrator Function


As the name suggests, the Orchestrator function acts as a coordinator of the Durable Functions workflow. It does the job of starting, stopping and waiting for activity functions and is also in charge of passing data (or state) in between them.

In our case, it calls the A_GetUsersToSync activity function to get the user profiles from Azure AD (using the Microsoft Graph API which is out of scope for this article) and then loops through the users to call the A_UpdateSharePointProfile function for each user


3) Activity Functions


As you might have guessed by now the Activity function is the one which actually does all the heavy lifting work. For example, the actual CSOM code which will update the SharePoint UserProfile properties will live in the A_UpdateSharePointProfile activity function:

And that's it! In 3 simple steps, we have a Durable Functions Orchestration set up. For more information on Durable Functions including dos and don'ts, please see the documentation: https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-overview

Hope you found this post useful!

Create Azure AD App Registration with Azure CLI 2.0

$
0
0
Previously, I have written about creating an Azure AD App registration using the Microsoft Graph API and PowerShell. But since then, the beta endpoint for creating app registrations had stopped working as reported in this GitHub issue: https://github.com/microsoftgraph/microsoft-graph-docs/issues/1365

Fortunately, I have recently discovered a great way to create Azure AD App Registrations using the Azure CLI 2.0. This also includes adding any permissions the app requires on resources e.g. Microsoft Graph, Office 365 SharePoint Online etc. This has not been previously possible with the Azure AD PowerShell Cmdlets.

So in this post, let's go through what is needed to achieve this:

First, you need to have the Azure CLI 2.0 installed on your machine. Follow this link to get it if you haven't already:
https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest

Once you have the CLI, here is the code to create an Azure AD App Registration including the required permissions:

The JSON in the requiredResourceManifest.json file can be fetched from the manifest of an App registration already created in Azure AD. So the recommendation would be to manually create an App Registration in Azure AD and configure the required permissions. Once you have the right set of permissions, edit the manifest and grab the JSON from the requiredResourceAccess array.

Trusting the App:
Bear in mind that the code in this post will only create the app registration. It will not grant the permissions which can only be done by an Admin by going to the app and clicking on the "Grant Permissions" button:


For more possibilities with the Azure CLI 2.0, checkout the reference: https://docs.microsoft.com/en-us/cli/azure/reference-index?view=azure-cli-latest

Code Splitting in SharePoint Framework (SPFx)

$
0
0
Code splitting is not a new concept to TypeScript/React/Webpack developers. In short, it is a optimisation technique which allows us to split our application bundle into smaller bundles and load them on-demand only when required.

E.g. when a React component or a third party package is only needed when the user clicks on a certain button, then there is no need to load in on the first page load. It can be fetched on-demand when the button is pressed. This reduces the amount of data fetched over the wire on first page load, thus improving performance and user experience. This can be particularly helpful in large applications with many third party packages and components.

In this post let's have a look at how to do code splitting in the SharePoint Framework. As an example, I am going to use an SPFx web part created using React but the code splitting approach can be used with other frameworks/libraries as well.

We are going to have a look at two scenarios where code splitting can really help:

1) Loading a React Component on-demand (where we load the DetailsList component from Office UI Fabric)

2) Loading a third party package on-demand (where we load the infamous-for-its-large-size moment js)

So to begin with, here is my render method of a React component created by default by the SPFx yeoman generator:

I have edited it to show only 2 buttons. This component will be our "main" component which will load other components and third party packages when a user clicks on the relevant button.

Load a React Component on-demand: 


The _loadDocumentsClicked function will fire when the user clicks on the Load Documents button. The DetailsList component is defined in a file called DetailsListComponent.tsx which is in the same folder as the main component.

Once the import function fetches the DetailsList component class, we create an an instance of the class and use ReactDom to insert the component to the detailsContainer div in our main component. 

Load a third party package (moment js) on-demand:


Similarly, the _loadMomentClicked function will fire when the load moment js button is clicked. it will fetch the moment package and then assign the value of moment().calendar() to a property in the current component's state.

And here is the code in action on a modern SharePoint page:

(click to zoom)

What is also important to note is that the bundle will be loaded only if it was not loaded earlier. The import function is smart enough to determine if the bundle is already downloaded and it does not request it again.

Hope you found this useful!

As always, the code for this is available on GitHub: https://github.com/vman/SPFxCodeSplitting

Code Splitting in SharePoint Framework Part 2: Optimizing the SharePoint Starter Kit

$
0
0
I was having a look at the SharePoint Starter Kit recently and I have to say it's a very useful collection of sample SPFx webparts, extensions and other modern SharePoint building blocks. You should check it out if you haven't already.

While I was looking at the different components, I noticed something interesting: When I created a production package and observed the minified JavaScript files, the file-sizes were bigger than expected.

(Note that these are just the sizes when the files are extracted on the file system. When they are included in a package and loaded on SharePoint pages, they will be compressed so their sizes would be smaller. The image is just to help compare the file-sizes after the optimisations)


So I started having a closer look at the SPFx components and noticed a few interesting things:

1) @pnp/sp: 


As expected, many of the components were using the @pnp/sp package but each component was statically importing it. This meant that each component will have a copy of @pnp/sp in its individual bundle:

The solution was to implement code splitting and separate out @pnp/sp into it's own file:

This approach has two benefits:
  • All components share the same @pnp/sp bundle
  • The @pnp/sp code is loaded dynamically on the page only when required

2) @pnp/spfx-property-controls


The @pnp/spfx-property-controls package is great when it comes to having pre-created custom controls to use in the SPFx webpart property pane.

One thing worth noting though is that the property pane is loaded much less frequently than the webpart code itself. The property pane is used only to configure the webpart so the code is only needed then and not when the web part loads normally on the page.

To further optimize the webpart bundles, we can separate out the property pane code (including the components from the @pnp/spfx-property-controls package) and load it on the page dynamically only when the property pane is loaded.

So instead of statically importing the property pane components like this:

We could dynamically import them:

This would also mean that the property pane custom controls will be split into their own JavaScript bundles and multiple webparts using the same type of control will share the code:


This is particularly helpful with controls like `PropertyFieldCollectionData` which is more than 700kb in uncompressed format!

After implementing both these changes, we can see that the file sizes have been considerably reduced:


Also important to note is not only the filesize reduction, the main benefit of this approach is that there is no duplicate code in the components.

I have submitted a Pull Request with these changes to the SP Starter Kit GitHub repo if you want to checkout the code:
https://github.com/SharePoint/sp-starter-kit/pull/216

Here is a link if you want to checkout the official Microsoft docs on dynamic loading of packages:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading

Thanks for reading!

Working with Application Permissions (App-Only Auth) in SharePoint Online and the Microsoft Graph

$
0
0
When working with SharePoint Online or the Microsoft Graph, there are many scenarios in which we need to read or write data without a user context. It might be a scheduled process, or it might be an operation that requires elevated permissions. In such scenarios, it is quite common for the solution to use "Application permissions" (a.k.a App-Only Authentication). This lets the solution have its own identity which can be used to grant the required permissions.

When working with Application permissions in Office 365, there are a lot of moving pieces to deal with like Client Ids, Client Secrets, Azure AD App Registrations, Certificates, Add-In Registrations, AppRegNew.aspx, AppInv.aspx etc.

What I want to do in this post is to explore different options for configuring and granting application permissions. There are a few combinations possible with the different moving pieces. My aim in this post is to explore them and determine which combination might be suitable for certain scenarios. We will also see some sample code which demonstrates how to authenticate with SPO and the Microsoft Graph using the different authentication options.

Here is a table I have put together which summarises the different options for working with applications permissions in SPO and the Microsoft Graph API. We will go through each on them in detail.



1) Interact with data from SharePoint Online with an Azure AD App Registration


If your solution uses an Azure AD App Registration created from the Azure AD portal and you want to read or write data to SharePoint Online:

You will need to use a Client Id and Certificate. Have a look at this link for details on how to create an AAD App Registration as well as the certificate: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread

If you try to use a Client Id and Client Secret created through AAD portal you will get the following error:

Microsoft.SharePoint.Client.ServerUnauthorizedAccessException: 'Access denied. You do not have permission to perform this action or access this resource.'

You will also get the "Access Denied" error if you try to write to the User Profile service. Reading from the User Profile service will work. If your solution needs write User Profile service access, your only option would be to use an Add-In registration (see the next section). Writing to the SPO Taxonomy Service will not work either through AAD App Registration or Add-In Registration. Read operations will work. See notes at the end of this post.

Here is some sample code to demo how to use a Client Id and Certificate with the AAD App Registration. You will need the SharePointPnPCoreOnline NuGet package:


2) Interact with data from SharePoint Online with a SharePoint Add-In Registration:


If your solution uses a SharePoint Add-In Registration (created through the /_layouts/15/AppRegNew.aspx page) and you want to read/write data to SharePoint Online:

You will need a Client Id and Client Secret created through the /_layouts/15/AppRegNew.aspx page and permissions granted from the /_layouts/15/AppInv.aspx page

See this link for details on how to create as well as assign permissions to the Add-In Registration:
https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azureacs

Here is a sample of how to use the Add-In registration to interact with data from SharePoint. You will need the SharePointPnPCoreOnline NuGet package.



3) Interact with data from the Microsoft Graph with an Azure AD App Registration


If your solution needs to interact with the Microsoft Graph, the only option is to have an Azure AD App Registration. However, within the Azure AD App Registration you can either use a Client Id, Client Secret pair or you can use the Client Id, Certificate pair as well.

1) Using a Client Id and Certificate:


The process to create the AAD App Registration and Certificate is exactly the same as described above in section 1. Only difference would be that instead of selecting SharePoint Online permissions, the App Registration will have to be granted the relevant permission to the Microsoft Graph.

Once that is done, here is the sample code to use the Client Id and Certificate to get data from the Microsoft Graph:



2) Using a Client Id and Client Secret:

The only change in this approach is using a Client Secret (Password) instead of a certificate. See this link to see how to generate a client secret for the AAD App Registration:
https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#get-application-id-and-authentication-key

Once you have the Client Id and Client Secret, you can use the sample code to get data from the Microsoft Graph:


In conclusion: 


Considering all factors, I would personally go with one of these two options:

If the solution is strictly going to deal with SharePoint Online data and not any other part of Office 365, you might want to consider the SharePoint Add-In Registration approach with a Client Id and Client Secret. That way you don't have to mess around with certificates. But remember that in the future if the same solution is going to read/write data from the Microsoft Graph, you might have to create another App Registration in Azure AD.

Another option would be to use an Azure AD App Registration with a Client Id and a Certificate. This allows us to interact with most of Office 365 data (including SharePoint Online and the Microsoft Graph) without maintaining separate applications. The caveats to this approach being the added complication of generating and managing certificates and also the fact that writing data to SharePoint Online Taxonomy and User Profile will not work (Reading data will be possible)

Notes:

1) Writing to the SPO Taxonomy Service with Application Permissions does not work from either AAD Portal or Add-In Registration. Read operations work. See more details here:
https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly#what-are-the-limitations-when-using-app-only

2) For the purpose of this post, I have only considered Azure AD v1.0 endpoint as we are only concerned with organisational accounts and not personal accounts.
https://docs.microsoft.com/en-gb/azure/active-directory/develop/azure-ad-endpoint-comparison

3) Technically, Add-In registrations created from the AppRegNew.aspx page are also registered in Azure AD. They are not visible through the AAD portal but you can list them via PowerShell.

4) It is also possible to create an App Registration in Azure AD and then use the AppInv.aspx page in SharePoint Online to assign it SharePoint specific permissions. You can also use this approach to assign a client secret which never expires to the Add-In registration. For more details on this, you can see this post by the very talented Sergei Sergeev https://spblog.net/post/2018/08/24/SharePoint-lifehacks-create-SharePoint-app-registration-with-client-secret-which-never-expires

It would be great if we can get some confirmation from Microsoft about this approach being supported/recommended. But even then, we will have to manage the SharePoint permissions in a different location than the Microsoft Graph permissions.

Hope you've found the post helpful!

Debugging a Microsoft Teams Tab built with SharePoint Framework

$
0
0
With SPFx 1.7, the ability to build Microsoft Teams tabs with SharePoint Framework was released in preview. It's not just a SharePoint page hosted in a teams tab, it's aware of the context information around the current user, team, channel, tab etc. and is also able to interact with the team.

Here is the Microsoft docs article about it, have a read if you haven't already:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-web-part-as-ms-teams-tab

The article walks us through the process of building and publishing a production package of an SPFx solution so that it can be used as a SPFx webpart as well as a tab in Microsoft Teams.

In this post, we will go through the process of actually debugging the solution when it is being built. During development, when we make changes to the TypeScript code, we don't want to publish a production package to the App Catalog every time to test our changes.

The ideal development flow for me is to upload the SPFx package once to the App Catalog on the dev tenant, run gulp serve and when changes are made to the code, they should immediately be available to test as a Microsoft Teams tab.

To achieve this, first you will need to create an SPFx 1.7 solution as described in the docs, then update the webpart code to make sure that the microsoftTeams.Context is available to consume.

(Note: Make sure to use the SPFx v1.7.0 and not SPFx v1.7.1 which is the latest version at the time of writing this article. There is a bug in the latest version which does not create the teams folder automatically through the generator. More details here)

Now here is where the process deviates. Instead of creating the production package with the --ship flag, we will create a development package with:

gulp bundle

and then

gulp package-solution

After which you should see the console screen similar to this:


Getting the warning which says that the scripts (Client Site Assets) will not be packaged with the solution is important as we want them to be referenced from the local dev machine.

Next, we will upload and deploy the package to the tenant app catalog as usual:


Notice that the scripts will be loaded from localhost.

Next we need to make the scripts available from localhost. For that, we will run:

gulp serve



Now we need to upload the teams package which will make the SPFx webpart available in teams as a Tab.

This process is exactly similar to how it's done in the Microsoft docs article:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-web-part-as-ms-teams-tab#packaging-and-deploying-your-web-part-as-a-microsoft-teams-tab

We make sure that side loading of apps is enabled on Teams, then go to any Team > Ellipsis > Manage Team >  Apps > Upload a custom app > Upload the Zip file.

Then go to a channel in the Team > The plus button (+) > Add a tab > Select your app > Save

That's it! You Teams tab is now loading the scripts from localhost:


Make sure gulp serve is still running and then you can test by changing something in the code and press the reload tab button. The tab should be updated with the new code.

You can also test in the Teams Desktop client. Just make sure that the "Developer Preview" option is enabled:


Thanks to petkir for this tip!

After this, your SPFx tab should load in on the Teams Desktop client as well:


Hope this helps!

Create Azure DevOps pipelines for a Microsoft Teams app built with SPFx

$
0
0
In this post, let's walk-through the configuration needed to create Azure DevOps build and release pipelines for a Microsoft Teams solution. 

The solution is built using the SharePoint Framework and will be surfaced in Microsoft Teams as a Tab. 

As the code for the SPFx solution is being hosted in a GitHub repository, I will only focus on the Azure Pipelines configuration. For information on how to work with Azure Repos, see the official Microsoft docs: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/toolchain/implement-ci-cd-with-azure-devops

These are the high level steps we are going to follow:

Build pipeline:


1) Create production package of the SPFx solution (.sppkg)
2) Create a Teams manifest package (.zip) 
3) Publish the SPFx and Teams packages as Artefacts to be consumed from the Release pipeline

Release pipeline:


1) Deploy the SPFx package to the SharePoint Tenant App Catalog (using the Office 365 CLI)
2) Deploy the Teams manifest package to the Microsoft Teams App Catalog (using the Office 365 CLI)

So without further ado, here are the screenshots and scripts to achieve this:

(Click on the images to zoom)

Build pipeline:



Use Node 8.12.0:


npm install:


gulp bundle --ship:


gulp package-solution --ship:


The .sppkg file needs to be copied to staging directory in order to be published to the release pipeline:


Create a .zip file for the teams app catalog and copy it to the staging directory as well:


Publish the .sppkg and .zip files so that they can be consumed from the Release pipeline:



Release pipeline: 


Now to create a new Release Pipeline which will deploy the packages to the respective app catalogs in the tenants.

The Artefacts used will be the .sppkg file and the .zip file for Teams which was published in the previous step:


Overview of tasks in the Release pipeline:


Before moving on to the tasks, we also need to configure some variables to hold the information for the tenants, credentials etc.


The username and password should be for an account with global admin permissions. This is because the Microsoft Graph API (used by the Office 365 CLI) needs this permission to deploy apps to the Microsoft Teams App catalog:
https://docs.microsoft.com/en-us/graph/api/teamsapp-publish?view=graph-rest-1.0#permissions

The TeamsManifestId will be used to check if the app already exists in the Teams App catalog. This can be fetched from the manifest.json file in the teams folder of the SPFx solution:


If you are feeling adventurous, you could look at cracking open the .zip file from the Release pipeline and then grabbing the id on-the-fly instead of having to specify it in a variable.

Next, we will need to use Node (same task as the build pipeline) and install the Office 365 CLI on the Release agent using:

npm install -g office365-cli


Next, we need to deploy the SPFx package to the SharePoint Tenant App Catalog:


Here is the script as a gist:

And lastly, we need to publish the Teams manifest to the Microsoft Teams App Catalog. We will do this using the Office 365 CLI as well. Thanks to Elio and Waldek to get this functionality in the Office 365 CLI at lightning speed!


Here is the script as a gist:

And that's it! You now have build and release pipelines configured to deploy SPFx solutions and Teams apps. The webpart will now be available in SharePoint and the tab will be available in Teams:



 Hope you found this walk-through useful!

Dependency Injection in SPFx: Using Service Scopes

$
0
0
I have written about this topic in the past but with the recent increase in SharePoint Framework adoption and with more features becoming available (e.g. MSGraphClient, AadHttpClient), I felt it would be a good time to revisit this.

With SPFx solutions getting more complex day by day and with lots of components to manage, passing the web part (or extension) context around to different parts of your code can get difficult to maintain real fast.

The problem:


For example, imagine we have created a custom service which needs the MSGraphClient to make a call to the Microsoft Graph and we are consuming this service in our SPFx webpart. To initialise the service, we need to either pass in the entire web part context to it, or explicitly pass in the MSGraphClient object from the context.

In the first case, we are unnecessarily passing in all other objects in the context to this service as well.

And in the second case, our code becomes tightly coupled i.e. in the future, if our service needs something else from the webpart context, we have to update the service as well as the consuming code (i.e. the webpart) to pass in the new dependency.

The solution:


Using Dependency Injection in SPFx through service scopes, we can abstract away the implementation details of our custom services from the calling code (i.e. webparts, extensions).

With service scopes, we can get hold of instances of SPFx classes like MSGraphClient, AadHttpClient, SPHttpClient and PageContext from our services without having to explicitly pass them in from the webparts.



In this post, lets see how to achieve this:

1) Calling the MSGraphClient from a custom service:



Consuming the custom service from an SPFx webpart:


If you observe the code, our calling webpart does not have any knowledge of the implementation details of the custom service, specifically the fact that it used the MSGraphClient internally to retrieve the current user details. In the future, if we wanted to change the implementation of the service, we could do it without changing any web part code.

2) Calling the AadHttpClient from a custom service:


Similarly, we can also use service scopes to get hold of an instance of the AadHttpClient class. In the code below, to keep things simple, I am using the AadHttpClient to make a call to the Microsoft Graph. Eventually, the result is the same as the previous code but it should demo how to use the AadHttpClient through service scopes.


Consuming the custom service from an SPFx webpart:



3) Calling the SPHttpClient from a custom service:


And finally, if we just want to make a call to the SharePoint REST API from our SPFx code, we can also use service scopes to get hold of an instance of the SPHttpClient class. The following code also demonstrates how to get an instance of the PageContext class to get different run time context values like the current web url. Using the current web url, we can make a call to the SharePoint REST API to get the web details:


Consuming the custom service from an SPFx webpart:


And that's it for this post! I am planning more posts on this topic in the future so keep an eye out for those :)

As always, the code from this post is available on GitHub: https://github.com/vman/ServiceScopeTestBench

Dependency Injection in SPFx: Using child scopes to work with multiple service instances

$
0
0
In the previous post, we saw how SharePoint Framework code can be decoupled by using Dependency Injection and Service Scopes. In short, we are able to register instances of our services on the "global" service scope and then consume those instances from any part of our code.

We can also consume instances of default services registered on the global service scope e.g. MSGraphClient, AadHttpClient, SPHttpClient etc. These registrations are done by the SharePoint Framework and all we have to do is hook into the global service scope to fetch their instances.

Now, when we register our custom service on the global service scope, all components on the SharePoint page share a single instance of the service. By components, I mean SPFx web parts and extensions. In this post, to keep things simple, I will only talk about web parts but the concepts apply to extensions as well.

When the first webpart on the page makes a call to the service scope, an instance of the custom service is created and returned. Any further calls made by the same or different webparts on the page will return the same instance of the service. Now this behaviour can be either good or bad depending on your requirements.

If you want different webparts on the page to have their own instances of the custom service, let's see how to achieve this:

Let's consider a simple custom service:

The service contains a private variable count which is initialised to 0. The increaseAndReturnCount function increases the count by 1 and then returns the value. Not the best code ever written but fits our current purpose.

Now, let's register and consume an instance of our custom service from an SPFx webpart:

If you are wondering where do we register this service as we only seem to be calling the consume function. The consume function does the registration for us by creating a default instance of the service, registering it on the service scope and then returning the same instance.

When we register our service on the global service scope returned by this.context.serviceScope, all webparts and SPFx components will share the same service instance:



Although multiple webparts are added on the page, they all share the same service instance and the same count variable keeps on increasing. OK, technically I am adding the same webpart multiple times on the page but the behaviour is the same even for different webparts calling the same service.

Now, to get around this issue, we only need to make a simple change to our code. We don't need to change any of the service code, we just need to change the way in which the service instance is created and returned:

By creating a new service scope as a child of the global service scope, we get a new service scope which is isolated to the current web part. This way, each web part will get it's own service scope to register and consume service instances.

And as a result, we get a new instance of the counter service for each web part where the counter variable is initialised to 0:


This way, we are able to create and maintain isolated service scopes per component on our page.

Hope you found this post useful!

The code is in GitHub if you want to take a look: https://github.com/vman/MultiInstanceServiceScopes

Using Microsoft Rush to manage SPFx projects with library components

$
0
0
If you haven't heard about Rush JS yet, you are probably not alone. In my opinion it's one of the hidden gems of the SPFx ecosystem or even the larger web dev ecosystem for that matter. It's a great open source tool which can be used to manage large repositories (monorepos) containing multiple node/npm projects with dependencies on each other. To know more about Rush, you can visit its website: https://rushjs.io/

In this post, we are specifically going to look at how we can use Rush to manage SPFx repositories containing multiple solutions including SPFx library components, web parts and extensions.

With SPFx library components being introduced in preview in v1.8 and expected to be generally available in v1.9, sooner or later you might find yourself trying to manage large SPFx repositories like this:
(click to zoom)

In this repo, there are 4 SPFx solutions: The org-app solution contains SPFx components which depend on org-library which is an SPFx library component. org-library itself depends on another SPFx library component util-library. And finally, another "consumer" solution org-app2 depends directly on util-library

When working in this type of repository, things will get tricky to manage real fast because of the various SPFx solutions, their dependencies on each other, needing to run gulp build or gulp serve multiple times after changing a single file, remembering which consumer projects to update after the libraries have been changed, managing multiple node_modules folders (if not using pnpm) etc. This is where Rush comes in.

If all projects are part of one Rush repository:
  1. You don't have to manage node_modules folders individually for each project. Rush will maintain a common node_modules folder and create symlinks in each project which point to it.
  2. Rush will also create a single lock file for your entire repository and all projects.
  3. You don't have to use npm link to connect your library to consuming webpart packages. Rush handles all the linking and unlinking for you.
  4. When you are working on the library and the consumer components simultaneously, you don't have to run gulp build multiple times. You just need to run "rush build" once. More on this later.
  5. When working on multiple libraries and components with different dependencies, you don't have to keep track of which webpart depends on which library. Rush will do this for you and handle the necessary updates.
  6. Based on your project dependencies, Rush will parallelise builds of projects which do not depend on each other. Thus, reducing build time.
Now before you go converting all your existing repositories to Rush, there are a couple of considerations to think about as well:
  1. Most Rush commands only work with Git repositories. It uses git change tracking to determine which files changed and which repositories to update. So, if you are using TFVC or any other version control system, you might be out of luck. More details on this here: https://github.com/Microsoft/web-build-tools/issues/1250
  2. When working on SharePoint Framework projects in a Rush repository, the local workbench seems to be broken currently. The SPO workbench works. A bug is logged here: https://github.com/SharePoint/sp-dev-docs/issues/3736
Now with all that out of the way, lets create a simple Rush repository with just couple of SPFx components! What we are going to do is create a Rush repository, create an SPFx library component (corporate-library), then create an SPFx webpart component (corporate-apps) and then consume the library component from the webpart. We will then use Rush to build and manage this repository.



First, we need to perform some setup:


This should install rush, convert your root folder to a git repo and a rush repo, and then commit the rush files to git.

Next, we will create the SPFx library component:


Notice we are using the --skip-install flag as we don't want to install the npm packages right now. We will do this with rush later. Select the options as in the following image:


Add the SPFx library component to your git repo:


Next, we will create the SPFx webpart project:


We will again use the --skip-install flag to skip installing the npm packages.

Select the options as per the following image:


Now add the webpart project to your git repo:

Your final solution structure should look like this:


Next, we need to tell Rush that the corporate-apps project has a dependency on the corporate-library project. We will do that by updating the dependencies in the corporate-apps/package.json file


After this, we need to configure Rush to work with our repository and project structure. For this, we need to update the rush.json file in the root folder. 

We can define the node, npm and rush versions we will use for this repository. You can use other package managers like pnpm and yarn with rush as well. For a list of all available options see here: https://rushjs.io/pages/configs/rush_json/

More importantly, we need to define our project folders and package names in this file. This is what Rush uses to find folders corresponding to package names.

By default, the file has a lot of config options and comments, but we only need these values for the demo:


Next, as we are going to use npm as the package manager, we need to delete the common\config\rush\pnpmfile.js file as it is only meant to be used with pnpm.

After this, it is time to install all the npm packages needed by all the projects in our repository. For that we just need to run:




Rush will install all npm packages in the common\temp\node_modules folder and create symlinks in each project to the common folder.

Next, we need to build our repository with:




Due to the dependencies added the corporate-apps/package.json file, Rush will build the library project first and then the consumer project.

Now we can use the code from our library project in our webpart project. Go to the corporate-apps\src\webparts\consumer\ConsumerWebPart.ts and add the following code:

If you run rush build again, you will see that the library project is not updated. This is because rush knows that only the webpart files changed and it should only update the relevant projects. Here is a working demo of the repository:

(click to zoom)

And that's it! You can now build on this to start including multiple SPFx libraries and components to your repository.

Here is an image of a larger Rush repository in action (the same one mentioned at the start of this blog) You can see that depending on the files changes, Rush updates only the required projects.


Also, all your projects in the Rush repository are still standalone SPFx projects. You can navigate to each project's working directory and run gulp build, gulp bundle or gulp package-solution just like you would in normal SPFx projects.

Hope you find this post helpful! Let me know in comments if you feel there is anything I have missed.

Links:

1) The simple Rush repository we built in this post: https://github.com/vman/SPFxRushSimpleDemo
2) Slightly more complex Rush repository: https://github.com/vman/SPFxRushComplexDemo
3) SPFx library component overview: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/library-component-overview
4) Rush website: https://rushjs.io/

Deploying Azure Function Proxies along with Function Apps

$
0
0
I am working with Azure Function Proxies in my latest project and I have found them to be a really handy tool! With Function Proxies, we can expose endpoints in a Functions App which redirect to other resources in Azure (or anywhere really).

I won't go into much detail describing Proxies, Microsoft docs has some great info already:

"With proxies, you can specify endpoints on your function app that are implemented by another resource. You can use these proxies to break a large API into multiple function apps (as in a microservice architecture), while still presenting a single API surface for clients."
https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies

In my case, we are working on a front-end app built using TypeScript and React which uses Azure Functions as the back-end. For hosting the front-end, we have turned on the static website hosting for blob storage and then placed the HTML, JS and CSS files in it. Then, used Azure Function Proxies to redirect the endpoints in the Function App to the blob storage URLs of the front-end resources. It's all working quite nicely!

Only speed-bump in this type of architecture is the cold start experienced by serverless resources on the consumption plan. This is not a problem for us right now because this application is a proof of concept for a larger project. (There are other options available as well which include moving the Function App to an App Service Plan or using the Azure Functions Premium plan when it comes out preview. But that is a discussion for another time)

Back to the current post! When I was looking into deploying this application as part of the CI/CD pipeline using Azure DevOps, I was looking for an easy way to deploy the Azure Function Proxy configuration as well. Fortunately, I came across this Microsoft docs article which talks about a file called proxies.json. If this file is found within your Function App, the functions runtime reads it and creates the necessary proxies!

Here is how my proxies.json file looks in Visual Studio. Make sure the file is copied to the output directory:


And once the Functions App is deployed:


This way, if you are deploying and recreating Function Apps with your CI/CD pipeline, you can automate the creation of Function Proxies as well!

Hope you found this post useful.

Create SPFx Library components containing Office UI Fabric React

$
0
0
When using Office UI Fabric React (OUIFR) components in SPFx projects, if you analyse the bundle structure you will notice that OUIFR components take up a lot of space which leads to increased bundle size.

If there are multiple SPFx components loaded on the page which depend on OUIFR components, the size of each bundle will be affected, as by default the OUIFR components are included in each SPFx bundle.

One way to mitigate this issue is to include all your custom components (which depend on OUIFR) into an SPFx library component and then consume the library component from SPFx webparts and extensions. This will make sure that if you have reusable custom components, they won't be loaded multiple times on the page if multiple SPFx webparts (or extensions) are consuming them.

Let's see how to achieve this. We will be working with SPFx 1.9.1 which contains the GA version of Library Components:

1) Create SPFx Library and Consumer component structure


First, make sure you have an SPFx library project as well as a "consumer" SPFx project containing webparts (or extensions). Instructions on how to create this are in the Microsoft docs: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/library-component-tutorial

The code for this post is also available on GitHub if you want to have a look: https://github.com/vman/spfx-lib-components-ouifr


2) Create a new custom react component which internally uses Office UI Fabric React


In the library component project, create a new react component in a new file e.g. ButtonComponent.tsx


Here are the contents of the custom react component:


3) Update the index.ts file


In the index.ts file of the library component, include the new component to be exported:


4) Update config.json


Next, in the config/config.json file of the library component, make sure that the entry point is pointing to /lib/index.js


This step was not necessary in SPFx 1.8.2-plusbeta but seems there is a bug in SPFx 1.9.1. I have created a GitHub issue here:

5) Update the consumer web part


Make sure that the library component is referenced in the consumer webpart e.g. through npm link or by using a monorepo manager like rush or lerna.

More details on using npm link in this in the Microsoft docs: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/library-component-tutorial

And finally, in your consumer SPFx react webpart, you can reference the custom component:


When you deploy both the SPFx packages in the app catalog and then add them on a page, you will see different bits of code being loaded separately:



And that's it! Sample repo on GitHub: https://github.com/vman/spfx-lib-components-ouifr

Viewing all 134 articles
Browse latest View live