When developing a custom Teams tab with the SharePoint Framework, we want to make sure that the SPFx custom tab (web part) is styled according to the current selected theme.
Also, if the user switches the theme (e.g. from default to dark), we want to make sure that the styling of our SPFx web part also changes without the user having to reload the tab.
Let's see how we can do this. In this post, depending on the Teams theme, we will dynamically add a CSS class to our top level react component. And when the theme changes, we will change this class as well. That will be the scope of this post without going into further details of Styling/CSS.
Also, since I have been exploring React hooks recently, we will be using them in the demo code.
The very first thing we need to do is to make sure that our SPFx tab loads with the currently selected theme when the tab loads for the first time.. This can be achieved with the teamsJS sdk bundled with SPFx:
The theme property will either have the values "default", "dark" or "contrast" (at the time of writing this post). Depending on this property, we can add a CSS class to the top level container of our web part.
Now the important part. When the user updates the theme, we want to fire an event which can be used to set the new theme in our tab. Luckily the teamsJS sdk provides us with a function which can be used to register this event handler:
https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/access-teams-context#theme-change-handling
With this, we are able to capture the theme update dynamically and update our component state. Next, we also want to change the CSS class of the top container when the theme changes. We will do this by using the useEffect hook again and setting the class depending on the selected theme.
This hook takes in the themeState as a dependency which means that it will run anytime themeState changes. We will use it to update the styleState.
I could have been a bit more clever here and simply updated the class in the registerOnThemeChangeHandler function itself. But I wanted to keep the themeState and styleState separate to have them loosely coupled and also to play around with hooks a bit more ;)
Here is the complete code for the React Functional component:
Hope you found the post useful! As always, the code for this post is available on GitHub: https://github.com/vman/spfx-teams-theme-hooks
Monday, 27 April 2020
Thursday, 5 March 2020
Office 365 CLI: Grant admin consent to specific scopes using Microsoft identity platform (AAD v2)
The Office 365 CLI is great tool for a variety of scenarios. Specifically, I find it super useful for CI/CD and automation. Since there is no official Microsoft Graph Azure DevOps task yet, the Office 365 CLI comes to the rescue when we want to make a call to the Graph from an Azure DevOps pipeline. Most recently, I was using it to deploy a Teams app when I noticed something interesting.
As I was calling the Office CLI from an automated script, I could not use the default deviceCode flow to login as there was no user interaction possible. So the other options were to use the username/password flow or use a custom certificate. I chose the former as it was very convenient and I could store the password securely in a Azure DevOps secret variable.
Now before using the Office CLI on a tenant, we need to consent to the AAD multi-tenant app used internally for authenticating to Office 356 services. This app is called "PnP Office 365 Management Shell" and the consent process is also described in the docs here: https://pnp.github.io/office365-cli/user-guide/connecting-office-365/
If this consent hasn't happened yet, we get the following error:
Error: AADSTS65001: The user or administrator has not consented to use the application with ID '31359c7f-bd7e-475c-86db-fdb8c937548e' named 'PnP Office 365 Management Shell'. Send an interactive authorization request for this user and resource.
If we go ahead with the default consent experience, we are presented with this screen where the CLI AAD app requests a large number of permissions on the tenant:
Now in my case, I did not need all the scopes the CLI was requesting. I only needed AppCatalog.ReadWrite.All to deploy my Teams app.
So that got me thinking, one of the benefits of using the Microsoft identity platform (also called AAD v2) is that apps can request consent to specific scopes:
With the Microsoft identity platform endpoint, you can ignore the static permissions defined in the app registration information in the Azure portal and request permissions incrementally instead, which means asking for a bare minimum set of permissions upfront and growing more over time as the customer uses additional app features. To do so, you can specify the scopes your app needs at any time by including the new scopes in the scope parameter when requesting an access token - without the need to pre-define them in the application registration information.
https://docs.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison#incremental-and-dynamic-consent
So armed with this knowledge, I tested whether I could request (and grant) only AppCatalog.ReadWrite.All to the PnP Office 365 Management Shell app.
Turns out it is indeed possible by going to this URL in a browser as an admin:
You will notice that in the client_id param, we are specifying the client id of the PnP Office 365 Management Shell app and in the scope param, we are only requesting the needed scope i.e. AppCatalog.ReadWrite.All
This time, we are presented with a significantly reduced scopes for consent. Notice only the "Read and write to all app catalogs" permission is present along with the couple of default permissions.
After granting the consent and then navigating to Azure AD -> Enterprise Applications -> All applications -> PnP Office 365 Management Shell -> Permissions, you will see that only the requested scope was granted:
This way, you can only request the scopes you need and also include multiple scopes by separating them with a space
If you are like me and would like to automate the consent process as well, that is possible by using the Azure CLI:
00000003-0000-0000-c000-000000000000 is the Application ID of the Microsoft Graph resource in AAD. This will be the same for all tenants.
Hope you found the post useful!
As I was calling the Office CLI from an automated script, I could not use the default deviceCode flow to login as there was no user interaction possible. So the other options were to use the username/password flow or use a custom certificate. I chose the former as it was very convenient and I could store the password securely in a Azure DevOps secret variable.
Now before using the Office CLI on a tenant, we need to consent to the AAD multi-tenant app used internally for authenticating to Office 356 services. This app is called "PnP Office 365 Management Shell" and the consent process is also described in the docs here: https://pnp.github.io/office365-cli/user-guide/connecting-office-365/
If this consent hasn't happened yet, we get the following error:
Error: AADSTS65001: The user or administrator has not consented to use the application with ID '31359c7f-bd7e-475c-86db-fdb8c937548e' named 'PnP Office 365 Management Shell'. Send an interactive authorization request for this user and resource.
If we go ahead with the default consent experience, we are presented with this screen where the CLI AAD app requests a large number of permissions on the tenant:
Now in my case, I did not need all the scopes the CLI was requesting. I only needed AppCatalog.ReadWrite.All to deploy my Teams app.
So that got me thinking, one of the benefits of using the Microsoft identity platform (also called AAD v2) is that apps can request consent to specific scopes:
With the Microsoft identity platform endpoint, you can ignore the static permissions defined in the app registration information in the Azure portal and request permissions incrementally instead, which means asking for a bare minimum set of permissions upfront and growing more over time as the customer uses additional app features. To do so, you can specify the scopes your app needs at any time by including the new scopes in the scope parameter when requesting an access token - without the need to pre-define them in the application registration information.
https://docs.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison#incremental-and-dynamic-consent
So armed with this knowledge, I tested whether I could request (and grant) only AppCatalog.ReadWrite.All to the PnP Office 365 Management Shell app.
Turns out it is indeed possible by going to this URL in a browser as an admin:
You will notice that in the client_id param, we are specifying the client id of the PnP Office 365 Management Shell app and in the scope param, we are only requesting the needed scope i.e. AppCatalog.ReadWrite.All
This time, we are presented with a significantly reduced scopes for consent. Notice only the "Read and write to all app catalogs" permission is present along with the couple of default permissions.
After granting the consent and then navigating to Azure AD -> Enterprise Applications -> All applications -> PnP Office 365 Management Shell -> Permissions, you will see that only the requested scope was granted:
This way, you can only request the scopes you need and also include multiple scopes by separating them with a space
Automating the consent:
If you are like me and would like to automate the consent process as well, that is possible by using the Azure CLI:
00000003-0000-0000-c000-000000000000 is the Application ID of the Microsoft Graph resource in AAD. This will be the same for all tenants.
Hope you found the post useful!
Monday, 24 February 2020
Microsoft Bot Framework v4: Send proactive messages to Teams channels and users
What is a Bot Framework Proactive message?
Usually, for starting a conversation with a Microsoft Teams bot, the user has to initiate the conversation either by sending a personal message to the bot, or by mentioning the bot in a Teams channel or by invoking a messaging extension.
With proactive messaging, the bot can start a conversation with a user (or in a Teams channel) without anyone having to invoke the bot first. This conversation can be started based on any custom logic fit for your application e.g. The occurrence of an external event, or a webhook getting triggered or even on a scheduled periodical basis.
More about Bot Framework proactive messages here: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp
I should mention that the bot will first need to be installed in the Team, if you want to send a proactive message to a Teams channel, or to the users who are part of that team.
How to send proactive messages?
So in this post, let's look at a few code samples which make it very easy for our Teams Bot to send proactive messages to users or channels in a Team.
These code samples are based on a standalone .NET Core console app. This is mainly to show that as long as you have the necessary information, your code doesn't need to be running under the Bot messaging endpoint. Once you have the information from the Bot messaging endpoint, the proactive messaging code can run from any platform e.g. from an Azure Function.
If you have a look at the Bot Framework code samples published by Microsoft, they all use code which is running under the messaging endpoint. This initially led me to believe that even for proactive messaging, the code should live under the same endpoint. But as we will see in this post, that is not the case.
What are the prerequisites?
As mentioned before, our bot will need to be installed in a Team first. This will allow the bot messaging endpoint to receive the required values from Teams and send it to our proactive messaging code. If the bot is not installed in the Team, you will get a "Forbidden: The bot is not part of the conversation roster" error.
Base URL (serviceUrl)
This is the Url to which our proactive messaging code should send all the requests. This Url is sent by Teams in the Bot payload in the turnContext.Activity.serviceUrl property. For all intents and purposes this url will remain constant but after having a discussion with Microsoft, they have recommended that this url might change (very rarely) and our bot should have the logic for updating the stored base url periodically from the payload sent to the bot. More about the Base Url here: https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#base-uri
Internal Team Id
This is the internal team id which is not the same as the Office 365 Group Id. The internal team id is in the following format: 19:bf2b184e7cbb4f9f9ca1b47f755cd943@thread.skype
You can get the internal team id from the Bot payload in the channelData.team.id property. You can also get this id through the Microsoft Graph API: https://docs.microsoft.com/en-us/graph/api/resources/team?view=graph-rest-1.0#properties
Channel Id
If we want our bot to post to a specific channel in a Team, then we will need the channel id as well. The format for the channel id is exactly the same as the internal team id. Also, you can get the channel id from the bot payload as well as the Microsoft Graph api: https://docs.microsoft.com/en-us/graph/api/resources/channel?view=graph-rest-1.0#properties
Internal Teams User Id
This would only be needed if you want to send a proactive personal message to a specific user. For all users in a team, Teams maintains an encoded user id so that only bots installed in a team are able to message users. To get this user id, our bot needs to call the conversations/{conversationId}/members REST API endpoint. Fortunately for us the Bot Framework wraps this call in a handy SDK method as shown in the third code sample below.
So once we have all the required values from the Bot messaging endpoint, we are able to send proactive messages. For this sample code, I am using the Microsoft.Bot.Builder v4.7.2
https://www.nuget.org/packages/Microsoft.Bot.Builder/
Usually, for starting a conversation with a Microsoft Teams bot, the user has to initiate the conversation either by sending a personal message to the bot, or by mentioning the bot in a Teams channel or by invoking a messaging extension.
With proactive messaging, the bot can start a conversation with a user (or in a Teams channel) without anyone having to invoke the bot first. This conversation can be started based on any custom logic fit for your application e.g. The occurrence of an external event, or a webhook getting triggered or even on a scheduled periodical basis.
More about Bot Framework proactive messages here: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp
I should mention that the bot will first need to be installed in the Team, if you want to send a proactive message to a Teams channel, or to the users who are part of that team.
How to send proactive messages?
So in this post, let's look at a few code samples which make it very easy for our Teams Bot to send proactive messages to users or channels in a Team.
These code samples are based on a standalone .NET Core console app. This is mainly to show that as long as you have the necessary information, your code doesn't need to be running under the Bot messaging endpoint. Once you have the information from the Bot messaging endpoint, the proactive messaging code can run from any platform e.g. from an Azure Function.
If you have a look at the Bot Framework code samples published by Microsoft, they all use code which is running under the messaging endpoint. This initially led me to believe that even for proactive messaging, the code should live under the same endpoint. But as we will see in this post, that is not the case.
What are the prerequisites?
As mentioned before, our bot will need to be installed in a Team first. This will allow the bot messaging endpoint to receive the required values from Teams and send it to our proactive messaging code. If the bot is not installed in the Team, you will get a "Forbidden: The bot is not part of the conversation roster" error.
Base URL (serviceUrl)
This is the Url to which our proactive messaging code should send all the requests. This Url is sent by Teams in the Bot payload in the turnContext.Activity.serviceUrl property. For all intents and purposes this url will remain constant but after having a discussion with Microsoft, they have recommended that this url might change (very rarely) and our bot should have the logic for updating the stored base url periodically from the payload sent to the bot. More about the Base Url here: https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#base-uri
Internal Team Id
This is the internal team id which is not the same as the Office 365 Group Id. The internal team id is in the following format: 19:bf2b184e7cbb4f9f9ca1b47f755cd943@thread.skype
You can get the internal team id from the Bot payload in the channelData.team.id property. You can also get this id through the Microsoft Graph API: https://docs.microsoft.com/en-us/graph/api/resources/team?view=graph-rest-1.0#properties
Channel Id
If we want our bot to post to a specific channel in a Team, then we will need the channel id as well. The format for the channel id is exactly the same as the internal team id. Also, you can get the channel id from the bot payload as well as the Microsoft Graph api: https://docs.microsoft.com/en-us/graph/api/resources/channel?view=graph-rest-1.0#properties
Internal Teams User Id
This would only be needed if you want to send a proactive personal message to a specific user. For all users in a team, Teams maintains an encoded user id so that only bots installed in a team are able to message users. To get this user id, our bot needs to call the conversations/{conversationId}/members REST API endpoint. Fortunately for us the Bot Framework wraps this call in a handy SDK method as shown in the third code sample below.
So once we have all the required values from the Bot messaging endpoint, we are able to send proactive messages. For this sample code, I am using the Microsoft.Bot.Builder v4.7.2
https://www.nuget.org/packages/Microsoft.Bot.Builder/
1) Post a proactive message in a Teams channel
(click to zoom)
2) Post a proactive message in a Teams channel and mention a user in it
(click to zoom)
3) Post a proactive personal message to a user
This code has been updated for Bot Framework v4.12.2 NuGet Gallery | Microsoft.Bot.Builder 4.12.2
Also, before running this code, make sure that the user has installed the bot app in the personal scope or is a member of a Team which has the bot installed.
(click to zoom)
Hope you found the post useful!
Wednesday, 5 February 2020
SPFx: Using React hooks to globally share service scope between components
In my previous posts, I have written quite a few times about SharePoint Framework service scopes (I will add links at the end of the article). In short, Service Scopes are the SPFx implementation of the Service Locator pattern i.e. a single shared "dictionary" where services (either oob SPFx or custom) are registered and can be consumed from any component in the application.
For example, without using service scopes, if we wanted to make a call to the Microsoft Graph (using MSGraphClient) from within a deeply nested react component, either we would have to pass in the SPFx context down all the components in the tree, or maybe a create a custom service which returns the web part context, and then call that service from within our nested component. Or maybe use redux to globally maintain the context in a single state object.
But with all these approaches (there may be more), testing the components would be difficult as they would have a dependency on the SPFx context which is hard to mock. Waldek Mastykarz has a great post on this.
Also, from a maintenance point of view, it could get tricky as almost all our components would start to depend on the entire context and we could easily loose track of which specific service from the context is needed by the component.
Now with my previous posts on service scopes, even though we were removing the dependency on the SPFx context, one issue still remained that the SPFx service scope was still needed to be passed into the component. We were just replacing the SPFx context with the SPFx service scopes. While this was good from a testing point of view, it wasn't great for maintainability.
Fortunately, in the recent versions of SPFx, React 16.8+ was supported which means that we can take advantage of React hooks. Specifically, the useContext hook. This gives us a very straightforward way to store the SPFx service scope in the global react context (which is different to the SPFx context) and then consume it from any component in our application no matter how deeply nested it is.
Let's see how to achieve this. In these code samples, I am using SPFx v1.10 which is the latest version at the time of writing.
First, we need to create the React application context object which will be used to store and consume the service scope. For now I am only storing the serviceScope in the context. Other values can be stored here as well.
React hooks can only be used from functional components and not classes. With the SPFx generator creating classes by default and hooks being fairly new, I am sure there is a lot of code out there already which use classes and not functional components. Changing all code to use functional components instead of classes is a non-starter.
Fortunately, there is a way to use react hooks with classes by creating a Higher Order Component (HOC) which is a functional component. We can wrap all our class components with this HOC and safely consume the useContext hook from within this component.
(Update: If you are interested in going down the "full hooks" approach and doing away entirely with classes, Garry Trinder has got you covered. He has created a fork which only uses functional components and hooks so we don't need the HOC. If you want to take this approach, check out the code here: https://github.com/garrytrinder/spfx-servicescopes-hooks)
Next, we update our SPFx webpart to only pass in the serviceScope once to our top level component:
Our top level component will need to be wrapped with the AppContext so that any nested component will be able to consume it. This just needs to be done once on the top level react component. You will notice that the HelloUser child component does not need any props passed in.
Due to the Higher Order component and the useContext hook, we are able to access the serviceScope property from withing the child component. We can grab the MSGraphClient from the serviceScope and start making calls to the Graph:
And that's it! This way, we can use the React useContext hook to globally share our SPFx service scope.
Hopefully you have found this post helpful! All code is added in the GitHub repo here: https://github.com/vman/spfx-servicescopes-hooks
Also, if you are interested, here are all my previous articles on SPFx service scopes:
SharePoint Framework: Org Chart web part using Office UI Fabric, React, OData batching and Service scopes
Getting the current context (SPHttpClient, PageContext) in a SharePoint Framework Service
Service Locator pattern in SPFx: Using Service Scopes
Service Locator pattern in SPFx: Using nested scopes to work with multiple components
For example, without using service scopes, if we wanted to make a call to the Microsoft Graph (using MSGraphClient) from within a deeply nested react component, either we would have to pass in the SPFx context down all the components in the tree, or maybe a create a custom service which returns the web part context, and then call that service from within our nested component. Or maybe use redux to globally maintain the context in a single state object.
But with all these approaches (there may be more), testing the components would be difficult as they would have a dependency on the SPFx context which is hard to mock. Waldek Mastykarz has a great post on this.
Also, from a maintenance point of view, it could get tricky as almost all our components would start to depend on the entire context and we could easily loose track of which specific service from the context is needed by the component.
Now with my previous posts on service scopes, even though we were removing the dependency on the SPFx context, one issue still remained that the SPFx service scope was still needed to be passed into the component. We were just replacing the SPFx context with the SPFx service scopes. While this was good from a testing point of view, it wasn't great for maintainability.
Fortunately, in the recent versions of SPFx, React 16.8+ was supported which means that we can take advantage of React hooks. Specifically, the useContext hook. This gives us a very straightforward way to store the SPFx service scope in the global react context (which is different to the SPFx context) and then consume it from any component in our application no matter how deeply nested it is.
Let's see how to achieve this. In these code samples, I am using SPFx v1.10 which is the latest version at the time of writing.
1) The Application Context object
First, we need to create the React application context object which will be used to store and consume the service scope. For now I am only storing the serviceScope in the context. Other values can be stored here as well.
2) React Higher Order Component (HOC)
React hooks can only be used from functional components and not classes. With the SPFx generator creating classes by default and hooks being fairly new, I am sure there is a lot of code out there already which use classes and not functional components. Changing all code to use functional components instead of classes is a non-starter.
Fortunately, there is a way to use react hooks with classes by creating a Higher Order Component (HOC) which is a functional component. We can wrap all our class components with this HOC and safely consume the useContext hook from within this component.
(Update: If you are interested in going down the "full hooks" approach and doing away entirely with classes, Garry Trinder has got you covered. He has created a fork which only uses functional components and hooks so we don't need the HOC. If you want to take this approach, check out the code here: https://github.com/garrytrinder/spfx-servicescopes-hooks)
3) SPFx web part
Next, we update our SPFx webpart to only pass in the serviceScope once to our top level component:
4) Top level React component
Our top level component will need to be wrapped with the AppContext so that any nested component will be able to consume it. This just needs to be done once on the top level react component. You will notice that the HelloUser child component does not need any props passed in.
5) Child component
Due to the Higher Order component and the useContext hook, we are able to access the serviceScope property from withing the child component. We can grab the MSGraphClient from the serviceScope and start making calls to the Graph:
And that's it! This way, we can use the React useContext hook to globally share our SPFx service scope.
Hopefully you have found this post helpful! All code is added in the GitHub repo here: https://github.com/vman/spfx-servicescopes-hooks
Also, if you are interested, here are all my previous articles on SPFx service scopes:
SharePoint Framework: Org Chart web part using Office UI Fabric, React, OData batching and Service scopes
Getting the current context (SPHttpClient, PageContext) in a SharePoint Framework Service
Service Locator pattern in SPFx: Using Service Scopes
Service Locator pattern in SPFx: Using nested scopes to work with multiple components
Wednesday, 15 January 2020
Create Microsoft Teams manifest manually for Personal app powered by SPFx
SPFx 1.10 was released recently which now includes support for Teams personal apps. Catch the announcement here: https://developer.microsoft.com/en-us/sharepoint/blogs/announcing-sharepoint-framework-1-10-extending-sharepoint-framework-across-microsoft-365/
To deploy a personal app with an SPFx package, you have the option of deploying the package to the SharePoint tenant app catalog and clicking on the "Sync to Teams" button which then makes the app available in Teams as shown here: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/integrate-with-teams-introduction
But what if you are coming at it from the Teams app point of view? You already have a Teams app with a bot or a messaging extension and want to add the SPFx powered personal app to it. You probably don't want to use the "Sync to Teams" option in this case because then your SPFx web-part will be available as a separate app in the Teams app catalog.
Fortunately, it's very simple to define a staticTab in the Teams manifest which points to the SPFx webpart. This then makes the SPFx webpart available as a teams personal app:
Notice the teams and personal query string parameters as they are very important. You will also have to replace the component id with the id of your web-part.
I have also updated the official MS Docs with this approach:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/creating-team-manifest-manually-for-webpart
Thanks to my colleague Jarbas for working with me in figuring out this one!
To deploy a personal app with an SPFx package, you have the option of deploying the package to the SharePoint tenant app catalog and clicking on the "Sync to Teams" button which then makes the app available in Teams as shown here: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/integrate-with-teams-introduction
But what if you are coming at it from the Teams app point of view? You already have a Teams app with a bot or a messaging extension and want to add the SPFx powered personal app to it. You probably don't want to use the "Sync to Teams" option in this case because then your SPFx web-part will be available as a separate app in the Teams app catalog.
Fortunately, it's very simple to define a staticTab in the Teams manifest which points to the SPFx webpart. This then makes the SPFx webpart available as a teams personal app:
Notice the teams and personal query string parameters as they are very important. You will also have to replace the component id with the id of your web-part.
I have also updated the official MS Docs with this approach:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/creating-team-manifest-manually-for-webpart
Thanks to my colleague Jarbas for working with me in figuring out this one!
Monday, 2 December 2019
SharePoint Framework Web Part and Property Pane Lifecycles
I was working on a SharePoint Framework webpart, especially on the property pane, and needed to understand when the SPFx WebPart lifecycle methods are executed. Specifically, the order in which they are fired.
I tried searching for this but couldn't find much information, so thought of creating this post as it might be helpful for other who might be looking for it.
The Microsoft docs on these methods/APIs are fairly extensive and they give a good overview of what each method does:
https://docs.microsoft.com/en-us/javascript/api/sp-webpart-base/baseclientsidewebpart?view=sp-typescript-latest#methods
https://docs.microsoft.com/en-us/javascript/api/sp-webpart-base/basewebpart?view=sp-typescript-latest
What is missing though is the order in which these methods are fired. So this post should serve as a nice complement to the docs.
I should mention that this post only focuses on the native SPFx lifecycle methods. If you are creating an SPFx webpart with React for example, the react lifecycle is out of scope for this post as there is loads of information available already on the interwebs.
1) protected onAfterDeserialize(deserializedObject: any, dataVersion: Version): TProperties;
2) protected onInit(): Promise<void>;
3) protected render(): void;
4) protected onBeforeSerialize(): void;
1) protected onDispose(): void;
Opening the property pane:
1) protected loadPropertyPaneResources(): Promise<void>
2) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
3) protected onPropertyPaneRendered(): void;
4) protected onPropertyPaneConfigurationStart(): void;
1) protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void;
2) protected render(): void;
3) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
4) protected onPropertyPaneRendered(): void;
5) protected onPropertyPaneConfigurationComplete(): void;
1) protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void;
2) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
3) protected onPropertyPaneRendered(): void;
When in non-reactive mode, after clicking on the "Apply" button, the methods are fired in the following order:
1) protected onAfterPropertyPaneChangesApplied(): void;
2) protected render(): void;
3) protected onPropertyPaneConfigurationComplete(): void;
4) protected onPropertyPaneRendered(): void;
When the property pane is closed by clicking on the "X" button
1) protected onPropertyPaneConfigurationComplete(): void;
Hope you found the post helpful! Let me know if I might have missed any. Would love to update this post in the future.
I tried searching for this but couldn't find much information, so thought of creating this post as it might be helpful for other who might be looking for it.
The Microsoft docs on these methods/APIs are fairly extensive and they give a good overview of what each method does:
https://docs.microsoft.com/en-us/javascript/api/sp-webpart-base/baseclientsidewebpart?view=sp-typescript-latest#methods
https://docs.microsoft.com/en-us/javascript/api/sp-webpart-base/basewebpart?view=sp-typescript-latest
What is missing though is the order in which these methods are fired. So this post should serve as a nice complement to the docs.
I should mention that this post only focuses on the native SPFx lifecycle methods. If you are creating an SPFx webpart with React for example, the react lifecycle is out of scope for this post as there is loads of information available already on the interwebs.
SPFx webpart method execution order:
When loading the web part on a page, the methods are fired in the following order:
1) protected onAfterDeserialize(deserializedObject: any, dataVersion: Version): TProperties;
2) protected onInit(): Promise<void>;
3) protected render(): void;
4) protected onBeforeSerialize(): void;
When the web part is removed from a page, the methods are fired in the following order:
1) protected onDispose(): void;
SPFx webpart property pane method execution order:
Opening the property pane:
1) protected loadPropertyPaneResources(): Promise<void>
2) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
3) protected onPropertyPaneRendered(): void;
4) protected onPropertyPaneConfigurationStart(): void;
Updating the properties in the property pane:
The SPFx property pane can be set in either the reactive mode or in a non-reactive mode:
"Reactive implies that changes made in the PropertyPane are transmitted to the web part instantly and the user can see instant updates. This helps the page creator get instant feedback and decide if they should keep the new configuration changes or not.
NonReactive implies that the configuration changes are transmitted to the web part only after "Apply" PropertyPane button is clicked."
When in reactive mode, if any property is changed, the methods are fired in the following order:
1) protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void;
2) protected render(): void;
3) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
4) protected onPropertyPaneRendered(): void;
5) protected onPropertyPaneConfigurationComplete(): void;
When in non-reactive mode, if any property is changed, the methods are fired in the following order:
1) protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void;
2) protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
3) protected onPropertyPaneRendered(): void;
When in non-reactive mode, after clicking on the "Apply" button, the methods are fired in the following order:
1) protected onAfterPropertyPaneChangesApplied(): void;
2) protected render(): void;
3) protected onPropertyPaneConfigurationComplete(): void;
4) protected onPropertyPaneRendered(): void;
When the property pane is closed by clicking on the "X" button
1) protected onPropertyPaneConfigurationComplete(): void;
Hope you found the post helpful! Let me know if I might have missed any. Would love to update this post in the future.
Thursday, 7 November 2019
Building a Microsoft Teams Bot: Get Team context details including Office 365 Group and SharePoint site url
The Microsoft Bot Framework v4.6 was released this week at Ignite and it's got some really great additions:
https://github.com/microsoft/botframework/blob/master/whats-new.md#november-2019-ignite
One thing which I am very happy about is that building Bots for Microsoft Teams has become way easier now!
But now, the Teams Bot Builder is part of the the Bot Framework SDK itself which is very good news. If you are just getting started building Bots for Microsoft Teams, you only need to install the Bot Framework package. Apart from that, the code to work with Microsoft Teams has been simplified as well.
Imagine you are building a Teams Bot and it needs to interact with the Office 365 Group which underpins the Team. Or maybe the Bot needs to store or retrieve some data from the SharePoint site associated with the Team. Let's see how easy it is now to achieve this:
Before we look at the code, make sure you are using the Microsoft.Bot.Builder 4.6+ version of packages in your Bot:
https://github.com/Microsoft/botbuilder-dotnet/#packages
https://www.nuget.org/packages/Microsoft.Bot.Builder/
We will also need the Microsoft Graph .NET SDK:
https://docs.microsoft.com/en-us/graph/sdks/sdk-installation
And here is the code:
We are using the new TeamsInfo class available in the Bot Framework to get the current team details. This class also has a few other helper methods which you might find useful:
https://github.com/microsoft/botbuilder-dotnet/blob/master/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
Internally the TeamsInfo.GetTeamDetailsAsync method calls the `/v3/teams/{team-id}` API endpoint to get the Office 365 Group id (a.k.a AADGroupId in the API). We can then use the Microsoft Graph API to get the other details including the SharePoint site url.
Hope this helps! For more details on building bots for Microsoft Teams, have a look at the official docs: https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams
https://github.com/microsoft/botframework/blob/master/whats-new.md#november-2019-ignite
One thing which I am very happy about is that building Bots for Microsoft Teams has become way easier now!
This wasn't always the case. Previously, the Bot Framework was separate to the Teams Bot Builder and they both didn't play nice all the time. Throw in more stuff like Bot Framework v3/v4, Messaging extensions, Adaptive Cards and it lead to tweets like this:
Interesting couple of weeks with Bot Framework v4, Teams Bot Builder, Messaging Extensions and user delegated auth! Not everything wants to play nice with each other but finally getting somewhere!— Vardhaman Deshpande (@vrdmn) August 30, 2019
But now, the Teams Bot Builder is part of the the Bot Framework SDK itself which is very good news. If you are just getting started building Bots for Microsoft Teams, you only need to install the Bot Framework package. Apart from that, the code to work with Microsoft Teams has been simplified as well.
Imagine you are building a Teams Bot and it needs to interact with the Office 365 Group which underpins the Team. Or maybe the Bot needs to store or retrieve some data from the SharePoint site associated with the Team. Let's see how easy it is now to achieve this:
Before we look at the code, make sure you are using the Microsoft.Bot.Builder 4.6+ version of packages in your Bot:
https://github.com/Microsoft/botbuilder-dotnet/#packages
https://www.nuget.org/packages/Microsoft.Bot.Builder/
We will also need the Microsoft Graph .NET SDK:
https://docs.microsoft.com/en-us/graph/sdks/sdk-installation
And here is the code:
We are using the new TeamsInfo class available in the Bot Framework to get the current team details. This class also has a few other helper methods which you might find useful:
https://github.com/microsoft/botbuilder-dotnet/blob/master/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
Internally the TeamsInfo.GetTeamDetailsAsync method calls the `/v3/teams/{team-id}` API endpoint to get the Office 365 Group id (a.k.a AADGroupId in the API). We can then use the Microsoft Graph API to get the other details including the SharePoint site url.
Hope this helps! For more details on building bots for Microsoft Teams, have a look at the official docs: https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams
Subscribe to:
Posts (Atom)



