Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building Microsoft Teams Apps with Serverless Python Part 3: Building Tabs with Adaptive Cards Using Python and Azure Functions

0.00/5 (No votes)
19 Jan 2022 1  
This article should demonstrate how to create a serverless Azure Functions web app in Python.
Here we discuss how to integrate Microsoft Teams with Azure serverless functions to create an Adaptive Cards app.

In the previous two articles of this three-part series, we explored how to use Python and Azure Functions to build a Microsoft Teams Personal Tab app and a Channel or Group app with single sign-on (SSO). Here, we’ll demonstrate how to create an Azure Function app in Python that replicates the functionality of the Tabs with Adaptive Cards sample C# app on GitHub.

 

Microsoft provides an excellent overview of Adaptive Cards for Microsoft Teams. Adaptive Cards are JSON-based snippets of interactive content embedded in various hosting applications, including Microsoft Teams and Outlook.  

The beauty of Adaptive Cards is their simplicity. Instead of embedding an external web application inside an app’s iframe, you can develop a bot that responds to activities, provides pure JSON with static content and actions, and lets the host control the styling and security.

If you know some Python and have your free Azure account, you can understand and implement the Adaptive Cards app step-by-step using this tutorial. Alternatively, you can download the source code from GitHub and jump to the configuration steps to get the app up and running.

Examining the Application Architecture

Like the Personal Tab and Channel Tab apps we previously presented in this article series, here we’re building a serverless app using Python and Flask to provide information inside a Microsoft Teams tab. However, unlike the other apps, we’re building a serverless function app that provides a Teams tab with JSON-based Adaptive Cards instead of full-page HTML content.  

This time, we’re creating a single az-function-messages HTTP trigger to provide Teams with logic and JSON Adaptive Cards:

Registering an Azure Bot

Before sending an access request to Teams, we must register a new application in the Azure Active Directory (AAD) App Registrations portal and create a secret for the app to authenticate to Azure. Follow the steps below to set up your new Azure Bot registration.

Follow the instructions in the Tab with Adaptive Cards README. You can use the same steps to register your Teams Python application with Azure AD. However, we’re creating an app registration with the adaptive-cards-bot name this time.

Then, change the App Registration in the Azure portal so that the Messaging endpoint link in Configuration points to the /api/az-function-messages endpoint of your function app website (for example, *.azurewebsites.net):

Creating the Application Using Visual Studio Code

This section guides you through constructing a basic project that runs Azure functions on top of a Python project.

First, create a new local directory structure like below: 

\PythonTeamsApps
   +-- \AdaptiveCards

Type code. in your terminal to open the selected folder in Visual Studio Code:

\AdaptiveCards>code .

Now it’s time to create our first Azure functions. Select the Azure tab and click the Create Function icon within the Functions section:

However, we still have no Function project on Azure. We’re creating it locally now to upload it to the cloud later. When the dialog box asks if you would like to create a new project, click Yes.

As the list that appears demonstrates, Azure functions support many programming languages, including Python, JavaScript, TypeScript, C#, PowerShell, and Java. Select Python:

Now we must choose the template for our Function project. Azure Functions triggers come in many forms to suit various situations, depending on when the Functions should run. In our Adaptive Cards app, we need an HTTP trigger that will respond to HTTP requests our Azure Bot makes via the Teams Tab:

Now give the Azure function a name: az-function-messages.

Then, provide the Authorization level. Make it public by choosing Anonymous:

That’s it! You now have a new Azure function.

Now select the Functions group and click the Deploy to the Function App button:

We still don’t have a Function App in Azure for our Adaptive Cards app. Create it by clicking the first option, + Create new Function App in Azure:

Then, name the project adaptive-cards-function-app, or something similar:

Observe how VS Code now displays a new folder, where you can conveniently browse data from your function app stored in the Azure cloud, including files, logs, and settings:

Right-click the Application Settings node next and select Download Remote Settings:

Then press F5 to run the function app.

As the screenshot shows, the HTTP trigger function is running locally at the 7071 port:

az-function-messages: [GET,POST] http://localhost:7071/api/az-function- messages

You can test the HTTP trigger by opening it in a browser:

