tag:blogger.com,1999:blog-44550588941100836582024-03-17T13:34:16.601+00:00Vardhaman DeshpandeFocusworks AI for Microsoft Teams - Chat with your business data <a href="https://focusworks.ai/">https://focusworks.ai/</a>
<br>
<br>
Fresh - Bring your SharePoint intranet to life <a href="https://freshintranet.com/">https://freshintranet.com/</a>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.comBlogger165125tag:blogger.com,1999:blog-4455058894110083658.post-53234346049888058132024-03-10T14:55:00.005+00:002024-03-17T13:33:45.186+00:00Create a Microsoft 365 Copilot plugin: Extend Microsoft 365 Copilot's knowledge<p>
Microsoft 365 Copilot is an enterprise AI tool that is already trained on your
Microsoft 365 data. If you want to "talk" to data such as your emails, Teams
chats or SharePoint documents, then all of it is already available as part of
it's "knowledge".
</p>
<p>
However, not all the data you want to work with will live in Microsoft 365.
There will be instances when you want to use Copilot's AI on data residing in
external systems. So how do we extend the knowledge of Microsoft 365 Copilot
with real time data coming from external systems?<span style="background-color: white;"> The answer is by using plugins! <b><span style="color: red;">Plugins not only help us do Retrieval Augmented Generation (RAG) with Copilot, but they also provide a framework for writing data to external systems.</span></b> </span></p>
<p>
To know more about the different Microsoft 365 Copilot extensibility options,
please have a look here: <a href="https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/decision-guide" target="_blank"><span style="color: #2b00fe;">https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/decision-guide</span></a>
</p>
<p>
So in this post, let's have a look at how to build a plugin which talks to an
external API and then infuses the real time knowledge into Copilot's AI. At
the time of this writing, there is nothing more volatile than Cryptocurrency
prices! So, I will be using a cryptocurrency price API and enhance Microsoft
365 Copilot's knowledge with real time Bitcoin and Ethereum rates!
</p>
<p style="text-align: center;">(click to zoom)</p>
<p style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8IC4j7n1JV5WTid7YBpO_9FTB4MZodkIRk6p3jlnCKocNZRnPyvkVr8kcHUBVLWBjaZT4qSX8onBOflOcH-UsZkkEPyzj6FR2sSchuK0O-rUMpXzHz-_PfbJvasDomeA3Pvy3FTA4sopL7XYorx6HWgxTZUfVCP3XLxfJUDpaAf4kcZF6RhvvNS6znJw/s1262/all.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1262" data-original-width="1080" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8IC4j7n1JV5WTid7YBpO_9FTB4MZodkIRk6p3jlnCKocNZRnPyvkVr8kcHUBVLWBjaZT4qSX8onBOflOcH-UsZkkEPyzj6FR2sSchuK0O-rUMpXzHz-_PfbJvasDomeA3Pvy3FTA4sopL7XYorx6HWgxTZUfVCP3XLxfJUDpaAf4kcZF6RhvvNS6znJw/w548-h640/all.png" width="548" /></a>
</p>
<p>
So let's see the different moving parts of the plugin. We will be using a
Microsoft Teams message extension built on the Bot Framework as a base for our
plugin:
</p>
<h3 style="text-align: left;">
<span style="color: #274e13;">1) App manifest</span>
</h3>
<p>
This is by far the most important part of the plugin. The name and description
(both short and long) are what tell Copilot about the nature of the plugin and
when to invoke it to get external data. We have to be very descriptive and
clear about the features of the plugin here as this is what the Copilot will
use to determine whether the plugin is invoked. The parameter descriptions are
used to tell Copilot how to create the parameters required by the plugin based
on the conversation.
</p>
<script src="https://gist.github.com/vman/194ea60798d299e76804aa02bfa7174a.js"></script>
<h3 style="text-align: left;">
<span style="color: #274e13;">2) Teams messaging extension code</span>
</h3>
<p>
This function does the heavy lifting in our code. It is called with the
parameters specified in the app manifest by Copilot. Based on the parameters
we can fetch external data and return it as adaptive cards.
</p>
<script src="https://gist.github.com/vman/1cddaf3b0cfe52b93d363b48465dd038.js"></script>
<h3 style="text-align: left;">
<span style="color: #274e13;">3) Talk to the external system (Cryptocurrency API)</span>
</h3>
<p>
This is helper function which is used to actually talk to the crypto api and
return rates.
</p>
<script src="https://gist.github.com/vman/9b5e97132e023c582e9f056f4c77d0bc.js"></script>
<p>
Hope you found this post useful! </p><p>The code for this solution is available on GitHub: <a href="https://github.com/vman/M365CopilotPlugin" target="_blank"><span style="color: #2b00fe;">https://github.com/vman/M365CopilotPlugin</span></a></p>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-11190505037367889722024-02-15T04:39:00.000+00:002024-02-15T04:39:58.228+00:00Generate images using Azure OpenAI DALL·E 3 in SPFx<p>
Dall E 3 is the latest AI image generation model coming out of OpenAI. It is
leaps and bounds ahead of the previous model Dall E 2. Having explored both,
the image quality as well as the adherence to text prompts is much better for
Dall E 3. It is now available as a preview in Azure OpenAI Service as well.
</p>
<p>
Given all this, it is safe to say if you are working on the Microsoft stack
and want to generate images with AI, using the Azure OpenAI Dall E 3 model
would be the recommended option.
</p>
<p>
In this post, let's explore the image generation API for Dall E 3 and also how
to use it from a SharePoint Framework (SPFx) solution. The full code of the
solution is available on GitHub: <a
href="https://github.com/vman/Augmentech.OpenAI"
><span style="color: #2b00fe;"
>https://github.com/vman/Augmentech.OpenAI</span
></a
>
</p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXN2tvMbfCJNmKxLmFdG1pSy0x471_FT8ZT-hEnvvqynnsVxLyqQt0v2TqPz2bGlhccMSDAr84zebjVPJYHL70sXdHVuLCwDm2_dhPwYnSzhvPW6DeJaaQMTS-43IU0B7gL66rbP5MKUb5m1LzmhpVmA36fqOB98oI8hw34EusztqS5-VplAHl7z6VMyo/s1098/image.gif"
imageanchor="1"
style="margin-left: 1em; margin-right: 1em;"
><img
border="0"
data-original-height="931"
data-original-width="1098"
height="542"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXN2tvMbfCJNmKxLmFdG1pSy0x471_FT8ZT-hEnvvqynnsVxLyqQt0v2TqPz2bGlhccMSDAr84zebjVPJYHL70sXdHVuLCwDm2_dhPwYnSzhvPW6DeJaaQMTS-43IU0B7gL66rbP5MKUb5m1LzmhpVmA36fqOB98oI8hw34EusztqS5-VplAHl7z6VMyo/w640-h542/image.gif"
width="640"
/></a>
</div>
<p>
First, let's build the web api which will wrap the Azure OpenAI API to create
images. This will be a simple ASP.NET Core Web API which will accept a text
prompt and return the generated image to the client.
</p>
<p>
To run this code, we will need the following NuGet package:
<a href="https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.13/"
><span style="color: #2b00fe;"
>https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.13/</span
></a
>
</p>
<script src="https://gist.github.com/vman/ff72d5cc235860c3f4e384ccc53547bd.js"></script>
<p>
Now for calling the API, we will use a standard React based SPFx webpart. The
webpart will use Fluent UI controls to grab the text prompt from user and send
it to our API.
</p>
<script src="https://gist.github.com/vman/214411e5d8157e1b6ea4d7554e7ee035.js"></script>
Hope this helps!
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-76552084420055472492024-01-25T12:08:00.002+00:002024-01-26T14:48:55.135+00:00Get structured JSON data back from GPT-4-Turbo<p>
With the latest gpt-4-turbo model out recently, there is one very helpful
feature which came with it: The JSON mode option. Using JSON mode, we are able
to predictably get responses back from OpenAI in structured JSON format.
</p>
<p>
This can help immensely when building APIs using Large Language Models (LLMs). Even though the model can be instructed to return JSON in it's system prompt, previously, there
was no guarantee that the model would return valid JSON. With the JSON mode option now, we can specify the required format and the
model will return data according to it. </p><p>To know more about JSON mode, have a look at the official OpenAI docs: <a href="https://platform.openai.com/docs/guides/text-generation/json-mode" rel="nofollow" target="_blank">https://platform.openai.com/docs/guides/text-generation/json-mode</a></p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3WAbfDjv2I2sgqxl3faosBuMEghKCfAZXJu8Hp3p6jPdIuy0E1fCAC2f2n7FcXWtt1czuBOqyI5nY08ggnAZVpfNoUrexAt7gdyOdRzVdnVSfRwxIi3k3mQd3w2w07twIzJpxcmd4xQBwW5fjTI2LcGx3ShEE_oThv7cQiXPivOA5-FMdeVfmokeA1DE/s1024/OIG%20(1).jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1024" data-original-width="1024" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3WAbfDjv2I2sgqxl3faosBuMEghKCfAZXJu8Hp3p6jPdIuy0E1fCAC2f2n7FcXWtt1czuBOqyI5nY08ggnAZVpfNoUrexAt7gdyOdRzVdnVSfRwxIi3k3mQd3w2w07twIzJpxcmd4xQBwW5fjTI2LcGx3ShEE_oThv7cQiXPivOA5-FMdeVfmokeA1DE/w400-h400/OIG%20(1).jpg" width="400" /></a></div><p>Now let's look at some code to see how this works in action:</p>
<p>I am using the Azure OpenAI service to host the gpt-4-turbo model and I am also using the <b>v1.0.0.-beta.12</b> version of the Azure OpenAI .NET SDK found on
NuGet here:
</p>
<p>
<a href="https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.12" target="_blank">https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.12</a>
</p>
<p>
<script src="https://gist.github.com/vman/aced6d66a6b8cda790348752044ba3c0.js"></script>
</p>
What is happening in the code is that in the system message, we are instructing the LLM that analyse the text provided by the user and then extract the cities mentioned in this text and return them in the specified JSON format. <div><br /><div>Also important is line 22 where we explicitly specify to use the response format as JSON. <br /><br />Next, we provide the actually text to parse in the user message. <div><br /></div><div>Once we get the data back in expected JSON schema, we are able to convert it to objects which can be used in code.<br /><div><br /></div><div>And as expected we get the following output:<div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjLRSwdrvjH1Fp4tf8ZgBJUsH_59Mkefd_LoPG_VuiuuTnA7BSz7lAqzElsIPKKXKHHdgjlf-9xpqhsJc7OCPgTQ_-isy-CI-x_Lfewj0s6-ZWPie8FS9W5gLpEsYUOtEQNCJ35ip4LZjKXsHYJY0-dtRaqeQLGx5jw1RkxJ5FpkfNjupmnRJMh6gprcpA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="87" data-original-width="1112" height="50" src="https://blogger.googleusercontent.com/img/a/AVvXsEjLRSwdrvjH1Fp4tf8ZgBJUsH_59Mkefd_LoPG_VuiuuTnA7BSz7lAqzElsIPKKXKHHdgjlf-9xpqhsJc7OCPgTQ_-isy-CI-x_Lfewj0s6-ZWPie8FS9W5gLpEsYUOtEQNCJ35ip4LZjKXsHYJY0-dtRaqeQLGx5jw1RkxJ5FpkfNjupmnRJMh6gprcpA=w640-h50" width="640" /></a></div><br /></div><div>Hope this helps!</div></div></div></div></div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-841119303365611252023-12-18T09:24:00.002+00:002023-12-18T09:28:32.136+00:00Using Microsoft Tokenizer to count Azure OpenAI model tokens<p>
If you have been working with OpenAI APIs, you will have come across the term
"tokens". Tokens are a way in which these APIs process and output text.
Various versions of the OpenAI APIs have different token context lengths. This
means there is a limit to the text they can process in a single request. More
about tokens here: <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/overview#tokens" target="_blank">https://learn.microsoft.com/en-us/azure/ai-services/openai/overview#tokens</a>
</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg5EyD3xjXSIjLfuWy2Z5S3v9XYyTshBAR3mCbI8fqBEPTX-xMu7eNozr63xo6ry61qSNh8LxMLezsg-i9fdYvF_q6sEnwv4OjQzteWiZWl-ilFMU-TmJmcH5EoY1IWYykTDxYPAau8-Q5gSznXAJ6OUCT8NVia5BsaTJxsmPu42ogSwe0SkxRIhPj8J0/s400/image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="178" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg5EyD3xjXSIjLfuWy2Z5S3v9XYyTshBAR3mCbI8fqBEPTX-xMu7eNozr63xo6ry61qSNh8LxMLezsg-i9fdYvF_q6sEnwv4OjQzteWiZWl-ilFMU-TmJmcH5EoY1IWYykTDxYPAau8-Q5gSznXAJ6OUCT8NVia5BsaTJxsmPu42ogSwe0SkxRIhPj8J0/s16000/image.png" /></a></div><p>
When building an app based on these APIs, we need to keep track of the tokens
being sent and make sure not to send more than the maximum context length of
the OpenAI model being used (e.g. gpt-3.5-turbo). If more tokens are sent than the maximum context length of the model, the request will fail with the
following error:</p>
<script src="https://gist.github.com/vman/0baa0b99e153bcdc511190dbfd411c25.js"></script>
<p>
To help with counting tokens before sending to the APIs, there are various
libraries available. One of them being the Microsoft Tokenizer: <a href="https://github.com/microsoft/Tokenizer" target="_blank">https://github.com/microsoft/Tokenizer</a>
which is an open source .NET and TypeScript implementation of OpenAI's
tiktoken library.
</p>
<p>
So in this post, let's see how we can use the Microsoft Tokenizer .NET SDK to
manage the tokens sent to OpenAI APIs.
</p>
<p>First we will need the Microsoft Tokenizer nuget package:</p>
<p>
<a href="https://www.nuget.org/packages/Microsoft.DeepDev.TokenizerLib/">https://www.nuget.org/packages/Microsoft.DeepDev.TokenizerLib/</a>
</p>
<p>
Since we will actually be counting the tokens of a chat between the user and
an AI assistant, we will also use the Azure OpenAI .NET SDK:
</p>
<p>
<span style="color: #2b00fe;"><a href="https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.8" target="_blank">https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.8</a></span>
</p>
<p>
Next, in our code we will first have to initialize the tokenizer and let it
know which OpenAI model will we be working with. Most of the recent models
like gpt-3.5-turbo, gpt-4 etc. share the same token encoding i.e.
cl100k_base. So we can use the same tokenizer across these models.
</p>
<p>Now let's look at the actual code:</p>
<script src="https://gist.github.com/vman/de7c1beecd7a7acdd3346761c97d9b7b.js"></script>
<p>What we have here is a sample chat history between a user and an assistant. Before sending the chat history to the OpenAI api to get the next message from the assistant, we are using the Tokenizer library to count the tokens, and if it comes out that there are more tokens present in the than the model supports, we are removing the earlier messages from the chat. This is so that the most recent conversations are sent to the API and the response generated stays relevant to the current conversation context. </p><p>Hope this helps!</p>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-42612184209044883152023-11-26T00:16:00.006+00:002023-11-27T09:47:40.739+00:00Manage Azure OpenAI Service using the Azure CLII was working on a project recently where we were using the Azure OpenAI service
quite heavily. As part of creating the DevOps pipelines for the project, we had
to look into automating the management of the Azure OpenAI service. Turns
out this functionality is possible with the Azure CLI however it is available under
the Cognitive Services module which can be a bit tricky to find. So here
is a quick blog post detailing some of the more frequently used operations for the Azure OpenAI service through
the Azure CLI:
<div>
<br />
<script src="https://gist.github.com/vman/5c7ac277bd4778a6c9baa7215bf17a9a.js"></script>
<div><br /></div>
</div>
For a full set of operations, please see the Microsoft docs: <span style="color: #2b00fe;"><a href="https://learn.microsoft.com/en-us/cli/azure/cognitiveservices?view=azure-cli-latest" target="_blank">https://learn.microsoft.com/en-us/cli/azure/cognitiveservices?view=azure-cli-latest</a></span>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-92076985215051488822023-11-16T09:52:00.004+00:002023-11-16T09:57:34.027+00:00Teams tab fails to load in the new Microsoft Teams Desktop client<p>
The new Microsoft Teams Desktop client was made generally available for
Windows and Mac recently. The good news is that the new client provides
feature parity for 3rd party apps like <a href="https://appsource.microsoft.com/en-us/product/office/wa200005115?tab=overview" target="_blank"><span style="color: #2b00fe;">Focusworks AI</span></a> giving customers a choice
of using their preferred Teams client to access the apps.
</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaFgy3CUQraQ5drTMfmkU7D8upCjjoAH41sRqeL1H3dnseYmXWk3zPUvm49TZDDLd0eQ_jI73vSCw6syVnZqfyqHhtoUaoBKjcmDuA7gsyi1twHgNoVnfWY7BsNluLtA3rfggNkj_id0JAZbrsf-LF0lyTVlCNRccQma2WsBoHJeMUgB-vPQg9TWvqizk/s278/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="278" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaFgy3CUQraQ5drTMfmkU7D8upCjjoAH41sRqeL1H3dnseYmXWk3zPUvm49TZDDLd0eQ_jI73vSCw6syVnZqfyqHhtoUaoBKjcmDuA7gsyi1twHgNoVnfWY7BsNluLtA3rfggNkj_id0JAZbrsf-LF0lyTVlCNRccQma2WsBoHJeMUgB-vPQg9TWvqizk/s1600/Untitled.png" width="278" /></a></div>
<p>
However, if you have a custom built Microsoft Teams tab or a task module as
part of your solution, and find that it fails to load in the new Microsoft
Teams client, there might be a specific reason for it.
</p>
<p>
And since there is no way to invoke the Developer tools in the new Teams
desktop client yet (November 2023), the experience can get a bit
frustrating.
</p>
<p>
In my case, I have a custom React/TypeScript based tab which is using
the <span style="color: red;"><b>@microsoft/teams-js</b></span> library to interact with Teams.
</p>
<p>
Since teams tabs are just HTML pages, we need to make sure that the page is
being loaded inside Teams before continuing to execute the code. To do that we
can use the <b><span style="color: red;">context.app.host.name</span></b> property and check that the value was
<b><span style="color: red;">"teams"</span></b> before moving ahead.
</p>
<script src="https://gist.github.com/vman/773d1a9d75e89dec4c0c0f2ee422a5bd.js"></script>
<p>
However, with the new desktop client my tab was failing to load. After a bit
of digging around I realised that the new Teams desktop client has an entirely
different host name property and the value is <b><span style="color: red;">"teamsModern"</span></b> as mentioned
here: <a href="https://learn.microsoft.com/en-us/javascript/api/%40microsoft/teams-js/hostname?view=msteams-client-js-latest" target="_blank"><span style="color: #2b00fe;">https://learn.microsoft.com/en-us/javascript/api/%40microsoft/teams-js/hostname?view=msteams-client-js-latest</span></a></p>
<p>So changing my code to include the new value as well worked!</p>
<script src="https://gist.github.com/vman/52aa1d0b9b2786e129a5e90dad99eb04.js"></script>
Hope this saves you some debugging time!
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-76231886848828615612023-10-24T09:41:00.001+01:002023-10-24T09:41:32.422+01:00Connect an OpenAI chat bot to the internet using Bing Search API<p>
In the previous post, we saw what is OpenAI function calling and how to use it
to chat with your organization's user directory using Microsoft Graph. Please
have a look at the article here: <a
href="https://www.vrdmn.com/2023/10/chat-with-your-user-directory-using.html"
target="_blank"
><span style="color: #2b00fe;"
>Chat with your user directory using OpenAI functions and Microsoft
Graph</span
></a
>
</p>
<p>
In this post, we will implement function calling for a very common scenario of
augmenting the large language model's responses with data fetched from
internet search.
</p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga6MP1p_ou0owmWW3OCNIfWRjRgnzg-511BAgFeKh-5FRxGG2DXg-OxhxMhPSs56N2xLfux9PlGGrgsLZEg1Hm8QOwqLhqHdIPNj7V8ehCqybFaknGHUw48uOWLpHjKF-WqgXVCQjYwDK4ign0lXhvwXPABur0X1wmtuU4zihQzZ4aB1hZssFqtF9uZi0/s733/oaibing.png"
style="margin-left: 1em; margin-right: 1em;"
><img
border="0"
data-original-height="326"
data-original-width="733"
height="178"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga6MP1p_ou0owmWW3OCNIfWRjRgnzg-511BAgFeKh-5FRxGG2DXg-OxhxMhPSs56N2xLfux9PlGGrgsLZEg1Hm8QOwqLhqHdIPNj7V8ehCqybFaknGHUw48uOWLpHjKF-WqgXVCQjYwDK4ign0lXhvwXPABur0X1wmtuU4zihQzZ4aB1hZssFqtF9uZi0/w400-h178/oaibing.png"
width="400"
/></a>
</div>
<p>
<span style="color: #351c75;"
><b
>Since the Large Language model (LLM) was trained with data only up to a
certain date, we cannot talk to it about events which happened after that
date. To solve this, we will use OpenAI function calling to call out the
Bing Search API and then augment the LLM's responses with the data
returned via internet search.</b
></span
>
</p>
<p>
<b
><span style="color: red;"
>This pattern is called Retrieval Augmented Generation or RAG. </span
></b
>
</p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzRjSlfAYyhAnl194H0Oukqtl8ep_mR0BwJ64qomIDRpXPdh0BdtNfB97UGge1e57TSm0wB4SP2jf9OyvOt-W_qIYgnUX5Q-Vk1CAafe-yGu4AHQvg71SMmoUp-VwqBBDhyphenhyphen93HJr98qWS2jGAkfLA3YiTKyPN3RlMBqiB1wmzMPK8DJRt3aH8uX-mjvCo/s810/internet.gif"
style="margin-left: 1em; margin-right: 1em;"
><img
border="0"
data-original-height="519"
data-original-width="810"
height="410"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzRjSlfAYyhAnl194H0Oukqtl8ep_mR0BwJ64qomIDRpXPdh0BdtNfB97UGge1e57TSm0wB4SP2jf9OyvOt-W_qIYgnUX5Q-Vk1CAafe-yGu4AHQvg71SMmoUp-VwqBBDhyphenhyphen93HJr98qWS2jGAkfLA3YiTKyPN3RlMBqiB1wmzMPK8DJRt3aH8uX-mjvCo/w640-h410/internet.gif"
width="640"
/></a>
</div>
<br />
<p>
Let's look at the code now on how to achieve this. In this code sample I have
used the following nuget packages:
</p>
<p>
<a
href="https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.6/"
target="_blank"
><span style="color: #2b00fe;"
>https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.6/</span
></a
>
</p>
<p>
<a
href="https://www.nuget.org/packages/Azure.Identity/1.10.2/"
target="_blank"
><span style="color: #2b00fe;"
>https://www.nuget.org/packages/Azure.Identity/1.10.2/</span
></a
>
</p>
<p>
The very first thing we will look at is our function definition for informing
the model that it can call out to external search API to search information:
</p>
<script src="https://gist.github.com/vman/0534094266c31d0361a79884ea65d631.js"></script>
In this function we are informing the LLM that if it needs to search the
internet as part of providing the responses, it can call this function. The
function name will be returned in the response and the relevant parameters will
be provided as well.
<div><br /></div>
<div>
Next, let's see how our orchestrator looks. I have added comments to each line
where relevant:
</div>
<div><br /></div>
<script src="https://gist.github.com/vman/7e69682c1819f9ff49c55a75a02329cf.js"></script>
This code is responsible for handling the chat with OpenAI, calling the Bing API
and also responding back to the user based on the response from internet
search.
<div><br /></div>
<div>
Next, let's have a look at the code which calls the Bing API based on the
parameters provided by the LLM. Before executing this code, you will need to
have created Bing Web Search API resource in Azure. Here is more information
on it: <a
href="https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/overview"
target="_blank"
><span style="color: #2b00fe;"
>https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/overview</span
></a
><br /><br />The Bing Web Search API key can be found in the "Keys and
Endpoint" section on the Azure resource:
</div>
<div><br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://blogger.googleusercontent.com/img/a/AVvXsEjPfA5mDFDe8qd1sbLDSEW8OMvOq80XokEiwArrOt-AsQTeg7POz7uOTCbvwHi4jmaGIsINiwqsp--Q9IJ6SrJc6-gnBNsBzldBqjuTNpg3Wd8TCr5bmwfi1v-UudCtgA0VcV2WXL1IK7SoEYCQDaWAZlESnLMgjUp1to_FpYrkjsEC9N9f_vOXcMpp0Bs"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="501"
data-original-width="1388"
height="231"
src="https://blogger.googleusercontent.com/img/a/AVvXsEjPfA5mDFDe8qd1sbLDSEW8OMvOq80XokEiwArrOt-AsQTeg7POz7uOTCbvwHi4jmaGIsINiwqsp--Q9IJ6SrJc6-gnBNsBzldBqjuTNpg3Wd8TCr5bmwfi1v-UudCtgA0VcV2WXL1IK7SoEYCQDaWAZlESnLMgjUp1to_FpYrkjsEC9N9f_vOXcMpp0Bs=w640-h231"
width="640"
/></a>
</div>
<div><br /></div>
Here is the code for calling the Bing Search API:
<div><br /></div>
<script src="https://gist.github.com/vman/9e4f38c4ce115d797edf77e43b2e9816.js"></script>
In this code, we are calling the Bing Web Search REST API to get results based
on the search query created by the LLM. Once the top 3 results are fetched we
are getting the text snippets of those results, combining them and sending it
back the LLM.
</div>
<div><br /></div>
<div>
We are only getting the search result snippets to keep this demo simple. In
production, ideally you will need to get the Url of each search result and
then get the content of the page using the Url.
</div>
<div><br /></div>
<div>
Finally, lets have a look at our CallChatGPT function which is responsible for
talking to the Open AI chat API:<br />
</div>
<script src="https://gist.github.com/vman/ec324b980cfd8f0a59b15128871778ca.js"></script>
This code defines the OpenAI function which will be included in our Chat API
calls. Also, the user's question is sent to the Chat API to determine if the
function needs to be called. This function is also called again after the
response from the Bing Web Search API is fetched. At that time, this function
contains the search results and uses them to generate an output in natural
language. <br /><br />This way, we can use Open AI function calling together
with Bing Web Search API to connect our chat bot to the internet!
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-58814369973615489542023-10-19T09:40:00.000+01:002023-10-19T09:40:49.671+01:00Chat with your user directory using OpenAI functions and Microsoft Graph<p>
Ever since OpenAI function calling was released, I have been incredibly
fascinated by it.
<b><span style="color: red;">To me, it is as big a game changer as ChatGPT itself.
</span></b>
</p>
<p>
With function calling, we are no longer limited by the data which was used to
train the Large Language Model (LLM). We can call out to external APIs,
protected company data and other business specific APIs and use the data to
supplement the responses from the LLM.
</p>
<p>
To know more about function calling specifically with the Azure OpenAI
service, check out the Microsoft docs:
<a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling" target="_blank"><span style="color: #2b00fe;">https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling
</span></a>
</p>
<p>
In this post, let's have a look at how we can leverage OpenAI function
calling to chat with our user directory and search for users in natural
language. To make this possible we will use the Microsoft Graph to do the heavy lifting.
</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6wx1gTV0hAEXw2GK4e43E_ftY9sGj4TowwRixmM-N1DRJFVE0R8Fp7egtyqcaZfFx3R471_MRThbclt6eetq43gTYrkIpeTWoDuZTkflPjBZ0V0p4TGovbszuolyJIWVGs-C5KZ4ZZbnzr8hEdkPWLJEOgKNzcfBfVqOifHiTqp5va5w7UWnBljWnTjw/s733/logo.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="733" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6wx1gTV0hAEXw2GK4e43E_ftY9sGj4TowwRixmM-N1DRJFVE0R8Fp7egtyqcaZfFx3R471_MRThbclt6eetq43gTYrkIpeTWoDuZTkflPjBZ0V0p4TGovbszuolyJIWVGs-C5KZ4ZZbnzr8hEdkPWLJEOgKNzcfBfVqOifHiTqp5va5w7UWnBljWnTjw/w400-h178/logo.png" width="400" /></a></div>
This is what we want to achieve: <span style="color: #351c75;"><b>The user asks a question about the people directory in natural language, the LLM is able to
transform the question to code which the Microsoft Graph understands and the LLM
is again able to transform the response from the
Microsoft Graph back to natural language.</b></span><p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8T8cwfJWE9X6JwEiLPTbp4BUp5UEWBf5c68cDtlP96GvFc6FYgxkjilX_MASqn7VsmXlibJifw0zlTWDCnviGS36NGlCjRmp-qR-X3OePuIxtG9NjD3uELjXRn_j53ZQl5MipESf4w0840PARYEhygJcYArTABjwqJHSiCN6Iy5blyXSDt-G1bBXmXkk/s756/Animation.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="756" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8T8cwfJWE9X6JwEiLPTbp4BUp5UEWBf5c68cDtlP96GvFc6FYgxkjilX_MASqn7VsmXlibJifw0zlTWDCnviGS36NGlCjRmp-qR-X3OePuIxtG9NjD3uELjXRn_j53ZQl5MipESf4w0840PARYEhygJcYArTABjwqJHSiCN6Iy5blyXSDt-G1bBXmXkk/s16000/Animation.gif" /></a>
</div>
<br />
<p>On a high level, our approach can be summarised as follows:</p>
<h4 style="text-align: left;">
<span style="color: red;">1. Define the OpenAI functions and make them available to the
LLM </span>
</h4>
<h4 style="text-align: left;">
<span style="color: red;"><br />
2. During the course of the chat, if the LLM thinks that to respond to the
user, it needs to call our function, it will respond with the function name
along with the parameters to be sent to function.<br />
<br />
</span>
</h4>
<h4 style="text-align: left;">
<span style="color: red;">3. Call the Microsoft Graph user search API based on the parameters
provided by the LLM.<br />
<br />
</span>
</h4>
<h4 style="text-align: left;">
<span style="color: red;">4. Send the results returned from the Microsoft Graph back to the LLM to
generate a response in natural language.</span></h4>
<p>Alright, let's now look at the code. In this code sample I have used the following nuget packages:</p><p><a href="https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.6/" target="_blank"><span style="color: #2b00fe;">https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.6/</span></a></p><p><a href="https://www.nuget.org/packages/Microsoft.Graph/5.30.0/" target="_blank"><span style="color: #2b00fe;">https://www.nuget.org/packages/Microsoft.Graph/5.30.0/</span></a></p><p><a href="https://www.nuget.org/packages/Azure.Identity/1.10.2/" target="_blank"><span style="color: #2b00fe;">https://www.nuget.org/packages/Azure.Identity/1.10.2/</span></a></p>
<p>The very first thing we will look at is our function definition:</p>
<p>
<script src="https://gist.github.com/vman/0f7834d2258c75fc80461661b74c79a6.js"></script>
</p>
<p>
In this function we are informing the LLM that if needs to search any users as
part of providing the responses, it can call this function. The function name
will be returned in the response and the relevant parameters will be provided
as well.
</p>
<p><b><span style="color: #351c75;">
The enums in the officeLocation and department parameter will instruct the LLM
to only return those values even if user asks a slightly different variation
in their question. We can see an example of this in the gif above. Even if the
question asked contains words like "devs" and "NY", the LLM is able to
determine and use the terms "Engineering" and "New York" instead.
</span></b></p>
<p>
Next, let's see how our orchestrator looks. I have added comments to each line
where relevant:
</p>
<script src="https://gist.github.com/vman/e655f6ca80ee7ff661fd2f6f12ddda2e.js"></script>
<p>
There is a lot to unpack here as this function is the one which does the heavy
lifting. This code is responsible for handling the chat with OpenAI, calling
the MS Graph and also responding back to the user based on the response from
the Graph.
</p>
<p>
Next, let's have a look at the code which calls the Microsoft Graph based on
the parameters provided by the LLM. </p><p>Before executing this code, you will need to have created an App registration with a <b>clientId</b> and <b>clientSecret. </b>Here is how to do that: <a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app" target="_blank"><span style="color: #2b00fe;">https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app</span></a></p><p>Since we are calling the Microsoft Graph <b><span style="color: red;">/users</span></b> endpoint with application permissions, the app registration will need a minimum of the <b><span style="color: red;">User.Read.All</span></b> application permission granted.<br /><a href="https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http" target="_blank"><span style="color: #2b00fe;">https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http</span></a> </p>
<script src="https://gist.github.com/vman/fcf20c2a2aaaa5a442d08bcbb0ac3f61.js"></script>
<p>
This code get the parameters sent from the LLM and uses the Microsoft Graph
.NET SDK to call the <b><span style="color: red;">/users/search</span></b> endpoint and fetch the users based on the
<b><span style="color: red;">officeLocation</span></b>, <b><span style="color: red;">department</span> </b>or <b><span style="color: red;">jobTitle</span></b> properties.
</p>
<p>
Once the users are returned, their <b><span style="color: red;">displayName</span></b> value is concatenated into a
string and returned to the orchestrator function so that it can be sent again
to the LLM.
</p>
<p>
Finally, lets have a look at our CallChatGPT function which is responsible for
talking to the Open AI chat api.
</p>
<script src="https://gist.github.com/vman/468922bc467d37330c6d7e01b7b717f3.js"></script>
<p>
This function defines the Open AI function which will be included in our Chat
API calls. Also, the user's question is sent to the API to determine if the
function needs to be called. This function is also called again after the
response from the Microsoft Graph is fetched. At that time, this function
contains the details fetched from the Graph to generate an output in natural
language.
</p>
<p>
This way, we can use Open AI function calling together with Microsoft Graph
API to chat with your user directory.
</p>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-52680361833428795642022-12-19T06:38:00.000+00:002022-12-19T06:38:57.702+00:00Building a Microsoft Teams app: Posting a message to Teams on behalf of the current user<p>
If you are building a Teams app and want to integrate with Teams channel
messages, this post will be helpful to you. Specifically, we will be looking at posting a Teams channel message, from the
app, on behalf of the currently logged in user. For example, there could
be a scenario where an event occurs within the Teams app and as a result a
message needs to be posted to a Teams channel but instead of it coming from a
bot, it needs to be posted by the logged in user's account.</p>
<p>Let's see how this can be done. </p>
<h3 style="text-align: left;">
<span style="color: #38761d;">1. Azure AD app and Permissions</span>
</h3>
<p>
First thing we need is an Azure AD app setup with the Microsoft Graph
<b><span style="color: red;">ChannelMessage.Send</span></b>
<b><span style="color: red;">delegated permission</span></b>. This permission is needed for posting messages to Teams using the current
user credentials.
</p>
<p>
I should mention setting up the right permissions is part of a larger
configuration in the Azure AD app needed for Single Sign On (SSO) setup in
Teams app. You can see the full configuration here: <a href="https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/tab-sso-register-aad" target="_blank"><span style="color: #2b00fe;">Register your tab app with Azure AD - Teams | Microsoft Learn</span></a>
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM_jRsRYDIvvLNnC7DJU8aI1PF1MrbRilY-rBa44Y6_kUP_tEgUkfwM2Fbkt0FrT_fA9VeJVMaPO75p6QYrVrQQuAGad5nlq7hlV5_yEwWrYYRyaj2sSgrFoeVG7n9-c9xl2vZ3Qxhh-t7s5g73YYNcsxsbccJ9Mc7mvIISreyBb1BypM3vc0Sl7nk/s2170/Screenshot%202022-12-16%20143141.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1234" data-original-width="2170" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM_jRsRYDIvvLNnC7DJU8aI1PF1MrbRilY-rBa44Y6_kUP_tEgUkfwM2Fbkt0FrT_fA9VeJVMaPO75p6QYrVrQQuAGad5nlq7hlV5_yEwWrYYRyaj2sSgrFoeVG7n9-c9xl2vZ3Qxhh-t7s5g73YYNcsxsbccJ9Mc7mvIISreyBb1BypM3vc0Sl7nk/w640-h364/Screenshot%202022-12-16%20143141.png" width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<h3 style="text-align: left;">
<span style="color: #38761d;">2. Current user's id token from Teams JS SDK v2</span>
</h3>
<p>
Once the permissions are setup, we need to setup our frontend so that it can
grab the current user's id token from Microsoft Teams. More info on Azure AD
id tokens here: <a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens" target="_blank"><span style="color: #2b00fe;">Microsoft identity platform ID tokens - Microsoft Entra | Microsoft
Learn</span></a>
</p>
<p>
Although this token is available from Teams JS SDK v2, it cannot itself be
used to make graph calls. We need to exchange it for a Microsoft Graph access
token. For this we will send the id token to our backend:</p>
<script src="https://gist.github.com/vman/1471e262c2baafab859db1b396631880.js"></script>
<h3 style="text-align: left;">
<span style="color: #38761d;">3. Getting Microsoft Graph delegated access token and posting message to Teams</span></h3>
<p>
It is recommended to do the token exchange as well as any further Graph calls
from the backend of your app instead passing the Graph access token back to
the frontend and making the calls from there:</p>
<script src="https://gist.github.com/vman/4350857ca03b08840653a96fc9c520a8.js"></script>
<p>In the code we first graph the id token from the Authorization header, then exchange the id token for a Microsoft Graph access token, then finally we are able to make a Graph call to post a message to Teams as the current user. <br /><br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj-YX79vI6pIdPy_MX97D1kRCxIep8qPCbfOIjYc4M0xzvoHJWXT0MlZ_Q61bUwRYsjvHg4tu_Q2cw0tAQlQnhqumefjEd-wHDmuQ4223H3lVv39r40DYfNCKYa1n20F1rWbyLn2_elVZhyTxbctJlFnTgt4DCw1xXOmUNaA4CF8hVAE2_6z7Kqiipd" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="204" data-original-width="1032" src="https://blogger.googleusercontent.com/img/a/AVvXsEj-YX79vI6pIdPy_MX97D1kRCxIep8qPCbfOIjYc4M0xzvoHJWXT0MlZ_Q61bUwRYsjvHg4tu_Q2cw0tAQlQnhqumefjEd-wHDmuQ4223H3lVv39r40DYfNCKYa1n20F1rWbyLn2_elVZhyTxbctJlFnTgt4DCw1xXOmUNaA4CF8hVAE2_6z7Kqiipd=s16000" /></a></div><br />Hope this helps!<p></p>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-79948269248325764222022-11-23T11:04:00.004+00:002022-11-23T11:04:51.041+00:00Building a custom Microsoft Teams tab: Show a native loading indicatorWhen building a custom Microsoft Teams tab with Teams manifest v1.7+, there is an option to natively show a loading indicator in Teams before our app loads. This can be helpful if your app needs to fetch any data before the first load.<div><br /></div><div>There are two parts to showing the loading indicator: </div><div><br /></div><div>1. The <b><span style="color: #674ea7;">showLoadingIndicator</span></b> property in the Teams manifest needs to be set to <b><span style="color: #674ea7;">true</span></b>.</div><div>2. When the tab loads, the <b><span style="color: #674ea7;">app.initialize()</span></b> and <b><span style="color: #674ea7;">app.notifySuccess()</span></b> methods of the Teams JS SDK v2 should be called to indicate to Teams that the app is ready to load and to hide the loading indicator.</div><div><br /></div><div>Now there are some "interesting" things to know about this. If you are using the <a href="https://dev.teams.microsoft.com/" target="_blank">Teams Developer Portal</a> to configure your Teams app, then as of now the <b><span style="color: #674ea7;">showLoadingIndicator</span></b> property is set to <b><span style="color: #674ea7;">true</span></b> by default and there is no way to change this in the portal. You will have to download the app manifest and make the necessary changes in the json manually.</div><div><br /></div><div>If you are just starting with Microsoft Teams development and are unaware of the loading indicator option, the error message shown on the tab is not much help in figuring out the issue. It simply says:</div><div><br /></div><div><span style="color: red;"><b>"There was a problem reaching the app"</b></span></div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-zpoTI-Ume-qvu5cquZ5GTbfBam1UvXKec81lsSAxvxUVWhRIN43Akdpu2a81dZ0wSuL3gYSwl8kOTxDo841_U_Wn06EjoWrQd4zVGqHI114XvBaBVS7ItMG0_wUDr-BiTOPILW20dGYbu-e43nDymubo_LH4unsgKa3rnxXEk6AVRnQ3AP5qz9Hm/s1404/Screenshot%202022-11-23%20100431.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="867" data-original-width="1404" height="395" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-zpoTI-Ume-qvu5cquZ5GTbfBam1UvXKec81lsSAxvxUVWhRIN43Akdpu2a81dZ0wSuL3gYSwl8kOTxDo841_U_Wn06EjoWrQd4zVGqHI114XvBaBVS7ItMG0_wUDr-BiTOPILW20dGYbu-e43nDymubo_LH4unsgKa3rnxXEk6AVRnQ3AP5qz9Hm/w640-h395/Screenshot%202022-11-23%20100431.png" width="640" /></a></div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><div style="text-align: left;">This is because the <b><span style="color: #674ea7;">showLoadingIndicator</span></b> property has been set to true by the Teams Developer portal and we don't yet have our tab calling the <b><span style="color: #674ea7;">app.initialize()</span></b> and <span style="color: #674ea7;"><b>app.notifySuccess()</b></span> methods of the SDK. So, Teams thinks that our app has failed to load and shows the error message.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">There are two possible solutions to fixing this problem:</div><div style="text-align: left;"><br /></div><div style="text-align: left;">1. Set the <b><span style="color: #674ea7;">showLoadingIndicator</span></b> property to <span style="color: #674ea7;"><b>false</b></span> manually in the manifest json. But the drawback to this approach then, is that no native loading indicator would be shown, and the users might have to stare at a blank screen until the content loads.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">2. Let the <b><span style="color: #674ea7;">showLoadingIndicator</span></b> property be set to true and then make sure we call the <b><span style="color: #674ea7;">app.initialize()</span></b> and <span style="color: #674ea7;"><b>app.notifySuccess()</b></span> methods of the SDK.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Manifest.json:</div><div style="text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiud_XA7cAEhQf6q2mJke8wnhVUVo6LjdE-_04E6j5BOcWX2Mc2kZrzhHjOHh_R2FwkfZqrwhAPhLq7HxozmQm67i80tGhG7RWThcGIbLndqPPBP3zqCfZiBcrLdXl3oGCCwaQXY0WKYxDXowT3b9y6Kqc2HcXjKg5up8_JfHDObs7vjcBINkLt9qWs/s440/Screenshot%202022-11-23%20102514.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="115" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiud_XA7cAEhQf6q2mJke8wnhVUVo6LjdE-_04E6j5BOcWX2Mc2kZrzhHjOHh_R2FwkfZqrwhAPhLq7HxozmQm67i80tGhG7RWThcGIbLndqPPBP3zqCfZiBcrLdXl3oGCCwaQXY0WKYxDXowT3b9y6Kqc2HcXjKg5up8_JfHDObs7vjcBINkLt9qWs/s16000/Screenshot%202022-11-23%20102514.png" /></a></div><br /><div style="text-align: left;"><br /></div><div style="text-align: left;">Tab react code:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5FRD_S6FZOXWC554Yg3LvoV79NmQD5QRXl1QFV_HJ1TyGGcKfPfuWYnO_vMxSS5FGeW-TrYVewq72O5sROe2TLv_BKRIeIHTrdBbZmv7iurPviOns4SCDrmJPc6Yyge3pDM0jj9jH_jea9eLawtOwXGd7yiyCNNUedtvdZVKtCyN5aRdy4hGllw81/s371/Screenshot%202022-11-23%20103135.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="234" data-original-width="371" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5FRD_S6FZOXWC554Yg3LvoV79NmQD5QRXl1QFV_HJ1TyGGcKfPfuWYnO_vMxSS5FGeW-TrYVewq72O5sROe2TLv_BKRIeIHTrdBbZmv7iurPviOns4SCDrmJPc6Yyge3pDM0jj9jH_jea9eLawtOwXGd7yiyCNNUedtvdZVKtCyN5aRdy4hGllw81/s16000/Screenshot%202022-11-23%20103135.png" /></a></div><br /><div style="text-align: left;"><br /></div><div style="text-align: left;">There are further options possible with the loading indicator like hiding the loading indicator even if the app continues to load in the background and explicitly specifying loading failure. To see more details about those, the Microsoft docs are a good place to start: <a href="https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/create-tab-pages/content-page?tabs=teamsjs-v2#show-a-native-loading-indicator" style="text-align: center;" target="_blank"><span style="color: #2b00fe;">Create a content page - Teams | Microsoft Learn</span></a></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Hope this helps!</div></div></div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-12760576984061116492022-09-14T12:53:00.000+01:002022-09-14T12:53:50.904+01:00Partially update documents in Azure Cosmos DB<p>
I have been working with Cosmos DB for a while now and until recently, there
was one thing which always annoyed me: When updating a JSON document stored in
a container, there was no way to only modify a few selected properties of the
document.
</p>
<p>
The entire JSON document had to be fetched by the client first, then locally
replace the properties to be updated, and then send the entire document back
to Cosmos DB and replace the previous version of the document.
</p>
<p>
This was always a challenge because first, it added more work for developers
and second, there could be concurrency issues if multiple clients could be
downloading multiple copies of the document and updating the data and sending
back their copy.
</p>
<p>
But fortunately, now it's possible to partially update a document in Cosmos DB
and basically do a PATCH operation while only sending the properties to be
changed over the wire.
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/a/AVvXsEjQRZ0wKnmJepJvKKOZBV4TohUsXjeINMnBeVIYB79zh3IPmwRZNDrOwIHgvCbDE94lD8GpCdhfDuE7py0FNEHfRsGiSjO5rcBlqW_RlgxTIjkf7mxLFau4eIavShvjwo-LWjSflUGbAYXEFW-QN2HFsVgUCYLW9FX2K-p-GRn_ogFzsvSHDh80xrWK" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="350" data-original-width="560" height="250" src="https://blogger.googleusercontent.com/img/a/AVvXsEjQRZ0wKnmJepJvKKOZBV4TohUsXjeINMnBeVIYB79zh3IPmwRZNDrOwIHgvCbDE94lD8GpCdhfDuE7py0FNEHfRsGiSjO5rcBlqW_RlgxTIjkf7mxLFau4eIavShvjwo-LWjSflUGbAYXEFW-QN2HFsVgUCYLW9FX2K-p-GRn_ogFzsvSHDh80xrWK=w400-h250" width="400" /></a>
</div>
<br /><br />
<p></p>
<p>
So in this post, let's have a look at the different ways in which we can
partially update documents in Cosmos DB:
</p>
<h3 style="text-align: left;">
<span style="color: #38761d;">Setting up by creating a Cosmos DB document</span>
</h3>
<p>
First, we will create our sample document using code. I should mention I am
using v3.30.1 of Azure Cosmos DB .NET core package from nuget: <a href="https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.30.1/">Microsoft.Azure.Cosmos</a>
</p>
<script src="https://gist.github.com/vman/16d665402e953342229cd74d026ef35f.js"></script>
<p>
As you can see this is a simple document representing a User object with a few
attached properties:
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/a/AVvXsEhLmn-Z6c5X0LttNh1x_ouvxy7ztNE8DbhzKBtQWcYhSEqSdaIaEC71IeTNZpJDsv03p1jKtMix9FKYWiEtdUVwObXachDW2mPcClk86U_ePiKH6tRqYFixwe68JMDvfV0JOABkCzWuTa3eexTS_mwuil6VbkDVg2LF_EX61GNMUNrmlGPVPnUZmN_f" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="379" data-original-width="650" src="https://blogger.googleusercontent.com/img/a/AVvXsEhLmn-Z6c5X0LttNh1x_ouvxy7ztNE8DbhzKBtQWcYhSEqSdaIaEC71IeTNZpJDsv03p1jKtMix9FKYWiEtdUVwObXachDW2mPcClk86U_ePiKH6tRqYFixwe68JMDvfV0JOABkCzWuTa3eexTS_mwuil6VbkDVg2LF_EX61GNMUNrmlGPVPnUZmN_f=s16000" /></a>
</div>
<br />Now in order to only change a few properties in the object, we need to use
the <span style="color: #2b00fe;"><a href="https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update" target="_blank">Partial document update feature in Azure Cosmos DB</a>
</span>
<p></p>
<h3 style="text-align: left;">
<span style="color: #38761d;">Update document properties</span>
</h3>
<p>
Now lets have a look at how we can modify and add properties to the User
object:
</p>
<script src="https://gist.github.com/vman/4697d430cf156ea19c2826f530078440.js"></script>
In the code, we are updating an existing property of the document as well as
adding a new property. Also, we are only sending the properties to be modified
over the wire.
<div><br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/a/AVvXsEgaWSFrsLwj8UaT1615nw0lq8ftSK0Pbu2YMV8-yhR1S3m1a-WcrI0BPBTAEIBj0Bisy4IHItF9twammsn6yMCV_lLnYzzPWa3sfaS2wXe-vDkpK_7PaEE0fFBFfhv_OzBi2WubrTzIKV1jiDwJ-fOf6YJ99KnLwnzQmIOxNWkpVhiJRPSYQ-oM8chi" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="407" data-original-width="634" height="410" src="https://blogger.googleusercontent.com/img/a/AVvXsEgaWSFrsLwj8UaT1615nw0lq8ftSK0Pbu2YMV8-yhR1S3m1a-WcrI0BPBTAEIBj0Bisy4IHItF9twammsn6yMCV_lLnYzzPWa3sfaS2wXe-vDkpK_7PaEE0fFBFfhv_OzBi2WubrTzIKV1jiDwJ-fOf6YJ99KnLwnzQmIOxNWkpVhiJRPSYQ-oM8chi=w640-h410" width="640" /></a>
</div>
<br />We can also send both properties in a single operation by adding the
operations to the patchOperations array.
</div>
<div><br /></div>
<h3 style="text-align: left;">
<span style="color: #38761d;">Update elements in array</span>
</h3>
<div><br /></div>
<div>
Just like document properties, we can also update single elements in an array
in the document. Elements can be updated, added, added at a specific index and
also removed from an array:
</div>
<div><br /></div>
<script src="https://gist.github.com/vman/d63e8ffbd5d8add1b8550b3e7f39b2fa.js"></script>
<div><br /></div>
<h3 style="text-align: left;">
<span style="color: #38761d;">Update objects and their properties</span>
</h3>
<div><br /></div>
<div>
The properties of a JSON document can themselves be objects as well. The Azure
Cosmos DB patch operations can also be used to update properties of specific
objects as well as modifying the objects themselves.
</div>
<div><br /></div>
<script src="https://gist.github.com/vman/06e51c3ecb38a7c5b6d55251eb34ae7e.js"></script>
<p>
Hope this helps! For more details, do have a look at the Microsoft docs for Azure Cosmos DB partial updates: <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update
</span></a></p>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com1tag:blogger.com,1999:blog-4455058894110083658.post-4353524166430995312022-02-07T08:47:00.000+00:002022-02-07T08:47:17.230+00:00Working with the Microsoft Graph communications API in a Microsoft Teams meeting app<p>
Following up on my previous
<a href="https://www.vrdmn.com/2022/01/working-with-apps-for-microsoft-teams.html" target="_blank">post about Microsoft Teams meeting apps</a>, let's now have a look at how we can invoke the Microsoft Graph in
our meeting app.
</p>
<p>
Specifically, we will be using the Microsoft Graph communications API to get
all meeting participants.
</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhN95cztfc2kUyQrXw8HSw6trhtx7qqHEcu8vDxhdYIoIs7hdyTT1Ymqn68mPN7SimHOvYwkLgjsGFiRtgF2Ic9TkqbBu_KIXmwbVNyD1Zmi0Z7RUBSLHxNaAAJ0aI4a0jo7JpTTp4-drnh90NW2vyJ2_KXy6Px86Lo8J-IIQnZrXMBaWCN9Za1ap8J" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="917" data-original-width="2705" height="217" src="https://blogger.googleusercontent.com/img/a/AVvXsEhN95cztfc2kUyQrXw8HSw6trhtx7qqHEcu8vDxhdYIoIs7hdyTT1Ymqn68mPN7SimHOvYwkLgjsGFiRtgF2Ic9TkqbBu_KIXmwbVNyD1Zmi0Z7RUBSLHxNaAAJ0aI4a0jo7JpTTp4-drnh90NW2vyJ2_KXy6Px86Lo8J-IIQnZrXMBaWCN9Za1ap8J=w640-h217" width="640" /></a></div><p></p><p>
When I was first looking at this scenario, I thought it would be quite
straightforward as this seems to be quite a common use case for meeting apps.
However, I was in for a bit of a surprise as that did not turn out to be the
case.
</p>
<p>
My thinking was that I would get a <span style="color: #990000;">meetingId</span> as part of the Teams JS SDK
context object and I would use this <span style="color: #990000;">meetingId</span> to get details from the Graph.
<span style="color: #990000;">But it turns out that the meetingId which the Teams JS SDK provides is
completely different than the meetingId recognised by that Microsoft
Graph</span>.
</p>
<p>
To fetch the meetingId which the Graph recognises, we have to introduce the
Bot Framework SDK to the mix and exchange the Teams JS SDK meetingId for the
Graph meeting Id.
</p>
<p>So let's see how to achieve this using code:</p>
<h3 style="text-align: left;">
<span style="color: #38761d;">1) Using Teams JS SDK, get user id, meeting id, tenant id</span>
</h3>
<p>
This bit is straightforward, in your Teams tab you can get the meeting context
using the Teams JS SDK:
</p>
<p>
<script src="https://gist.github.com/vman/c71e0123e562ed11ba8c17597894be6c.js"></script>
</p>
<h3 style="text-align: left;">
<span style="color: #38761d;">2) Use Bot SDK to get Graph meeting ID from the Teams meeting id</span>
</h3>
<p>
Next, from your Teams Bot, call the
<span style="color: red;"><b>/v1/meetings/{meeting-id}</b></span> API. Couple
of things you should know beforehand:
</p>
<p>
This API is currently in developer preview. This means that it will only work
for when the Microsoft Teams Clients have been switched to preview mode.
</p>
<p>
Secondly, we have to turn on Resource Specific Consent (RSC) for your app and
request the required scopes. More details on both points here: <a href="https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/api-references?tabs=dotnet#get-meeting-details-api" target="_blank">https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/api-references?tabs=dotnet#get-meeting-details-api</a>
</p>
<p>
Once you have everything setup, you can call the API to get the meeting
details:
</p>
<p>
<script src="https://gist.github.com/vman/efdd1ec0ef164b4349801d8dbbbcc1d1.js"></script>
</p>
<p>
The response would contain a details object with an <b><span style="color: #990000;">msGraphResourceId</span></b>
property. This will be the meeting id which would work with the Graph API.
</p>
<p>
<script src="https://gist.github.com/vman/f24248516b8585bca6de60f735de6a28.js"></script>
</p>
<h3 style="text-align: left;">
<span style="color: #38761d;">3) Use Graph API to get meeting participants from Graph meeting id</span>
</h3>
<p>
Finally, from the msGraphResourceId, we can make a regular call to the Graph
to get the meeting details. Specifically, we will call the <b><span style="color: red;">/onlineMeetings/{meetingId} </span></b>endpoint: <a href="https://docs.microsoft.com/en-us/graph/api/onlinemeeting-get?view=graph-rest-1.0&tabs=http" target="_blank">https://docs.microsoft.com/en-us/graph/api/onlinemeeting-get?view=graph-rest-1.0&tabs=http</a></p><p>In this case, we will get the meeting
participants:
</p>
<p><script src="https://gist.github.com/vman/ae3d698dbbd4b240a9569fba997a2f53.js"></script></p>
<p><span style="color: red;">Make sure you have the correct Delegated or Application permissions granted to the Azure AD app registration you are using for authenticating to the Graph.</span></p><p>This is not an ideal situation as you might not have a requirement for a Bot
in your Teams app and might be building a tab for example. In this
case, <a href="https://twitter.com/YannickReekmans" target="_blank">Yannick Reekmans</a> has written a post for you here: <a href="https://blog.yannickreekmans.be/get-full-meeting-details-in-a-teams-meetings-app-without-using-bot-sdk/">https://blog.yannickreekmans.be/get-full-meeting-details-in-a-teams-meetings-app-without-using-bot-sdk/</a> </p><p>That's it for now. Hope you find this helpful when exploring meeting apps right now.</p>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-22014852884528653802022-01-13T04:44:00.000+00:002022-01-13T04:44:36.703+00:00Working with Apps for Microsoft Teams meetings<p>
Microsoft Teams meetings extensions or "Apps for Teams meetings" are the
newest entry in the Microsoft Teams extensibility story. They can be used to
build custom experiences right into the meeting experience. Meeting
participants are able to interact with the custom experiences either before,
during or after the meeting. To know more about apps for Teams meetings, a
great place to start is the Microsoft docs: <a
href="https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/teams-apps-in-meetings"
><span style="color: #2b00fe;"
>https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/teams-apps-in-meetings</span
></a
>
</p>
<h3 style="text-align: left;">
<span style="color: #274e13;">Pre-meeting and Post-meeting experiences</span>
</h3>
<p>
The pre and post meeting experiences are not too different than what we are
used to when building Tabs for Teams. The basic structure remains the same
with the only different thing being that the Teams SDK provides the meeting
specific APIs when invoked from a meeting app. These APIs can be used to get
meeting details like participants and meeting context in our app. More
information on the APIs here: <a
href="https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/api-references?tabs=dotnet"
><span style="color: #2b00fe;"
>https://docs.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/api-references?tabs=dotnet</span
></a
>
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-l8W9UbTLQXE/YdZ6neYUzfI/AAAAAAAAG_4/UtMZsg3cEhwYhW9_Du0iufIC-HeAuCd-ACNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="693"
data-original-width="1200"
height="370"
src="https://lh3.googleusercontent.com/-l8W9UbTLQXE/YdZ6neYUzfI/AAAAAAAAG_4/UtMZsg3cEhwYhW9_Du0iufIC-HeAuCd-ACNcBGAsYHQ/w640-h370/image.png"
width="640"
/></a>
</div>
<br />
<h3 style="text-align: left;">
<span style="color: #274e13;">In-meeting experiences</span>
</h3>
<p></p>
<p>
When it comes to the In-meeting experiences, there are two main areas: The
side panel and the in meeting dialog box (also known as content bubble). The
side panel is used to show custom experiences while the meeting is in progress
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-VXPei8e4e9I/YdZ9WcbWqiI/AAAAAAAAHAA/nglg8sPp6LICNtuZsw3bP3Bg51OyLshiwCNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="1015"
data-original-width="1752"
height="370"
src="https://lh3.googleusercontent.com/-VXPei8e4e9I/YdZ9WcbWqiI/AAAAAAAAHAA/nglg8sPp6LICNtuZsw3bP3Bg51OyLshiwCNcBGAsYHQ/w640-h370/image.png"
width="640"
/></a>
</div>
<br />And the in-meeting dialog box (or content bubble) is used to show content,
prompt or collect feedback from the users during the meeting:
<div><br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-vewoHKiQWZc/YdaAXHcL1FI/AAAAAAAAHAI/ToZ2AhHLhPUwnsaUXtiKrUqBE6c3IgYYwCNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="972"
data-original-width="1682"
height="370"
src="https://lh3.googleusercontent.com/-vewoHKiQWZc/YdaAXHcL1FI/AAAAAAAAHAI/ToZ2AhHLhPUwnsaUXtiKrUqBE6c3IgYYwCNcBGAsYHQ/w640-h370/image.png"
width="640"
/></a>
</div>
<br /><span style="color: #cc0000;"
>I should mention here that the In-meeting experiences only work in the
Teams desktop and mobile clients as of now. They don't work in the Teams web
browser interface at the time of this writing. To me this is the biggest
challenge for using them in production.</span
>
</div>
<div>
<span style="color: #cc0000;"><br /></span>
</div>
<div>
Now that's enough introduction of the concepts, let's take a look at how to
actually build these experiences and what are the moving pieces when building
them. The Microsoft docs do provide some great step by step tutorials for each
of the use cases.
</div>
<div><br /></div>
<div>
In this post we will look at the In-meeting dialog box. We will take a look at
the Microsoft's code sample and walk through it. You can find the code sample
here: <a
href="https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/meetings-content-bubble/csharp"
target="_blank"
>https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/meetings-content-bubble/csharp</a
>
</div>
<div>
<br />
<h3 style="text-align: left;">
<span style="color: #38761d;"
>1) Configure an Azure Bot and enable Teams Channel</span
>
</h3>
<p>
Create a bot in Azure and configure the endpoint which should receive the
Teams events:
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/--SAMfeWi118/Yd1nv9pb6eI/AAAAAAAAHAo/bAUEIKHec10XykhraAqSMMrQ9Vutd5_OACNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="660"
data-original-width="996"
height="424"
src="https://lh3.googleusercontent.com/--SAMfeWi118/Yd1nv9pb6eI/AAAAAAAAHAo/bAUEIKHec10XykhraAqSMMrQ9Vutd5_OACNcBGAsYHQ/w640-h424/image.png"
width="640"
/></a>
</div>
<br />Next, add the Teams channel so that the bot is able to talk to Microsoft
Teams:
<p></p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-yATSg6J07Zo/Yd1oYrlKZ3I/AAAAAAAAHAw/dcmhOgfX_mID1vnjmkBOAmI3JttVnQztwCNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="569"
data-original-width="1128"
height="322"
src="https://lh3.googleusercontent.com/-yATSg6J07Zo/Yd1oYrlKZ3I/AAAAAAAAHAw/dcmhOgfX_mID1vnjmkBOAmI3JttVnQztwCNcBGAsYHQ/w640-h322/image.png"
width="640"
/></a>
</div>
<br /><br />
<p></p>
<h3 style="text-align: left;">
<span style="color: #38761d;">2) Update the Teams manifest</span>
</h3>
<p>
For the In-meeting dialog box, we don't have to make any special changes in
the manifest. We just need to make sure that since the dialog box will be
shown via the bot, our Teams app manifest should have the bot configured as
part of it.
</p>
<script src="https://gist.github.com/vman/0265d7ceb253336745de50ab781f25f3.js"></script>
<p><br /></p>
<h3 style="text-align: left;">
<span style="color: #38761d;"
>3) Create an Azure AD app registration for the app</span
>
</h3>
<div><br /></div>
<div>
When we created the Azure Bot, a new Azure AD app registration was created
behind the scenes as well. Grab the client id and client secret from this
app as we would need it later to add to our bot config
</div>
<div><br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-voyVoJ05tok/Yd1paaKAIDI/AAAAAAAAHA4/RoGAVtD-OK4XFzYa4XvMFLIJimQzYKzEgCNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="509"
data-original-width="1302"
height="250"
src="https://lh3.googleusercontent.com/-voyVoJ05tok/Yd1paaKAIDI/AAAAAAAAHA4/RoGAVtD-OK4XFzYa4XvMFLIJimQzYKzEgCNcBGAsYHQ/w640-h250/image.png"
width="640"
/></a>
</div>
<br /><br />
</div>
<h3 style="text-align: left;">
<span style="color: #38761d;"
>4) Start the ngrok tunnel and update the code sample:</span
>
</h3>
<p></p>
<div class="separator" style="clear: both; text-align: left;">
To debug locally, you will need to setup an ngrok tunnel to your local
machine. More details here: <a
href="https://ngrok.com/"
target="_blank"
>https://ngrok.com/</a
>
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-LSbW0RIneAs/Yd1sY0oY_hI/AAAAAAAAHBA/bvh_VM-3XQQQbCORUsktlFJtPDTktdZWACNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="283"
data-original-width="712"
height="254"
src="https://lh3.googleusercontent.com/-LSbW0RIneAs/Yd1sY0oY_hI/AAAAAAAAHBA/bvh_VM-3XQQQbCORUsktlFJtPDTktdZWACNcBGAsYHQ/w640-h254/image.png"
width="640"
/></a>
</div>
<br />And in the code sample, update the bot client id and client secret along
with the ngrok tunnel url:
<p></p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://lh3.googleusercontent.com/-PzYzSDoMlhY/Yd1tFOeb98I/AAAAAAAAHBI/G5pKyjSKCbIuJgysilHKmqy1HD2BxJQXQCNcBGAsYHQ/image.png"
style="margin-left: 1em; margin-right: 1em;"
><img
alt=""
data-original-height="87"
data-original-width="542"
src="https://lh3.googleusercontent.com/-PzYzSDoMlhY/Yd1tFOeb98I/AAAAAAAAHBI/G5pKyjSKCbIuJgysilHKmqy1HD2BxJQXQCNcBGAsYHQ/s16000/image.png"
/></a>
</div>
<br /><br />
<p></p>
<h3 style="text-align: left;">
<span style="color: #38761d;">5) In meeting dialog code:</span>
</h3>
<p>
The way to bring up an in-meeting dialog is to use the regular Bot
Framework <span style="color: #38761d;"
><b>turnContext.SendActivityAsync(activity) </b></span
>code but with updated Teams channel data:
</p>
<script src="https://gist.github.com/vman/7e3fc7e7a0ae9a482c2b9d9c44baea26.js"></script>
<p>
You will notice that in the In-meeting dialog url, there is a reference to
<b
><span style="color: #38761d;"
>{_config["BaseUrl"]}/ContentBubble </span
></b
>
</p>
<p>
This means that the contents of the in-meeting dialog have to be hosted in
our app. This is good news as that means we have complete control over what
is displayed in the dialog. In this code sample, the contents are hosted in
an MVC view:
</p>
<script src="https://gist.github.com/vman/b2bce06d02944a061d9e7e07fe2aea36.js"></script>
<p>
And once everything fits together, we can see the sample code running to
show an In-meeting dialog launched in the context of a meeting:
</p>
<div class="separator" style="clear: both; text-align: center;">
<a
href="https://blogger.googleusercontent.com/img/a/AVvXsEgVUj2cyIFsgqo-HtCVDt_R1yEIcu6sco60YPvi0yqBvhQf8AoXcsNJyJUPUloBc1Rk78BZAV5I0n_tJ2M2E09mJwtoIQHlEoQpsq8rbgAr6xW0XkSx9iWIuFinbdrSJbm2-HHM3pVm5Bb5PY_mIAzxCTiG9WCslmPSSG8fYyXwJmn4RpCopbXcQuGs=s1089"
style="margin-left: 1em; margin-right: 1em;"
><img
border="0"
data-original-height="667"
data-original-width="1089"
height="392"
src="https://blogger.googleusercontent.com/img/a/AVvXsEgVUj2cyIFsgqo-HtCVDt_R1yEIcu6sco60YPvi0yqBvhQf8AoXcsNJyJUPUloBc1Rk78BZAV5I0n_tJ2M2E09mJwtoIQHlEoQpsq8rbgAr6xW0XkSx9iWIuFinbdrSJbm2-HHM3pVm5Bb5PY_mIAzxCTiG9WCslmPSSG8fYyXwJmn4RpCopbXcQuGs=w640-h392"
width="640"
/></a>
</div>
<br />
Hope this helps, and thanks for reading!
</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com3tag:blogger.com,1999:blog-4455058894110083658.post-79979951689676503512021-12-16T05:50:00.001+00:002021-12-16T05:50:20.269+00:00Building a Microsoft Teams bot: Sending custom data from an adaptive card button to the bot<p>
This is the third article in my "Building a Microsoft Teams Bot"
series. In this series, I have written down some interesting things I came
across while creating a Microsoft Teams Bot app which is now available on
AppSource: <a href="https://appsource.microsoft.com/en-us/product/office/WA200002297" target="_blank"><span style="color: #2b00fe;">https://appsource.microsoft.com/en-us/product/office/WA200002297</span></a>
</p>
<p>Click here to see the previous articles in the series: </p>
<p>
<a href="https://www.vrdmn.com/2021/01/building-microsoft-teams-bot-for.html" target="_blank"><span style="color: #2b00fe;">Building a Microsoft Teams Bot: Posting an Adaptive Card carousel as a
welcome message</span></a>
</p>
<p>
<a href="https://www.vrdmn.com/2021/11/building-microsoft-teams-bot-deep.html" target="_blank">
<span style="color: #2b00fe;">
Building a Microsoft Teams Bot: Deep linking to a Teams message from an
Adaptive Card button
</span>
</a>
</p>
<p>
Todays article is around how to pass custom data from Adaptive Cards back to
the Teams bot. This can be useful in scenarios when we want to show multiple
options to the user in an Adaptive card and when the user selects an option, we
want to send that option back to the bot and perform a relevant operation.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-tpvS_6a55Uw/YbostgTNpeI/AAAAAAAAG-o/ur9OzctJ-3A8cf555fBomj7BkCZnd2MSwCNcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="204" data-original-width="964" height="135" src="https://lh3.googleusercontent.com/-tpvS_6a55Uw/YbostgTNpeI/AAAAAAAAG-o/ur9OzctJ-3A8cf555fBomj7BkCZnd2MSwCNcBGAsYHQ/w640-h135/image.png" width="640" /></a></div><br /><br /><p></p>
To achieve this, we will use the data property of the Adaptive Card Submit action: <a href="https://adaptivecards.io/explorer/Action.Submit.html" target="_blank"><span style="color: #2b00fe;">https://adaptivecards.io/explorer/Action.Submit.html</span></a> <div>This property contains a key value pair of custom data which can be sent back to the bot. </div><div><br /></div><div>Here is a sample of how the card json will look when posting it to Teams:</div>
<br />
<script src="https://gist.github.com/vman/d96d669739eeef2811a28b5a2c732f84.js"></script>
<p>
To build this type of card in our bot code, we will iterate over the options and create Adaptive card Submit buttons with the relevant values in the data property:</p>
<script src="https://gist.github.com/vman/5f0cf2f8612ca2608c0396c8b35a6312.js"></script>
<p>
And finally, when the user clicks on a button, Teams platform will send the corresponding data property back to the bot. This can then be used in the Submit action of the bot to perform a relevant operation.
</p>
<script src="https://gist.github.com/vman/2a97f3311ebe701df36740ab0bc9939d.js"></script>
Hope this was helpful!Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-79299735034675017762021-11-16T07:00:00.007+00:002021-11-24T08:11:06.853+00:00Interactively authenticate Microsoft Graph .NET SDK with Azure Identity library<p>
In this post, we will have a look at the new
recommended way of using the Azure Identity library to authenticate to the
Microsoft Graph .NET SDK. Since v4 of the Microsoft Graph SDK, using the Azure.Identity library is the preferred way to auth with the Graph over the previous Microsoft.Graph.Auth method. You can read more about it here: <span style="color: #2b00fe;"><a href="https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/upgrade-to-v4.md#azure-identity" target="_blank">https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/upgrade-to-v4.md#azure-identity</a></span></p>
<p>Specifically, we will have a look at Interactive browser based authentication where the user
would enter their credentials and the Azure Identity library will fetch the
access token based on them.</p>
<p>
Before going through the code, let us check out the Azure AD App
registration which would be used to authenticate to the Graph API</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-Zwlnvr-4ovk/YZKX-Dkt15I/AAAAAAAAG8s/4IS02ZK_KtM5nsHDcgKmM0ICeXEi8tZNwCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="788" data-original-width="1314" height="384" src="https://lh3.googleusercontent.com/-Zwlnvr-4ovk/YZKX-Dkt15I/AAAAAAAAG8s/4IS02ZK_KtM5nsHDcgKmM0ICeXEi8tZNwCLcBGAsYHQ/w640-h384/image.png" width="640" /></a>
</div><div class="separator" style="clear: both; text-align: left;"><p>Since we are going to authenticate from a .NET Desktop console application, we will select Desktop as a platform and add the default redirect URIs. In addition, we will also add <b><span style="color: #38761d;">http://localhost</span></b> to the list.</p><p>The supported account types can be as per your requirements. I have selected the app to be <b><span style="color: #38761d;">multi tenant.</span></b></p></div>
<div class="separator" style="clear: both; text-align: left;">
Select the <b><span style="color: #38761d;">Allow public client flows</span></b> to <b><span style="color: #38761d;">"Yes"</span></b>: </div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-_Gnl8kCtgQA/YZKYeFUZumI/AAAAAAAAG88/vx6KACNQ7foudLDuMC6tRvJ660716e_ngCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="576" data-original-width="741" height="497" src="https://lh3.googleusercontent.com/-_Gnl8kCtgQA/YZKYeFUZumI/AAAAAAAAG88/vx6KACNQ7foudLDuMC6tRvJ660716e_ngCLcBGAsYHQ/w640-h497/image.png" width="640" /></a>
</div>
<br />Select the needed scopes:
<p></p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-jlbA2-7Jpm0/YZKYH3xmr7I/AAAAAAAAG8w/amcPCpXccFY20NWF4JSDfn8Cpek1S0vrACLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="562" data-original-width="1325" height="272" src="https://lh3.googleusercontent.com/-jlbA2-7Jpm0/YZKYH3xmr7I/AAAAAAAAG8w/amcPCpXccFY20NWF4JSDfn8Cpek1S0vrACLcBGAsYHQ/w640-h272/image.png" width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
Once all of this in place, we can use the following code to open a browser
window and authenticate to the Microsoft Graph once the user enters their
credentials:
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<script src="https://gist.github.com/vman/b636205d1cdfa9d2c86dc90d70179851.js"></script>
If this is the first time logging into this tenant, you will need to grant permissions to the app:
<p></p>
<div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-yw9_N2Wa-bw/YZNSXH7AwuI/AAAAAAAAG9I/E_XmxBJ7jXUeKg3NEPTy4OqL2L37CsGXgCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="642" data-original-width="543" height="400" src="https://lh3.googleusercontent.com/-yw9_N2Wa-bw/YZNSXH7AwuI/AAAAAAAAG9I/E_XmxBJ7jXUeKg3NEPTy4OqL2L37CsGXgCLcBGAsYHQ/w338-h400/image.png" width="338" /></a></div><br /></div>Once the authentication happens, you will see a similar message in the browser:</div><div><br /></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-PDFlWMBrtF8/YZNWeL7nLzI/AAAAAAAAG9k/ddOqg1DB7k0OAZhm0cp-XDtX77ocIDYUwCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="208" data-original-width="784" height="170" src="https://lh3.googleusercontent.com/-PDFlWMBrtF8/YZNWeL7nLzI/AAAAAAAAG9k/ddOqg1DB7k0OAZhm0cp-XDtX77ocIDYUwCLcBGAsYHQ/w640-h170/image.png" width="640" /></a></div></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div>and our console window logs the current access token along with the expiry time and also uses the Graph SDK to get the display name of the current user:</div><div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-x6nI5YQtcq4/YZNVAjy83II/AAAAAAAAG9c/VBuTTCmXTv4BF7lZhpUTGYkph5JEALqAgCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="225" data-original-width="476" height="189" src="https://lh3.googleusercontent.com/-x6nI5YQtcq4/YZNVAjy83II/AAAAAAAAG9c/VBuTTCmXTv4BF7lZhpUTGYkph5JEALqAgCLcBGAsYHQ/w400-h189/image.png" width="400" /></a></div><br /><div style="text-align: left;">Hope that helps!</div></div></div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-286112528488546782021-11-09T06:13:00.005+00:002021-11-13T13:24:24.499+00:00Building a Microsoft Teams Bot: Deep linking to a Teams message from an Adaptive Card button<p>
This is the second article in my "Building a Microsoft Teams Bot" series. In
this series, I am planning to write down some interesting things I came across
while creating a Microsoft Teams Bot app which is now available on
AppSource: <a href="https://appsource.microsoft.com/en-us/product/office/WA200002297" target="_blank"><span style="color: #2b00fe;">https://appsource.microsoft.com/en-us/product/office/WA200002297</span></a>
</p>
<p>
Click here to see the previous article in the series: <a href="https://www.vrdmn.com/2021/01/building-microsoft-teams-bot-for.html" target="_blank"><span style="color: #2b00fe;">Building a Microsoft Teams Bot: Posting an Adaptive Card carousel as a
welcome message</span></a>
</p>
<p>
Todays article is around how to create deep links to teams messages from
Adaptive cards. This can be useful in scenarios when you want to send users to
a specific Teams chat message when they click on an Adaptive Card button:
</p>
<div style="text-align: center;">
<a href="https://1.bp.blogspot.com/-xGvYqcJrxk4/YYoO4uy6VaI/AAAAAAAAG7o/_daDISf5RWoBRXROsviUeSoJJ5HBvLNZgCLcBGAsYHQ/s1009/Animation.gif"><img border="0" data-original-height="412" data-original-width="1009" height="261" src="https://1.bp.blogspot.com/-xGvYqcJrxk4/YYoO4uy6VaI/AAAAAAAAG7o/_daDISf5RWoBRXROsviUeSoJJ5HBvLNZgCLcBGAsYHQ/w640-h261/Animation.gif" width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<div class="separator" style="clear: both; text-align: left;">
<span style="text-align: left;">If you are building the deep link manually, it can be grabbed from the
"Copy link" button from the ellipsis menu in a Teams message:</span>
</div>
<div class="separator" style="clear: both; text-align: left;">
<span style="text-align: left;"><br /></span>
</div>
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-UjoFt62izSs/YYkwj77XSdI/AAAAAAAAG7g/tgkX2vdv3CAu0_twXhWVeoovOUtGzLcHACLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="581" data-original-width="1076" height="346" src="https://lh3.googleusercontent.com/-UjoFt62izSs/YYkwj77XSdI/AAAAAAAAG7g/tgkX2vdv3CAu0_twXhWVeoovOUtGzLcHACLcBGAsYHQ/w640-h346/image.png" width="640" /></a>
</div>
<br />Deep links to personal chats are in a different format compared to channel
messages.
<div>
<div class="separator" style="clear: both; text-align: left;">
For Teams messages, the deep link format is:
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b><span style="color: #38761d;">https://teams.microsoft.com/l/message/</span><span style="color: red;">{ChannelId}</span><span style="color: #38761d;">/</span><span style="color: red;">{messageId}</span></b>
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
For personal chats, the deep link format is:
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b><span style="color: #38761d;">https://teams.microsoft.com/l/message/19:</span><span style="color: red;">{userAadObjectId}</span><span style="color: #38761d;">_</span><span style="color: red;">{botId}</span><span style="color: #38761d;">@unq.gbl.spaces/</span><span style="color: red;">{messageId}</span><span style="color: #38761d;">?context=%7B%22contextType%22:%22chat%22%7D</span></b>
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
For bots and messaging extensions, this deep link can be built from parts of
the payload sent to the Bot from the Teams platform when the bot is invoked:
</div>
<div class="separator" style="clear: both; text-align: left;"><br /></div>
<div class="separator" style="clear: both; text-align: left;"></div>
<script src="https://gist.github.com/vman/805d6b5ca538a95c72bf8b6afe39aef3.js"></script>
Once you have the desired deep link, the next step is to assign it as the URI
to an Adaptive Card button:
<br />
<script src="https://gist.github.com/vman/203e715767a7a26fe7520fa56830007f.js"></script>
</div>
<div><br /></div>
<div>
And that's it! Now you can easily add buttons to your adaptive cards which
take the user to specific messages in Teams personal chats or channels. </div><div><br /></div><div>Hope
this was helpful!</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-18064791194322184302021-11-01T07:06:00.003+00:002021-11-01T07:08:57.272+00:00Working with MSAL and multiple Azure AD accounts in a React SPA<p>I came across an interesting scenario recently: I was working with a React SPA which used Azure AD for authenticating users, and it needed to work with multiple accounts logged in simultaneously. Specifically, we were building an Azure AD
multi tenant application which needed to login to multiple M365 and Azure
tenants and allow the user to manage all tenants at the same time.</p>
<p>
The good thing was that MSAL v2 does support working with multiple accounts at the same time.
So in this post, let's see how we are able to do that in a React SPA with MSAL js.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-eMjQqKhTqF8/YX627asPqKI/AAAAAAAAG6c/uolNdqk9zhootflkCa_bt85wP7Rlg_LSgCLcBGAsYHQ/s1244/Animation3.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="718" data-original-width="1244" height="370" src="https://1.bp.blogspot.com/-eMjQqKhTqF8/YX627asPqKI/AAAAAAAAG6c/uolNdqk9zhootflkCa_bt85wP7Rlg_LSgCLcBGAsYHQ/w640-h370/Animation3.gif" width="640" /></a></div>
<p>
Before looking at the code, we would need to create a multi tenant Azure AD
app which would be used to sign in to the different tenants. Step by step
instructions can be found here: <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa#register-your-application" target="_blank"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa#register-your-application</span></a>
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-xq_vtHcGk4c/YX46-xCCmmI/AAAAAAAAG54/hLKIEyzFZyoUM-O0iwl1a3NmS3XOnhjDQCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="876" data-original-width="1293" height="434" src="https://lh3.googleusercontent.com/-xq_vtHcGk4c/YX46-xCCmmI/AAAAAAAAG54/hLKIEyzFZyoUM-O0iwl1a3NmS3XOnhjDQCLcBGAsYHQ/w640-h434/image.png" width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: left;">
Once this is in place, we can start looking at the code itself. I have take
the MSAL React tutorial as the starting point for this code and modified it to work
with multiple accounts. If you want to build it from scratch, this would be a
good starting point: <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react" target="_blank"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react</span></a>
</div>
<br />The very first thing we would need is to setup the configuration for our
app:
<div>
<script src="https://gist.github.com/vman/e4e576df02989afc8357524ce7ccead7.js"></script>
</div>
<div>
You will notice the authority is set to the <b><span style="color: #38761d;">/organizations</span></b> end point. This is
because our app is a multi-tenant app which would be used to login to
different tenants.
</div>
<div><br /></div>
<div>
With the config, we will now initiate a <b><span style="color: #38761d;">PublicClientApplication</span></b> in the
<b><span style="color: #38761d;">index.tsx</span></b> file:
</div>
<script src="https://gist.github.com/vman/ca85e8ae2ac486c1e88561b4958d7f5c.js"></script>
<div><br /></div>
Now lets get to the most important <b><span style="color: #38761d;">App.tsx</span></b> file:
<div><br /></div>
<script src="https://gist.github.com/vman/1d739323b4f23fbca8487a9107834820.js"></script>
<div>
There are multiple things happening here. Let's unpack them one by one.
</div>
<div><br /></div>
<div>
First, we are using the MSAL react <b><span style="color: #38761d;">useMsalAuthentication</span></b> hook to setup
the authentication and get the <b><span style="color: #38761d;">login</span></b> method which we will use later.
</div>
<div><br /></div>
<div>
What is also important is the <b><span style="color: #38761d;">prompt: 'select_account'</span></b> property in
the request which would help us login with a new account when we are already
signed in with one account.
</div><div><br /></div><div><span style="background-color: white;">The <b><span style="color: #38761d;">AuthenticatedTemplate</span></b> and <b><span style="color: #38761d;">UnauthenticatedTemplate</span></b> MSAL react components help us display different views when at least one user is logged in or no user is logged in respectively.</span></div>
<div><br /></div>
<div>Next, lets look at the <b><span style="color: #38761d;">ProfileContent.tsx</span></b> component:</div>
<div><br /></div>
<script src="https://gist.github.com/vman/efccb3765a51483da32e2d3f8550bcc4.js"></script>
Based on the homeId of passed in to this component as a property, we are using the <b><span style="color: #38761d;">PublicClientApplication.acquireTokenSilent </span></b>method to first get the access token of the relevant user. <div><br /></div><div>Once the accessToken is fetched, we are making a call to the Microsoft Graph to get the basic details of the user. We are using the <b><span style="color: #38761d;">callMsGraph</span></b> function for this.</div><div><br /></div><div>The <b><span style="color: #38761d;">ProfileData</span></b> component takes in all properties fetched from the graph and displays it.</div><div><br /></div><div>The handleLogout function uses the <b><span style="color: #38761d;">PublicClientApplication.logoutRedirect</span></b> function to log out the specific user.</div><div><br /></div><div>So after everything is in place, we would be able to work with multiple users logged in simultaneously at the same time.</div><div><br /></div><div>Hope this helps! </div><div><br /></div><div>As always, the code for this post can be found on GitHub: <b><span style="color: #0b5394;"><a href="https://github.com/vman/ts-msal-react-tutorial" target="_blank">https://github.com/vman/ts-msal-react-tutorial</a></span></b></div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-62434894593541244152021-07-12T14:48:00.001+01:002021-07-12T14:49:40.895+01:00Working with Adaptive Card Universal Actions in a Microsoft Teams Bot<p>
Universal Actions for Adaptive cards are a mechanism to handle user
interactions uniformly no matter where the user is accessing the Adaptive Card
from. It allows Bot developers to send the same Adaptive Cards to Microsoft
Teams, Outlook etc. without having to write redundant client specific code. As the Microsoft
docs state:</p>
<p>
<i>Universal Actions for Adaptive Cards evolved from developer feedback that
even though layout and rendering for Adaptive Cards was universal, action
handling was not. Even if a developer wanted to send the same card to
different places, they have to handle actions differently. Universal Actions
for Adaptive Cards brings the bot as the common backend for handling
actions. </i>
</p>
<p>
<a href="https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/universal-actions-for-adaptive-cards/overview?tabs=mobile"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/universal-actions-for-adaptive-cards/overview?tabs=mobile</span></a>
</p>
<p>
In addition to these user interactions, there are couple of really useful
features delivered as part of Universal Actions. They are "User Specific
Views" and "Up to date cards"
</p>
<h3 style="text-align: left;"><span style="color: #38761d;">User specific views:</span></h3><p>
By using user specific views, different users see different views on the card
depending on their identity and the actions they have taken. This was not the
case earlier when all users who viewed an Adaptive Card posted in a Teams
channel saw the same exact card. Lets see a quick example:
</p>
<p>
<span style="background-color: white;"><script src="https://gist.github.com/vman/8721fd056271550a8db94f2490d859c8.js"></script></span>
</p>
<p>
<span style="background-color: white;">First lets talk about the <span style="color: red;"><b>refresh</b></span> property. The property contains two
important values: <span style="color: red;"><b>action</b></span> and <b><span style="color: red;">userIds</span></b>. When an adaptive card containing a
refresh property will load, first the Teams platform will check if the
current user viewing the Adaptive Card is present in the userIds property.
If they are, then an action will be sent to the bot containing the verb
mentioned in the <span style="color: red;"><b>verb</b></span> property. We will have to write Bot Framework code which handles
this call from the Teams platform. As a response to this call, we can return
a new adaptive card which will only be visible to the current
user. </span>
</p>
<p>
All other users viewing the Adaptive card who are not part of the userIds property will keep seeing a
shared common view of the base card. </p>
<h3 style="text-align: left;"><span style="color: #38761d;">Up to date cards:</span></h3><p>
With up to date cards, we can use the Bot Framework message update
functionality to update the user specific views in adaptive cards on the fly. This is so that the cards are updated to their latest state without the user having to reload the card. </p>
<p>
<span style="background-color: white;"><script src="https://gist.github.com/vman/5d29ee42ebab74231703b31b739e015c.js"></script></span>
</p>
<p>Now that we have covered the different moving parts, let's see how we can put
all of this together in a code sample for a approving an asset in a Teams
channel:</p>
<h3 style="text-align: left;"><span style="color: #38761d;">1) A user starts the approval process by sending a command to the bot:</span></h3>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-kpkcXZEx7VE/YOowtdMeEzI/AAAAAAAAG10/bZo39plvocE-IDaM_tUj0v4kyonDSkyvwCLcBGAsYHQ/request.gif" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="350" data-original-width="760" src="https://lh3.googleusercontent.com/-kpkcXZEx7VE/YOowtdMeEzI/AAAAAAAAG10/bZo39plvocE-IDaM_tUj0v4kyonDSkyvwCLcBGAsYHQ/s16000/request.gif" /></a>
</div>
<br /><br />
<p></p>
<p>
<script src="https://gist.github.com/vman/57659fb04928b7bff34f18aa4f0b926e.js"></script>
</p>
<p>The user starting the approval request is the "Owner" of this asset. When the owner sends an approval request, an adaptive card with a "Approve" button will be shown to everyone in the Team who is
not the owner. Where as, the owner will see a view on the card which contains a list of users who have approved the request.</p>
<p>
<script src="https://gist.github.com/vman/d865e9ea9052b4478536f2733ad05671.js"></script>
</p>
<h3 style="text-align: left;"><span style="color: #38761d;">2) Approving the asset and refreshing the Adaptive Card with latest state: </span></h3><p>Any user in the team can click on the approve button to approve the asset. Once they approve, they will be shown a
different card.
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-QbulA_PzE1Q/YOowyOYFWaI/AAAAAAAAG14/iJItScY63bo-5VPOZNtMMHL6vjLdKR-YwCLcBGAsYHQ/approve.gif" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="374" data-original-width="901" src="https://lh3.googleusercontent.com/-QbulA_PzE1Q/YOowyOYFWaI/AAAAAAAAG14/iJItScY63bo-5VPOZNtMMHL6vjLdKR-YwCLcBGAsYHQ/s16000/approve.gif" /></a>
</div>
<br />
<p></p>
<p>
Owner will always see who approved the card. This will be kept up to date using
the message edit mechanism without the need to manually reload the card.
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-arsffIwn-hc/YOoxE_nlBWI/AAAAAAAAG2E/UkEpXsXd0TUI9cUbcCUN6LBoL9i988uXgCLcBGAsYHQ/ownersuccess.gif" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="311" data-original-width="712" src="https://lh3.googleusercontent.com/-arsffIwn-hc/YOoxE_nlBWI/AAAAAAAAG2E/UkEpXsXd0TUI9cUbcCUN6LBoL9i988uXgCLcBGAsYHQ/s16000/ownersuccess.gif" /></a>
</div>
<div>
<br />
<div>
<script src="https://gist.github.com/vman/e2c1560e2265fc536c1c086547c3f3cc.js"></script>
<p>Now we come to the crux of the blog post. Whenever a user will click on the "Approve" button, or and Adaptive Card will load which contains a refresh property with the current user's userId, an <span style="color: red; font-weight: bold;">adaptiveCard/action</span><span> request will be sent to our bot. The request will contain information on the action such as the verb and the context in which the action occurred. </span></p><p><span>Out bot framework code will have to respond with the correct card depending on the action. </span></p><p><span>In the above code, when the <b><span style="color: red;">approveClicked</span></b> action occurs, we add the user approving the asset to our persistent storage and return a card to them thanking them for the approval.</span></p><p>When the <b><span style="color: red;">refreshCard</span></b> action occurs, it means that a user listed in the userIds property of a card is trying to view the card. So based on the identity of the user, we will return the correct card. This is used to show the owner of the card a list of users who have approved it.</p><p>Hope you found the post useful!</p><p><span style="font-size: medium;">Full code sample of this blog post available on GitHub: <b><a href="https://github.com/vman/Universal.Actions">https://github.com/vman/Universal.Actions</a></b></span></p>
</div>
</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com3tag:blogger.com,1999:blog-4455058894110083658.post-2665560752614229032021-01-18T10:39:00.001+00:002021-11-06T13:49:27.041+00:00Building a Microsoft Teams Bot: Posting an Adaptive Card carousel as a welcome message<div>
In November 2020, I was happy to release my side project "Snooze Bot" as a
free app on the Microsoft Teams store:
<a href="https://appsource.microsoft.com/en-us/product/office/WA200002297"><span style="color: #2b00fe;">https://appsource.microsoft.com/en-us/product/office/WA200002297</span></a>
</div>
<div><br /></div>
<div>
I had been working on it for a few weeks. The fact that all of us were under
lockdown gave me some extra time in the evenings and weekends to focus on
learning the Microsoft Teams platform and create an app on it which addressed
a gap which I noticed in my day to day use.
</div>
<div><br /></div>
<div>
We all get a lot of Teams messages daily and need a way to manage them or come
back to them at a later time. Snooze bot helps us do exactly that. It lets us
Snooze message which we want to deal with later. When a message is snoozed, we
get an option to select the duration after which Snooze Bot should remind us
about the message. When the time arrives, the bot will send you personal
message in teams reminding about the snoozed message.
</div>
<div><br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-awwdSk_5Gk4/YAMOdAvlsBI/AAAAAAAAGu0/odEBHdijxi4H2n45LLgvUOhAIKXtLfcfACLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="689" data-original-width="1225" height="180" src="https://lh3.googleusercontent.com/-awwdSk_5Gk4/YAMOdAvlsBI/AAAAAAAAGu0/odEBHdijxi4H2n45LLgvUOhAIKXtLfcfACLcBGAsYHQ/image.png" width="320" /></a>
</div>
<br />
</div>
<div>
If you haven't checked out Snooze Bot yet, feel free to install it and give it
a try. I am happy to hear any feedback and potential improvements.
</div>
<div><br /></div>
<div>
One of my goals when creating the app was to learn about the Microsoft Teams
developer platform and also blog about the interesting things I came across.
So in this series of posts, let's outline some Microsoft Teams development
concepts which I found really useful. The first one being posting an Adaptive
Card carousel as a welcome message when the bot is added by the user.
</div>
<div><br /></div>
<div>
It's always recommended as a good practice to send a welcome message when the
user adds the bot. According to the Microsoft docs:
</div>
<div><br /></div>
<div>
<i>In personal contexts, welcome messages set your bot's tone. The message
includes a greeting, what the bot can do, and some suggestions for how to
interact (for example, “Try asking me about …”). If possible, these
suggestions should return stored responses without having to sign in.</i>
</div>
<div>
<a href="https://docs.microsoft.com/en-us/microsoftteams/platform/bots/design/bots#introduce-a-bot"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/microsoftteams/platform/bots/design/bots#introduce-a-bot</span></a>
</div>
<div><br /></div>
<div>Also, sending the welcome message one of the requirements before the app is accepted in AppSource by the validation team.</div><div><br /></div><div>
So we can send a simple chat message from the bot to the user as a welcome
message. So why go for an Adaptive Card carousel? This is because adding too
much information in a single message can get overwhelming for the user and
they might be tempted to just skip it. Also if your bot has a
lot of functionality you need a way to efficiently present that information to
the user. This is where carousels created by Adaptive Cards some into
play: </div>
<div><div class="separator" style="clear: both; text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-v4CZ6b-2bMU/YAMOIgPtC2I/AAAAAAAAGuo/sgcbE6gwNeMikbXIL6IQdkVBSwEhV4xsACLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="566" data-original-width="1148" height="316" src="https://lh3.googleusercontent.com/-v4CZ6b-2bMU/YAMOIgPtC2I/AAAAAAAAGuo/sgcbE6gwNeMikbXIL6IQdkVBSwEhV4xsACLcBGAsYHQ/w640-h316/image.png" width="640" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;">
<span style="text-align: left;"><br /></span>
</div>
<div>So let's have a look at the code which helps us send the welcome message in Snooze Bot</div></div><div><br /></div><h3 style="text-align: left;"><span style="color: #38761d;">The Adaptive Card json:</span></h3><div><br /></div><div>First, we need to define the Adaptive cards which will show up in the welcome message. I am storing mine as json files in my solution. The cards contain helpful text and also links to images which show the functionality of Snooze Bot</div><div><br /></div>
<script src="https://gist.github.com/vman/abea0a00442ac110ac65e50623e33c6a.js"></script>
<script src="https://gist.github.com/vman/c9f00d69735429669da0ce251fe03784.js"></script>
<h3 style="text-align: left;"><span style="color: #38761d;">
The Bot:</span></h3><div><br /></div>
Next, the actual bot code itself. Since I am using .NET Core for this bot we will need the Adaptive cards nuget package:<div><br /></div><div><a href="https://www.nuget.org/packages/AdaptiveCards/">NuGet Gallery | AdaptiveCards 2.4.0</a></div>
<div><br /></div>
And here is the code where we do the following things:
<div><br /></div>
1) Capture the OnMembersAddedAsync event from the Bot Framework<div>2) Get the Adaptive cards from the json files </div><div>3) Insert the adaptive cards into a Bot Framework carousel and send it to the user
<script src="https://gist.github.com/vman/bfef5644c95434d32b1f765b415a35da.js"></script></div><div><br /></div>
And that's it. Whenever a user will download and install the app, the welcome message will be sent to them introducing your bot and it's funtionality.
Hope you found the post useful!Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-88450627569913054752021-01-05T10:19:00.000+00:002021-01-05T10:19:40.154+00:00Microsoft 365 multi-tenant apps: Working with application permissions in Microsoft Graph<p>
Creating multi-tenant (SaaS) apps in Microsoft 365 has been possible for a
while now. Azure AD multi tenant apps allow us to host our custom applications
in an Azure AD/M365 "home" tenant while enabling the apps to also have access
to resources hosted in other tenants. To know more about multi-tenant apps,
head over to the Microsoft docs: <a href="https://docs.microsoft.com/en-in/azure/active-directory/develop/single-and-multi-tenant-apps">https://docs.microsoft.com/en-in/azure/active-directory/develop/single-and-multi-tenant-apps</a>
</p>
<p>
Hosting applications in a home tenant as SaaS has a lot of advantages
particularly for ISVs when it comes to product based applications. Users are able to consume the apps directly by signing into
them instead of the conventional way of an admin having to deploy the product to the customer tenant first. It makes life easy for the admins as well as they don't have to go
through complex deployment scripts and instructions. Moreover, after the
application is deployed, new features and bug fixes can be rolled out to the
application "on the fly" as opposed to releasing feature packs and hotfixes
which again have to be installed manually.
</p>
<p>
So in this post, we are going to have a look at using the
Microsoft Graph API in such apps configured to be multi tenant.
</p>
<p>
(Multi tenant apps also allow users with personal Microsoft accounts to sign
into them but that is a topic for another day! Also, in this post we will only
focus on the application permissions i.e. granting permissions to applications
without a user context)
</p>
<h3 style="text-align: left;">
<span style="color: #38761d;">Configure an app to be a multi-tenant in the home tenant's Azure AD</span>
</h3>
<p>
1) When creating a multitenant app registration, make sure that the "Accounts
in any organizational directory" is selected. Also, we need to add a redirect
url as this will be the url the admin will be redirected to after
successfully granting consent to our application. Ideally, this would be the
landing page of your application but in the screenshot I am just using the AAD
home as an example:
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-T_zBZweGMIE/X-DTv2lULBI/AAAAAAAAGr0/wfyZ3S6gJOwAHlR9DJfwwtgi53YsghogwCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="924" data-original-width="1056" height="560" src="https://lh3.googleusercontent.com/-T_zBZweGMIE/X-DTv2lULBI/AAAAAAAAGr0/wfyZ3S6gJOwAHlR9DJfwwtgi53YsghogwCLcBGAsYHQ/w640-h560/image.png" width="640" /></a>
</div>
<br /><br />
</div>
<p></p>
<p>
2) Assign required permissions. In this case, we are going to demo the code to
get all the Microsoft 365 Groups on the tenant and also the root SharePoint Online site, so
selecting the relevant permissions here:
</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-h5s0V7PjuL0/X-DUODt7-eI/AAAAAAAAGr8/xz0g9ZeI6v8Swp7Qe72CW4-phmAnkX8VQCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="918" data-original-width="1516" height="388" src="https://lh3.googleusercontent.com/-h5s0V7PjuL0/X-DUODt7-eI/AAAAAAAAGr8/xz0g9ZeI6v8Swp7Qe72CW4-phmAnkX8VQCLcBGAsYHQ/w640-h388/image.png" width="640" /></a>
</div>
<br /><br />
<p></p>
<p>3) Create a client secret and record it along with the client id. We will need this later in our code.</p>
<p></p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://lh3.googleusercontent.com/-x8Thdt34TmE/X-DU8d5eUnI/AAAAAAAAGsI/WxMDDw3x1OQPmLyhnS4AU3c6n9oZnnm3gCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="816" data-original-width="1527" height="342" src="https://lh3.googleusercontent.com/-x8Thdt34TmE/X-DU8d5eUnI/AAAAAAAAGsI/WxMDDw3x1OQPmLyhnS4AU3c6n9oZnnm3gCLcBGAsYHQ/w640-h342/image.png" width="640" /></a>
</div>
<br /><br />
<p></p>
<h3 style="text-align: left;">
<span style="color: #38761d;">Granting consent to a multi tenant app in other "consumer" tenant</span>
</h3>
<p>
Next, let's have a look at how the multi tenant app hosted in it's home tenant
can be granted permission to access resources in other tenants.
</p>
<p>
What we will have to do is to construct a url for admin consent which would be
unique to our application. An Azure AD admin of the other tenant will need to
navigate to the url and then consent to granting the permissions to our app on
the tenant. The Azure AD url will have the following structure:
</p>
<p><script src="https://gist.github.com/vman/00c5434748eba3d88d4dc675ff8f5cc8.js"></script></p>
In the link above, replace the client id with the client id of your multi tenant Azure AD app. Also, notice that we are using the<b><span style="color: red;"> /.default</span></b> static scope which means that all permissions configured in the app will be requested for consent. <div><br /></div><div>When the admin navigates to this url, they will see the consent prompt:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-RFvu2ndLBhs/X-IPYR6NWoI/AAAAAAAAGsY/w8QC8MPZ2KEDKmNUDQetQztCeHqyy2sHwCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="733" data-original-width="531" height="640" src="https://lh3.googleusercontent.com/-RFvu2ndLBhs/X-IPYR6NWoI/AAAAAAAAGsY/w8QC8MPZ2KEDKmNUDQetQztCeHqyy2sHwCLcBGAsYHQ/w464-h640/image.png" width="464" /></a></div><br />Once the consent is granted, the multitenant app will have permissions to access the resources on the other tenant. This can be checked by going to:</div><div><br /></div><div>Azure Active Directory > Enterprise Applications > All applications and searching for our app there.</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-c7arxu79IkE/X-IQHotU_AI/AAAAAAAAGsg/v5jMYNrhvdMEtTjcDYPSFfIgV8rnl434wCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="463" data-original-width="1411" height="210" src="https://lh3.googleusercontent.com/-c7arxu79IkE/X-IQHotU_AI/AAAAAAAAGsg/v5jMYNrhvdMEtTjcDYPSFfIgV8rnl434wCLcBGAsYHQ/w640-h210/image.png" width="640" /></a></div><br />This confirms that the multi tenant app has permissions on this tenant. Also this process can be repeated on any number of Azure AD/M365 tenants.</div><div><br /><h3 style="text-align: left;">
<span style="color: #38761d;">Use the Microsoft Graph API to get Microsoft 365 data from the consumer
tenant</span>
</h3>
<p>
With everything setup and also the admin consent granted, let's have a look at
the Microsoft Graph code to get data from the consumer tenant.
</p>
<p>
In this code, I am using the .NET SDK for Microsoft Graph found on nuget here:
</p>
<p>
<a href="https://www.nuget.org/packages/Microsoft.Graph/3.21.0">Microsoft.Graph</a>
</p>
<p>And the new preview version of Microsoft.Graph.Auth found here:</p>
<p>
<a href="https://www.nuget.org/packages/Microsoft.Graph.Auth/1.0.0-preview.6">Microsoft.Graph.Auth</a>
</p>
<p>
And finally here is the code to get all the Microsoft 365 Groups and the
SharePoint root site url of the consumer tenant. For the sake of simplicity, I am using a .NET Core console application:</p>
<script src="https://gist.github.com/vman/cc43f7fac4949be23d9684824a95a214.js"></script>
And we are able to get the data from the consumer tenant back:
</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://lh3.googleusercontent.com/-_1mQYnS1TB4/X-IRkvswz8I/AAAAAAAAGss/ouAoHTI_CjQXziDmpA_5e6shV1UmbCwNgCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="509" data-original-width="978" height="334" src="https://lh3.googleusercontent.com/-_1mQYnS1TB4/X-IRkvswz8I/AAAAAAAAGss/ouAoHTI_CjQXziDmpA_5e6shV1UmbCwNgCLcBGAsYHQ/w640-h334/image.png" width="640" /></a></div><br />Thanks for reading! Hope this helps.</div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0tag:blogger.com,1999:blog-4455058894110083658.post-45876528428492760232020-09-04T09:34:00.004+01:002020-09-04T09:54:36.514+01:00Microsoft Teams messaging extensions using SPFx: Getting the message data with Microsoft Graph<p>
With SPFx 1.11, one of the things possible now is that SharePoint Framework web
parts can be exposed as Microsoft Teams messaging extensions. So what are
messaging extensions exactly? According to the Teams docs:
</p>
<p>
<i>"Messaging extensions allow users to interact with your web service through
buttons and forms in the Microsoft Teams client. They can search, or
initiate actions, in an external system from the compose message area, the
command box, or directly from a message. You can then send the results of
that interaction back to the Microsoft Teams client, typically in the form
of a richly formatted card."</i>
</p>
<p>
<a href="https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/what-are-messaging-extensions" target="_blank"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/what-are-messaging-extensions</span></a>
</p>
<p>
As a Microsoft 365 Developer, messaging extensions are a great
way to invoke custom code right in the Teams client. This
opens up the possibility of users interacting with your application right in the context of their conversations without
having to leave Teams.</p>
<p>
The SPFx docs give a nice overview of how to setup web parts so that they are
exposed as <span style="color: red;">compose extensions.</span> This enables the custom SPFx webpart to
be invoked from the <span style="color: red;">"Compose new message"</span> box in Teams: <span style="color: #2b00fe;"><a href="https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-expose-webparts-teams#expose-web-part-as-microsoft-teams-messaging-extension" target="_blank"><span style="color: #2b00fe;">https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-expose-webparts-teams#expose-web-part-as-microsoft-teams-messaging-extension</span></a>
</span></p>
<p>In this post, we are going to be talking about SPFx webparts being hosted
in task modules which show up in <b><span style="color: red;">"message actions" i.e. invoking custom code
on messages which are already posted in Teams.</span></b> This could be either in channels or in personal or group chats.</p>
<div class="separator" style="clear: both; text-align: center;"><span style="font-size: x-small;">(click to zoom)</span></div><div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-NPKYIG0oAB4/X0pncIyatnI/AAAAAAAAGlQ/qnOCa_Fe6Uc0IaXt95cDAXejpQrvFESvgCLcBGAsYHQ/s1157/teamsmessageaction.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="697" data-original-width="1157" src="https://1.bp.blogspot.com/-NPKYIG0oAB4/X0pncIyatnI/AAAAAAAAGlQ/qnOCa_Fe6Uc0IaXt95cDAXejpQrvFESvgCLcBGAsYHQ/s640/teamsmessageaction.gif" width="640" /></a>
</div>
<br />
<p>
Now behind the scenes, when a message action is invoked on a message,
we want to get the message context passed to our SharePoint Framework web part. By message context, I mean properties like teams id and channel id in which the action was invoked. If the message action was invoked in a personal chat or a group chat, then we
need to know the chat id instead. And finally, we need the data about the message itself e.g. message id,
message body, who posted the message etc. so that we can then send the information to our application right from the SPFx webpart.</p>
<p>Now if we were using the <span style="color: red;">Bot Framework</span> to power our message action (and task module), then getting
these properties is straightforward as every time the message action is
invoked, the Bot Framework sends this information to our messaging
endpoint: <a href="https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/create-task-module?tabs=json#example-fetchtask-request" target="_blank">https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/create-task-module?tabs=json#example-fetchtask-request</a>
</p>
<p>
When using SharePoint Framework however, we have to take a longer
route. When the message action would be invoked on a Teams message: Although we get the context information like team id, channel id and chat id, <b><span style="color: red;">all we will get about the message itself is just the id</span></b>. No other data about the message like the body, user etc will be available. Getting all these other details would be up to us. Let's see how we do that:</p><h3 style="text-align: left;"><span style="color: #38761d;">Teams app manifest</span></h3><div><span style="color: #38761d;"><br /></span></div><div>First, to get the SPFx powered message action working, we need to configure it in the Teams manifest.</div><div><br /></div><div>Notice that the <b><span style="color: red;">fetchTask property is set to false</span></b>. This makes the task module defined in the manifest to be displayed. (If fetchTask is set to true, the Teams will go to the Bot messaging endpoint to get the task module dynamically)</div><div><br /></div><div>Also notice that the url for a SharePoint Framework Task module is slightly different compared to a Teams tab</div><div><br /></div>
<script src="https://gist.github.com/vman/754a3fb6de059926a6ba94fd472702e1.js"></script>
<h3 style="text-align: left;"><span style="color: #38761d;">SPFx and Microsoft Graph:</span></h3>
<p>Although we won't have the message data directly provided to us in SPFx, we would have all the context information necessary to fetch the data. As part of the microsoftTeams context object, we will have the teamId, channelId, chatId and the parentMessage. We can then use these details along with the Microsoft Graph to get the message details:</p><p>Before we go through the code, make sure that the SPFx solution has the <b><span style="color: red;">Chat.Read</span></b> permissions on the <b><span style="color: red;">Microsoft Graph </span></b>configured in the <b><span style="color: red;">package-solution.json</span></b> file. This will allow us to read the Teams messages on behalf of the currently logged in user</p><p><br /></p>
<script src="https://gist.github.com/vman/163b3ff61d92759926a95940156114f0.js"></script>
And finally, here is the SPFx code to get the message details on which the message action was invoked:<div><br /><script src="https://gist.github.com/vman/437ed8709786a07fcd1d1d973cacfe3e.js"></script>
</div>
Hope you found the post useful! Here is the SPFx webpart code on GitHub: <span style="color: #2b00fe;"><a href="https://github.com/vman/spfx-teams-message-action" target="_blank">https://github.com/vman/spfx-teams-message-action</a></span><div><br /></div><div><u><b><span style="color: red;">Note:</span></b></u></div><div><u><b><span style="color: red;"><br /></span></b></u></div><div>This approach currently works with Teams messages in personal chats, group chats and top level messages in Teams channels. There seems to be a gap right now where this approach does not work for replies posted to top level teams messages. I have opened up a GitHub issue about this and will post a follow up soon. <span style="color: #2b00fe;"><a href="https://github.com/OfficeDev/microsoft-teams-library-js/issues/398" target="_blank"><span style="color: #2b00fe;">https://github.com/OfficeDev/microsoft-teams-library-js/issues/398</span></a> </span></div><div><br /></div>Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com1tag:blogger.com,1999:blog-4455058894110083658.post-75070253026686358932020-07-20T11:26:00.000+01:002020-07-20T11:26:10.784+01:00Microsoft Teams Bot Framework: Mention a user in an Adaptive CardMicrosoft Teams announced support for Adaptive Cards 1.2 recently. With
that, a nifty feature to allow mentioning users in Adaptive Cards posted in
Teams was also introduced. This allows us the ability to send a notification to the user and can
draw their attention towards the card.
<div><br /></div>
<h4 style="text-align: left;"><font color="#38761d">User gets a notification of the mention:</font></h4><div><br /></div><div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-eIuvwS0PnN4/XxMfIQLNFtI/AAAAAAAAGhk/zbpZioMLVoUBLr4ox-m3UfjTDTAxLBTtQCLcBGAsYHQ/s1017/Capture.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="1017" height="125" src="https://1.bp.blogspot.com/-eIuvwS0PnN4/XxMfIQLNFtI/AAAAAAAAGhk/zbpZioMLVoUBLr4ox-m3UfjTDTAxLBTtQCLcBGAsYHQ/w625-h125/Capture.PNG" width="625" /></a></div><div class="separator" style="clear: both; text-align: center;"><font size="1">(click to zoom)</font></div><div class="separator" style="clear: both; text-align: center;"><br /></div>
<h4 style="text-align: left;"><font color="#38761d">
Other users are able to contact the user directly from the mention in the
card:
</font></h4>
<div style="text-align: center;"><br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-8e19w8lfUek/XxMejRSa7vI/AAAAAAAAGhY/fbpDdfK29mMthtYZkpFRlIt9mIXAd-f3ACLcBGAsYHQ/s1007/Capture2.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="255" data-original-width="1007" height="158" src="https://1.bp.blogspot.com/-8e19w8lfUek/XxMejRSa7vI/AAAAAAAAGhY/fbpDdfK29mMthtYZkpFRlIt9mIXAd-f3ACLcBGAsYHQ/w625-h158/Capture2.PNG" width="625" /></a>
</div><div class="separator" style="clear: both; text-align: center;"><font size="1">(click to zoom)</font></div>
<div class="separator" style="clear: both; text-align: center;"><br /></div>
<div>
Checkout the announcement if you haven't already: <a href="https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cconnector-html#mention-support-within-adaptive-cards-v12" target="_blank"><font color="#2b00fe">https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-format?tabs=adaptive-md%2Cconnector-html#mention-support-within-adaptive-cards-v12</font></a>
</div>
<div><br /></div>
<div>
In the docs, there is a great example of the JSON we need to send to Teams
to post the card containing the mention. So in this post, lets see how we
can do this when using the Bot Framework .NET Core SDK:
</div>
<div><br /></div>
<div>For this code to work, we will need the following Nuget packages:</div>
<div><br /></div>
<div>
<a href="https://www.nuget.org/packages/Microsoft.Bot.Builder" target="_blank"><font color="#2b00fe">Microsoft.Bot.Builder</font></a>
</div>
<div><br /></div>
<div>
<a href="https://www.nuget.org/packages/AdaptiveCards/" target="_blank"><font color="#2b00fe">AdaptiveCards</font></a>
</div>
<div><br /></div>
<div>
<script src="https://gist.github.com/vman/0de2bf68e3bc737617fd8244c3adadcd.js"></script>
</div>
<div><br /></div>
<div>
Quick note: I noticed that the user mentioned is only notified when the
Adaptive card is first created. If you update the same adaptive card later
and mention the same user again, they are not notified. This is probably for
the best as the Adaptive card might be updated several times and if you got
a notification every time, it might be really annoying to the user.
</div><div><br /></div><div>Hope you found this post useful!</div>
</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com1tag:blogger.com,1999:blog-4455058894110083658.post-51523001792999038482020-06-24T09:59:00.000+01:002020-06-25T01:16:05.493+01:00Using .NET Standard CSOM and MSAL.NET for App-Only auth in SharePoint OnlineSo after long last, the .NET Standard version of SharePoint Online CSOM was
released yesterday! The official announcement can be found here: <a href="https://developer.microsoft.com/en-us/microsoft-365/blogs/net-standard-version-of-sharepoint-online-csom-apis/" target="_blank">https://developer.microsoft.com/en-us/microsoft-365/blogs/net-standard-version-of-sharepoint-online-csom-apis/</a>
<br />
<div>
<br /></div>
<div>
One of the key differences compared to the .NET Framework CSOM was that the
authentication is completely independent of CSOM library now. Previously,
there were native classes like
<span style="color: #d52c1f;">SharePointOnlineCredentials</span> which were used for
auth, but they have been removed now.
</div>
<div>
<br /></div>
<div>
Since .NET Standard CSOM now uses <span style="color: #b51200;">OAuth</span> for
authentication, it's up to the developer to get an access token and pass it
along with the call to SharePoint Online. The CSOM library does not care how
the access token was fetched.
</div>
<div>
<br /></div>
<div>
<span style="color: #0b8043;"> So in this post,</span>
<span style="color: #0b8043;">let's have a look at getting an Application authentication (aka App-Only)
access token using MSAL.NET and use it with the new .NET Standard CSOM to
get data from SharePoint Online.</span>
</div>
<div>
<br /></div>
<div>
When making app-only calls to SharePoint Online, we can either use an Azure AD
app registration (with the Client Certificate) or we can use SharePoint
App-Only authentication created via the AppRegNew.aspx and AppInv.aspx pages.
(There are other workarounds available but that would be out of scope for this
post) I go into more details about this in my previous post: <a href="https://www.vrdmn.com/2019/01/working-with-application-permissions.html" target="_blank">Working with Application Permissions (App-Only Auth) in SharePoint Online
and the Microsoft Graph</a>
</div>
<div>
<br /></div>
<div>
The recommended approach is to go with an Azure AD App Registration and the
Client Certificate approach so that is what we will be using. To do that,
first we will need to create an App Registration in the Azure AD portal and
configure it with the Certificate, SPO API permissions etc. Here is a detailed
walk-through on this in the Microsoft docs: <a href="https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread" target="_blank">https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread</a>
</div>
<div>
<br /></div>
<div>
Let's have a look at a few important bits of my Azure AD app registration:
</div>
<div>
<br /></div>
<div>
The certificate:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-NNEmlvAKe-M/XvIZ2IzQecI/AAAAAAAAGdk/GzM9thKysAgM7HFTzWmOcLc_BpzCkVy4gCK4BGAsYHg/s1351/cert.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="637" data-original-width="1351" height="295" src="https://1.bp.blogspot.com/-NNEmlvAKe-M/XvIZ2IzQecI/AAAAAAAAGdk/GzM9thKysAgM7HFTzWmOcLc_BpzCkVy4gCK4BGAsYHg/w625-h295/cert.PNG" width="625" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
The consented SharePoint permissions:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-7NaFGG--R_E/XvIZ8X1zokI/AAAAAAAAGd0/9u8JPhQzzkYn6g8dKXhdrNmjipPxVXfSgCK4BGAsYHg/s1295/permissions.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="648" data-original-width="1295" height="313" src="https://1.bp.blogspot.com/-7NaFGG--R_E/XvIZ8X1zokI/AAAAAAAAGd0/9u8JPhQzzkYn6g8dKXhdrNmjipPxVXfSgCK4BGAsYHg/w625-h313/permissions.PNG" width="625" /></a>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Once the Azure AD App Registration is configured correctly, we can start
looking at the code.
</div>
<div>
<br /></div>
<div>
We will be using a .NET Core 3.1 Console app project for this along with the
following nuget packages:
</div>
<div>
<br /></div>
<div>
1) <a href="https://www.nuget.org/packages/Microsoft.SharePointOnline.CSOM/16.1.20211.12000" target="_blank">Microsoft.SharePointOnline.CSOM v16.1.20211.12000</a>
</div>
<div>
<br /></div>
<div>
2) <a href="https://www.nuget.org/packages/Microsoft.Identity.Client/4.15.0" target="_blank">Microsoft.Identity.Client v4.15.0</a>
</div>
<div>
<br /></div>
<div>
And finally, here is the code which uses MSAL.NET to get the access token and
attaches it to the .NET Standard CSOM requests going to SharePoint:
</div>
<div>
<br /></div>
<script src="https://gist.github.com/vman/3322ef27202e637f1a799a4c2e5f8b4d.js"></script>
<div>
<br /></div>
<div>
<span style="color: #d52c1f;"><u>Note:</u></span> Make sure that you are using the right
way to access the certificate as per your scenario. Here, for demo purposes, I
have installed the certificate to my local machine and I am accessing it from
there. In production scenarios, it's recommended to store the certificate in
Azure Key Vault.
<a href="https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#using-this-principal-in-your-application-and-make-use-of-the-azure-keyvault-to-store-the-certificate-and-retrieve-it-using-an-azure-function" target="_blank">More details here</a>
</div>
<div>
<br /></div>
And when I run the code, I am able to get the title of my SharePoint site back:
<br />
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-mI90klLvibg/XvIbWwxjGjI/AAAAAAAAGeg/CQcvpXZF9YksYlEFqw7ZXolUK8Y4bJaEgCK4BGAsYHg/s979/console.PNG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="311" data-original-width="979" height="200" src="https://1.bp.blogspot.com/-mI90klLvibg/XvIbWwxjGjI/AAAAAAAAGeg/CQcvpXZF9YksYlEFqw7ZXolUK8Y4bJaEgCK4BGAsYHg/w625-h200/console.PNG" width="625" /></a>
</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
</div>
<div class="separator" style="clear: both; text-align: left;">
Hope you found this post useful! I am very glad .NET CSOM Standard is finally
available and we are able to use it .NET Core projects going forward. This is
going to make things so much easier!
</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com28tag:blogger.com,1999:blog-4455058894110083658.post-21275569436326974542020-06-22T10:27:00.002+01:002020-06-22T10:42:31.416+01:00Using the Microsoft Search API (preview) to query SharePoint contentThe new Microsoft Search API (preview) has been available in the Graph beta
endpoint for a while now. If you haven't had a chance to look at the API yet,
the docs explain it quite nicely:
<br />
<div>
<br /></div>
<div>
<i>"The Microsoft Search API provides one unified search endpoint that you can
use to query data in the Microsoft cloud - messages and events in Outlook
mailboxes, and files on OneDrive and SharePoint - that Microsoft Search
already indexes."</i><br />
<div>
<br /></div>
<div>
And it's also currently planned that Microsoft Teams search will also be
transitioned to use Microsoft Search in the future: <a href="https://twitter.com/williambaer/status/1273644094904872960" target="_blank">https://twitter.com/williambaer/status/1273644094904872960</a>
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-XRTHPrt7nR4/Xu9koskiFWI/AAAAAAAAGcY/SRKJLzVYSekWRZqd1Adyrg66-1eaghZmwCK4BGAsYHg/s191/graph-icon-1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="190" data-original-width="191" src="https://1.bp.blogspot.com/-XRTHPrt7nR4/Xu9koskiFWI/AAAAAAAAGcY/SRKJLzVYSekWRZqd1Adyrg66-1eaghZmwCK4BGAsYHg/graph-icon-1.png" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Considering everything, it looks like Microsoft Search will play an
important role in Microsoft 365 solutions going forward. Given this, I decided to check out the Graph API .NET SDK late last year to
try and search SharePoint files. But quickly stumbled on a roadblock which
did not allow the API to work with the SDK: <a href="https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet/issues/43" target="_blank">https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet/issues/43</a> </div>
<div>
<br /></div>
<div>
Fortunately, the issue was fixed recently and we are able to use the .NET
Graph SDK for testing. </div>
<div>
<br /></div>
<div>
<span style="color: #0b8043; font-size: large;">The code:</span></div>
<div>
<br /></div>
<div>
Let's see how can we search SharePoint Online content using the new Microsoft Search API:
</div>
<div>
<br /></div>
<div>
For this code to work, you will need the Microsoft.Graph.Beta nuget
package:
</div>
<div>
<a href="https://www.nuget.org/packages/Microsoft.Graph.Beta/0.19.0-preview" target="_blank">https://www.nuget.org/packages/Microsoft.Graph.Beta/0.19.0-preview</a>
</div>
<div>
<br /></div>
<div>
We are going to use the KQL sytax with the Microsoft Graph Search API
to query SharePoint Modern pages in a tenant. Once the query completes, we will display the page name and page url in the console:
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/9bb4733746fd928659dd91e23f52eea3.js"></script>
</div>
<div>
<span style="color: #0b8043; font-size: large;">Considerations:</span></div>
<div>
<br /></div>
<div>
Although it works great, there are a few considerations currently:</div>
<div>
<br /></div>
<div>
<ul style="text-align: left;">
<li>The API only works with delegated access for now i.e. with a user
context. Application permissions are not supported.
</li>
<li>When searching SharePoint Online content, we are not able to specify
fields to return in the result. Only a default set of fields can be
returned.</li>
<li>There is no custom sorting available as of now when it comes to
SharePoint content. The content is sorted by default by relevance.
</li>
</ul>
</div>
</div>
<div>
<br /></div>
<div>
Hope you found this post useful and helps you get started with the Microsoft Search API. To read up more on the Microsoft Search API in Graph, have a look here:</div>
<div>
<div>
<a href="https://docs.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-beta" target="_blank">https://docs.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-beta</a></div>
<div>
<a href="https://docs.microsoft.com/en-us/graph/search-concept-files" target="_blank">https://docs.microsoft.com/en-us/graph/search-concept-files</a>
</div>
</div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com2tag:blogger.com,1999:blog-4455058894110083658.post-43710376264517202252020-05-26T10:08:00.001+01:002020-05-26T13:04:08.666+01:00Create a custom React hook to mimic class component's setState behaviourI have been playing around with react hooks recently, and slowly wrapping my
head around how they work. Hooks are a great way to create reusable
functionality, but there are some instances where I think class components
fared better. Let's talk about one such scenario, and also how to get around it
when using hooks.<br />
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-xK79Fsy3eks/XsqtHu5GdpI/AAAAAAAAGZQ/MP5cxBbvx9EBpTdkKjloeH-w4L0OaJIDQCK4BGAsYHg/220px-React-icon.svg.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="407" data-original-width="1040" height="250" src="https://1.bp.blogspot.com/-xK79Fsy3eks/XsqtHu5GdpI/AAAAAAAAGZQ/MP5cxBbvx9EBpTdkKjloeH-w4L0OaJIDQCK4BGAsYHg/w640-h250/220px-React-icon.svg.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
When working with react state in a class component, if we wanted to update a
specific property on the state object, all we had to do was call the
<span style="color: #f57c00;"><b>setState</b></span> method and pass in an object
containing only the updated property and value. The resultant state would be
the previous state merged with the new property value.</div>
<div>
<br /></div>
<div>
It works a bit differently with hooks. When using the default
<b><span style="color: #f57c00;">useState</span></b> hook, the new object passed in
entirely replaces the previous state. So all the properties in the previous
state are overwritten. So every time you want to update a state object, the
onus is on the developer to keep track of the previous state and update the
new state based on that. Here is how this behaviour manifests in code:</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/8407f5f3b1dd6a941eb69dc9349405a6.js"></script>
</div>
<div>
As you can see in the code the value of the FirstName property is lost when we update the LastName property. </div>
<div>
<br /></div>
<div>
There is a way to set the state based on previous state by using
the functional update pattern: <a href="https://reactjs.org/docs/hooks-reference.html#functional-updates"><span style="color: #3367d6;">https://reactjs.org/docs/hooks-reference.html#functional-updates</span></a></div>
<div>
<br /></div>
<div>
But that means that every time we used to just use <span style="color: #f57c00;"><b>this.setState</b></span> in class components, we now
have to use</div>
<div>
<b><span style="color: #f57c00;"><br /></span></b>
</div>
<div>
<span style="color: #f57c00;">setState(prevState => {</span></div>
<div>
<span style="color: #f57c00;"> return {...prevState, ...updatedValues};</span>
</div>
<div>
<span style="color: #f57c00;">});</span></div>
<div>
<div>
<br /></div>
<div>
This would be additional overhead for us devs which I wanted to check if we
could avoid.
</div>
<div>
<br /></div>
<div>
Fortunately with the reusable nature of hooks, we can simply create a custom
hook which mimics the class components
<b><span style="color: #f57c00;">setState</span></b> behaviour and merges the new
state with the previous state. And we can use this custom hook any time we
want to merge the state.</div>
<div>
<br /></div>
<div>
Here is how the previous code would look when using our custom hook:
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/e72a8d790f6f76c106944d091c8d6bfd.js"></script>
</div>
<div>
<br /></div>
The only change in code is that we have replaced react's <b><span style="color: #f57c00;">useState</span></b> with our custom <b><span style="color: #f57c00;">useCustomState</span></b>. This gives us back the ability to merge state objects instead of replacing them. (I have also changed the variable names from state and setState to <b><span style="color: #f57c00;">customState</span></b> and <b><span style="color: #f57c00;">setCustomState</span></b> respectively to make it explicit that we are using the custom hook. But you can just as well keep using the state and setState names)</div>
<div>
<br /></div>
<div>
So how does our custom hook work? It's built as a wrapper on top of the
<b><span style="color: #f57c00;">useState</span></b> hook's functional update pattern. Any time a state object is passed to the
<b><span style="color: #f57c00;">setCustomState </span></b>function, it internally
uses the functional update pattern and merges the new state with the previous
state. Let's have a look at the code:
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/66ef1b1291918dd326c38424d0ef8621.js"></script>
</div>
<div>
<br /></div>
<div>
This automates the overhead of using the functional pattern. It is no longer
the the developers responsibility, and instead, is done by the custom hook.
</div>
<div>
<br />
<div>
But wait, what if there is a scenario where a new state depends on the
previous state? Our custom hook does not yet expose a way for the developer
to update the state based on the previous state. Let's fix that. We can update our hook to add a method which accepts a function. This function will receive the previous
state from react.
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/de999049f005806edde2ded49c6b6e0b.js"></script>
</div>
</div>
And consuming the new hook can be done by passing in a function updater:
<br />
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/862cc797b86e8c9b49b997d2f4f90ffc.js"></script>
Hope that helps! For the sake of completeness here is the full code for our
custom hook:
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/vman/a14f0f45733f17c33177f6d1073ee592.js"></script></div>
Full solution including the custom hook and the consuming code can be found here:
<span style="color: #3367d6;"><a href="https://github.com/vman/spfx-react-hooks-customstate" target="_blank">https://github.com/vman/spfx-react-hooks-customstate</a></span><br />
<br />
This post generated some good discussion on twitter with <a href="https://twitter.com/yp_code" target="_blank"><span style="color: blue;">Yannick Plenevaux</span></a> regarding the ideal cases when to use this approach as opposed to other approaches of state management like using the useState or useReducer hooks. Have a look here: <a href="https://twitter.com/yp_code/status/1265233820292460544" target="_blank"><span style="color: blue;">https://twitter.com/yp_code/status/1265244244077416448</span></a> </div>
Vardhaman Deshpandehttp://www.blogger.com/profile/17919845281919756108noreply@blogger.com0