Observe how the HTTP response is just plain text. Now, let’s make the Azure function responds with JSON-based adaptive cards to render inside the Microsoft Teams tab.

First, open the Adaptive Cards Designer. Then, click the New Card button. Click Blank Card next and change the Select Host App value to Microsoft Teams - Light.

Then, drag the TextBlock from the Elements section and drop it onto the empty Adaptive Card:

Next, double click the TextBlock and type the text: “WELCOME TO MS TEAMS ADAPTIVE CARDS WITH PYTHON AND AZURE FUNCTIONS”:

Then, click the + to expand the Element Properties panel and change the Font Weight value to Bolder and the Size to Extra Large:

Now click the + to expand the Card Payload Editor:

Title: Inserting image...

Let’s use this JSON card definition in our app. Create a new Models folder in your project, and add an AdaptiveCards.py file with these contents:

Python
def get_sample_card():
  return 
  """
  """

Now copy the JSON payload from the Adaptive Cards Designer and paste it inside the triple quotes of your get_sample_card function:

Python
def get_sample_card():
  return 
  """{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4",
    "body": [
        {
            "type": "TextBlock",
            "text": "WELCOME TO MS TEAMS ADAPTIVE CARDS WITH PYTHON AND AZURE FUNCTIONS",
            "wrap": true,
            "size": "ExtraLarge",
            "weight": "Bolder"
        }
    ]
}
  """

Then, open the az-function-messages\__init__.py file and replace its contents with the following code snippet:

Python
import azure.functions as func
 
from Models.AdaptiveCards import get_sample_card 
 
def main(req: func.HttpRequest) -> func.HttpResponse: 
  return func.HttpResponse( 
    """{ 
      "tab":{ 
        "type":"continue", 
        "value":{ 
          "cards":[ 
            { 
            "card":""" + get_sample_card() + """} 
          ] 
        } 
      } 
      } 
    """, 
  
    status_code=200 
  )

Installing an App in Microsoft Teams

Let’s now upload our basic Python app to Microsoft Teams.

First, download the files from the original C# project appPackage folder. Then, create an appPackage folder in your project root. Move the downloaded files there.

Open the manifest.json file next. Below are the configurations needed so that Microsoft Teams knows how to integrate with our Python app. Replace the {{BOT-ID}} placeholder with the MicrosoftAppId from the app you registered in Azure.

JavaScript
<<some JSON code here>>
  "staticTabs": [
    {
      "entityId": "homeTab",
      "name": "Home",
      "contentBotId": "{{BOT-ID}}",
      "scopes": [ "personal" ]
    }
<<some JSON code here>>

Next, zip the three files. Alternatively, you can customize the color.png and outline.png images to identify your app better:

Open Microsoft Teams next and select the Apps tab. Click Upload a Custom App:

Then, select the .zip file you’ve just created:

Next, select the new TabAdaptiveCard app within the Built for your org section:

Now, go back to the VS Code project and hit F5 to run the application. Open the app:

Observe how Teams displays the basic Adaptive Card you created earlier in your custom Python app:

Why Use Adaptive Cards?

The problem that Adaptive Cards addresses is not new. You want to extend an existing application’s capabilities by displaying another application’s data and functions.

The traditional approach to this problem has been heavyweight. For example, Microsoft Teams tabs display the embedded application inside an iframe. This method gives the hosted app complete control over presenting and securing data. We experienced this kind of approach in the first two articles of this series.

Adaptive Cards provide a lightweight alternative. This approach requires less coordination between the embedded and hosting applications, and it also works across different application types. Adaptive Cards are flexible enough to cover most needs while giving the hosting application firm control over style and security.

Using Adaptive Cards in Your Bot

Microsoft embeds Adaptive Cards technology across many of its products, including Teams. Since the space here doesn't allow us to address the many aspects of this technology, you may need to browse additional material. If you want to dive into designing Adaptive Cards effectively, refer to Microsoft Docs.  

The diagram and the table below illustrate how we're using Adaptive Cards in the context of our bot application:

Step Type Description
1 tab/fetch tab/fetch is the first invoke request your bot receives when a user opens an Adaptive Card tab. When your bot receives the request, it either sends a tab continue response or a tab auth response
2 decision When the user token is not available, we need to send back the auth response
3 auth Card response for authentication
4 openUrl Opens a URL in the default browser
5 continue Adaptive Card with a user image, name, and Task Module invoke action
6 Action.Submit Gathers input fields, merges with an optional data field, and sends an event to the client
7 continue task/fetch: adaptive Card to show in the Task Module
8 Action.Submit task/submit: gathers input fields, merges with an optional data field, and sends an event to the client
9 continue Adaptive Card to show task/submit action
10 continue Adaptive Card showing sample text and Submit Action
11 Action.Submit tab/submit: gathers input fields, merges with an optional data field, and sends an event to the client
12 continue Adaptive Card to show sign out action

Before implementing the code for the Cards above, install the following Python packages:

  • Microsoft Bot Framework allows you to develop bots in Python that interact with MS Teams.
  • Microsoft Graph will let us get information about the logged-in user.
  • Flask is a micro web framework for Python that will deal with HTTP requests/responses when MS Teams interacts with our bot's Azure function.
Python
pip install botbuilder-core
pip install botbuilder-schema
pip install botbuilder-integration-aiohttp
pip install microsoftgraph-python
pip install flask[async]

Now open the AdaptiveCards.py file in the Models folder and replace its contents with the following code:

Python
from http import HTTPStatus
 
from botbuilder.core import (
    CardFactory
)
 
# Card response for authentication
def createAuthResponse (signInLink):
    adaptive_card = {
        "status": HTTPStatus.OK,
        "body": {
            "tab": {
                "type": "auth",
                "suggestedActions": {
                    "actions": [
                        {
                            "type": "openUrl",
                            "value": signInLink,
                            "title": "Sign in to this app"
                        }
                    ]
                }
            },
        }
    }
 
    return CardFactory.adaptive_card(adaptive_card)
 
def  createFetchResponse(userImage, displayName):
    adaptive_card = {
        "status": HTTPStatus.OK,
        "body": {
            "tab": {
                "type": "continue",
                "value": {
                    "cards": [
                        {
                            "card": getAdaptiveCardUserDetails(userImage, displayName),
                        },
                        {
                            "card": getAdaptiveCardSubmitAction(),
                        }
                    ]
                },
            },
        }
    }
 
    return CardFactory.adaptive_card(adaptive_card)
 
# Adaptive Card with user image, name and Task Module invoke action
def getAdaptiveCardUserDetails(image, name):
    if (image and image != ''):
        image = f"data:image/png;base64, {image}"
    else:
        image = "https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg"
 
    adaptive_card = {
        "$schema": 'http://adaptivecards.io/schemas/adaptive-card.json',
        "body": [
            {
                "type": "ColumnSet",
                "columns": [
                    {
                        "type": "Column",
                        "items": [
                            {
                                "type": "Image",
                                "url": image,
                                "size": "Medium"
                            }
                        ],
                        "width": "auto"
                    },
                    {
                        "type": "Column",
                        "items": [
                            {
                                "type": "TextBlock",
                                "weight": "Bolder",
                                "text": 'Hello: ' + name,
                                "wrap": True
                            },
                        ],
                        "width": "stretch"
                    }
                ]
            },
            {
                "type": 'ActionSet',
                "actions": [
                    {
                        "type": "Action.Submit",
                        "title": "Show Task Module",
                        "data": {
                            "msteams": {
                                "type": "task/fetch"
                            }
                        }
                    }
                ]
            }
        ],
        "type": 'AdaptiveCard',
        "version": '1.4'
    }
  
    return adaptive_card
 
# Adaptive Card showing sample text and Submit Action
def getAdaptiveCardSubmitAction():
    return {
        "$schema": 'http://adaptivecards.io/schemas/adaptive-card.json',
        "body": [
            {
                "type": 'Image',
                "height": '157px',
                "width": '300px',
                "url": 'https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg',
            },
            {
                "type": 'TextBlock',
                "size": 'Medium',
                "weight": 'Bolder',
                "text": 'tab/fetch is the first invoke request that your bot receives when a user opens an Adaptive Card tab. When your bot receives the request, it either sends a tab continue response or a tab auth response',
                "wrap": True,
            },
            {
                "type": 'TextBlock',
                "size": 'Medium',
                "weight": 'Bolder',
                "text": 'tab/submit request is triggered to your bot with the corresponding data through the Action.Submit function of Adaptive Card',
                "wrap": True,
            },
            {
                "type": 'ActionSet',
                "actions": [
                    {
                        "type": 'Action.Submit',
                        "title": 'Sign Out',
                    }
                ],
            }
        ],
        "type": 'AdaptiveCard',
        "version": '1.4'
    };
 
def invokeTaskResponse():
    adaptiveCard = {
        "status": HTTPStatus.OK,
        "body": {
            "task": {
                "type": 'continue',
                "value": {
                    "card": {
                        "contentType": "application/vnd.microsoft.card.adaptive",
                        "content": adaptiveCardTaskModule()
                    },
                    "heigth": 250,
                    "width": 400,
                    "title": 'Sample Adaptive Card'
                }
            }
        }
    }
 
    return CardFactory.adaptive_card(adaptiveCard)
 
# Adaptive Card to show in task module
def adaptiveCardTaskModule():
    return {
        '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
        'body': [
            {
                'type': 'TextBlock',
                'size': 'Medium',
                'weight': 'Bolder',
                'text': 'Sample task module flow for tab'
            },
            {
                'type': 'Image',
                'height': '50px',
                'width': '50px',
                'url': 'https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg',
            },
            {
                'type': 'ActionSet',
                'actions': [
                    {
                        'type': "Action.Submit",
                        'title': "Close",
                        'data': {
                            'msteams': {
                                'type': "task/submit"
                            }
                        }
                    }
                ]
            }
        ],
        'type': 'AdaptiveCard',
        'version': '1.4'}
 
# Card response for tab submit request
def taskSubmitResponse():
  adaptive_card = {
    'status': HTTPStatus.OK,
    'body': {
        'task': {
            'value': {
                'tab': {
                    'type': "continue",
                    'value': {
                        'cards': [
                            {
                                'card': taskSubmitCard()
                            }
                        ]
                    }
                }
            },
            'type': "continue"
        },
        'responseType': "task"
    }
  }
 
  return CardFactory.adaptive_card(adaptive_card)
 
# Adaptive Card to show task/submit action
def taskSubmitCard():
    return {
        '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
        'body': [
            {
                'type': 'TextBlock',
                'size': 'Medium',
                'weight': 'Bolder',
                'text': 'The action called task/submit. Please refresh to load contents again.',
                'wrap': True,
            }
        ],
        'type': 'AdaptiveCard',
        'version': '1.4'
    }
 
# Card response for tab submit request
def createSubmitResponse():
  adaptive_card = {
      'status': HTTPStatus.OK,
      'body': {
          'tab': {
              'type': "continue",
              'value': {
                  'cards': [
                      {
                          'card': signOutCard(),
                      }
                  ]
              },
          },
      }
  }
 
  return CardFactory.adaptive_card(adaptive_card)
 
# Adaptive Card to show sign out action
def signOutCard():
    return {
    '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
    'body': [
        {
            'type': 'TextBlock',
            'size': 'Medium',
            'weight': 'Bolder',
            'text': 'Sign out successful. Please refresh to Sign in again.',
            'wrap': True,
        }
    ],
    'type': 'AdaptiveCard',
    'version': '1.4'
}

Now create a folder named bots and create two files inside it: __init__.py and teams_task_module_bot.py:

\bots
   |---- __init__.py
   +---- teams_task_module_bot.py

Add add this code snippet to the __init__.py file:

Python
from .teams_task_module_bot import TeamsTaskModuleBot
 
__all__ = ["TeamsTaskModuleBot"]

Then add this code block to the teams_task_module_bot.py:

Python
import os
 
from botbuilder.core import (
    TurnContext,
)
from botbuilder.schema.teams import (
    TaskModuleRequest,
    TaskModuleResponse,
    TabRequest,
    TabSubmit
)
from botbuilder.core.teams import TeamsActivityHandler
from Models.AdaptiveCards import createAuthResponse, createSubmitResponse, invokeTaskResponse, taskSubmitResponse
 
from graphClient import GraphClient
 
class TeamsTaskModuleBot(TeamsActivityHandler):
    async def on_teams_tab_fetch(  # pylint: disable=unused-argument
        self, turn_context: TurnContext, tab_request: TabRequest
    ):
        # When the Bot Service Auth flow completes, turn_context will contain a magic code used for verification.
        magicCode = ''
        if turn_context.activity.value is not None and 'state' in turn_context.activity.value is not None:
            magicCode = turn_context.activity.value['state'] 
 
        # Getting the tokenResponse for the user
        tokenResponse = await turn_context.adapter.get_user_token(turn_context, os.environ.get("ConnectionName"), magicCode)
 
        if (not tokenResponse) or (not tokenResponse.token):
            # Token is not available, hence we need to send back the auth response
 
            # Retrieve the OAuth Sign in Link.
            signInLink = await turn_context.adapter.get_oauth_sign_in_link(turn_context, os.environ.get("ConnectionName"))
 
            # Generating and returning auth response.
            return createAuthResponse(signInLink)
 
        graphClient = GraphClient(tokenResponse.token);
 
        profile = graphClient.GetUserProfile()
 
        userImage = graphClient.GetUserPhoto(profile["id"])
 
        return createAuthResponse(userImage, profile["displayName"])
 
    async def on_teams_tab_submit(  # pylint: disable=unused-argument
        self, turn_context: TurnContext, tab_submit: TabSubmit
    ):
        adapter = turn_context.adapter
        await adapter.sign_out_user(turn_context, os.environ.get("ConnectionName"))
 
        # Generating and returning submit response.
        return createSubmitResponse();
 
    async def on_teams_task_module_fetch(
        self, turn_context: TurnContext, task_module_request: TaskModuleRequest
    ) -> TaskModuleResponse:
        return invokeTaskResponse()
 
    async def on_teams_task_module_submit(
        self, turn_context: TurnContext, task_module_request: TaskModuleRequest
    ) -> TaskModuleResponse:
        return taskSubmitResponse()

Replace the code in the __ini__.py file inside the az-function-messages file with the following content:

Python
from flask import Flask,request,Response
import sys
 
import azure.functions as func 
import os
from botbuilder.schema import Activity
from botbuilder.core import(  
    BotFrameworkAdapterSettings,
    BotFrameworkAdapter
)
 
from http import HTTPStatus
from bots.teams_task_module_bot import TeamsTaskModuleBot
from config import DefaultConfig
 
CONFIG = DefaultConfig()
 
app = Flask(__name__)
 
this = sys.modules[__name__]
this.cacheHelper = None
 
SETTINGS = BotFrameworkAdapterSettings(os.environ.get("MicrosoftAppId"), os.environ.get("MicrosoftAppPassword"))
ADAPTER = BotFrameworkAdapter(SETTINGS)
 
BOT = TeamsTaskModuleBot()
 
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    response = func.WsgiMiddleware(app).handle(req, context)
    return response
 
@app.route("/api/az-function-messages",methods=["POST"])
async def messages():
    if "application/json" in request.headers["content-type"]:
        jsonmessage = request.json
    else:
        return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
 
    activity = Activity().deserialize(jsonmessage)
 
    auth_header = request.headers["Authorization"] if "Authorization" in request.headers else ""
 
    response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
    if response:
        return response.body["content"]["body"]
    return func.HttpResponse(status_code=HTTPStatus.OK)

Then, open the local.settings.json file and add the following keys:

JavaScript
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "***********",
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "FUNCTIONS_EXTENSION_VERSION": "~4",
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": ""***********",
    "WEBSITE_CONTENTSHARE": ""***********",
    "APPINSIGHTS_INSTRUMENTATIONKEY": ""***********",
    "MicrosoftAppId": "<<YOUR-MICROSOFT-APP-ID>>",
    "MicrosoftAppPassword": "<<YOUR-MICROSOFT-APP-PASSWORD>>",
    "ConnectionName": "<<YOUR-CONNECTION-NAME>>",,
    "ApplicationBaseUrl": "<<YOUR-APPLICATION-BASE-URL>>"
  }
}

Next, create a file named config.py with the following content:

Python
import os
 
class DefaultConfig:
  PORT = 3978
  APP_ID = os.environ.get("MicrosoftAppId", "")
  APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
  CONNECTION_NAME = os.environ.get("ConnectionName", "")

Finally, create a file named graphClient.py with the following content:

Python
import requests
import os
import sys
import base64
from microsoftgraph.client import Client
 
this = sys.modules[__name__]
this.cache = None
 
graph_url = 'https://graph.microsoft.com/v1.0'
 
class GraphClient():
    def __init__(self, token):
      if ((token == None) or (token.strip() == "")):
        raise Exception("SimpleGraphClient: Invalid token received.");
 
      self._token = token;
      if this.cache is None:
        this.cache = dict()
 
      # Get an Authenticated Microsoft Graph client using the token issued to the user.
      self.graphClient = Client(os.environ.get("MicrosoftAppId"), os.environ.get("MicrosoftAppPassword"))
 
    def GetUserProfile(self):
      # Send GET to /me
      user = requests.get(
        '{0}/me'.format(graph_url),
        headers={
          'Authorization': 'Bearer {0}'.format(self._token)
        })
      # Return the JSON result
      return user.json()
 
    def GetUserPhoto(self, user_id):
      if user_id not in this.cache:
        photo_response = requests.get(
          '{0}/me/photo/$value'.format(graph_url),
          headers={
          'Authorization': 'Bearer {0}'.format(self._token)
          }, stream=True)
 
        photo = photo_response.raw.read()
        this.cache[user_id] = base64.b64encode(photo).decode('utf-8')
      return this.cache[user_id]

Testing the Full Application

Now you have everything ready to get your app up and running.

Hit the F5 key to start the application and open the TabAdaptiveCard app on Microsoft Teams.

When a new Microsoft Teams tab opens, it invokes the on_teams_tab_fetch function on your bot. If the user is authenticated, that function calls the createFetchResponse function. Otherwise, it calls the createAuthResponse function.

Here, you’ll see the auth card that the createAuthResponse function generated because the user hasn’t signed up yet:

When the user signs in, Microsoft Teams invokes the signInLink URL that your bot generates. This link takes the user to the Microsoft Login website, which authenticates the user with a Microsoft account using OAuth 2.

Now it’s time for your bot’s on_teams_tab_fetch function to invoke the createFetchResponse function and display two cards: one for displaying user details and one for displaying a Sign Out action:

After Microsoft authenticates the user, the page will remember it, even if the user closes and reopens Microsoft Teams. So, your Azure Function bot app gets the same benefits of Single Sign-On (SSO) as the previous applications in this article series.

When the user clicks the Show Task Module button, Microsoft Teams calls your bot's on_teams_task_module_submit function and displays the taskSubmitResponse:

When the user clicks the Close button, the taskSubmitResponse function displays the taskSubmitCard card:

When the user clicks the Sign Out button, the createSubmitResponse function displays the signOutCard card:

Next Steps

In this final article in the Building Microsoft Teams Apps with Serverless Python series, we discussed how to integrate Microsoft Teams with Azure serverless functions to create an Adaptive Cards app.

This third app had some fundamental differences in how it used Azure Functions compared to the previous two apps. Instead of rendering HTML content inside the Teams tab, it used Adaptive Cards to provide pure JSON content and let the client Microsoft Teams provide the styling to render the HTML seamlessly.

In building our newest app, we learned how to create and configure an Azure Bot to return various adaptive cards to respond to different Microsoft Teams activity requests. With the help of Microsoft Graph, we accessed secured user profile information from Azure Active Directory (AD) on behalf of Teams and filled Adaptive Cards with customized data.

You’ve learned how to easily create a minimal project for a working Channel Tab app hosted on the Azure cloud using Microsoft Teams with Python and Visual Studio Code. You now have a functional application to use as a template for your future adaptive cards apps.

We’ve explored just how easy it is to use Python and serverless Azure functions to build helpful apps throughout this series. When you integrate these apps into Teams Personal, Channel, and Group tabs, your coworkers can easily access them from within the application they’re already using every day. Integrating Adaptive Cards into your apps enables seamlessly sharing content between Teams and other services.

Now that you know how to do all this, sign up for a free trial of Azure to expand the app you created here or create a unique app to help boost your organization’s productivity.

To learn more about how to build apps for an individual, your team, your organization, or for all Microsoft Teams users everywhere, check out Develop apps for Microsoft Teams.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here