Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph

This article shows how to create Microsoft Teams online meetings in ASP.NET Core using Microsoft Graph. Azure AD is used to implement the authentication using Microsoft.Identity.Web and the authenticated user can create teams meetings and send emails to all participants or attendees of the meeting.

Code: https://github.com/damienbod/TeamsAdminUI

Blogs in this series

Setup Azure App registration

An Azure App registration is setup to authenticate against Azure AD. The ASP.NET Core application will use delegated permissions for the Microsoft Graph. The listed permissions underneath are required to create the teams meetings and to send emails to the attendees. The account used to login needs access to office and should be able to send emails.

  • User.Read
  • Mail.Send
  • Mail.ReadWrite
  • OnlineMeetings.ReadWrite

This is the list of permissions I have activate for this demo.

The Azure App registration requires a user secret or a certificate to authentication the ASP.NET Core Razor page application. Microsoft.Identity.Web uses this to authenticate the application. You should always authenticate the application if possible.

Setup ASP.NET Core application

The Microsoft.Identity.Web Nuget packages with the MicrosoftGraphBeta package are used to implement the Azure AD client. We want to implement Open ID Connect code flow with PKCE and a secret to authenticate the identity and the Microsoft packages implements this client for us.

<ItemGroup>
<PackageReference 
	Include="Microsoft.Identity.Web" 
	Version="1.16.1" />
<PackageReference 
	Include="Microsoft.Identity.Web.UI" 
	Version="1.16.1" />
<PackageReference 
	Include="Microsoft.Identity.Web.MicrosoftGraphBeta" 
	Version="1.16.1" />
</ItemGroup>

The ConfigureServices method is used to add the required services for the Azure AD client authentication and the Microsoft Graph client for the API calls. The AddMicrosoftGraph is used to initialize the required permissions.

public void ConfigureServices(IServiceCollection services)
{
	// more services ...

	var scopes = "User.read Mail.Send Mail.ReadWrite OnlineMeetings.ReadWrite";

	services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
		.EnableTokenAcquisitionToCallDownstreamApi()
		.AddMicrosoftGraph("https://graph.microsoft.com/beta", scopes)
		.AddInMemoryTokenCaches();

	services.AddRazorPages().AddMvcOptions(options =>
	{
		var policy = new AuthorizationPolicyBuilder()
			.RequireAuthenticatedUser()
			.Build();
		options.Filters.Add(new AuthorizeFilter(policy));
	}).AddMicrosoftIdentityUI();
}

The AzureAd configuration is read from the app.settings file. The secrets are read from the user secrets in local development.

"AzureAd": {
	"Instance": "https://login.microsoftonline.com/",
	"Domain": "damienbodsharepoint.onmicrosoft.com",
	"TenantId": "5698af84-5720-4ff0-bdc3-9d9195314244",
	"ClientId": "a611a690-9f96-424f-9ea5-4ba99a642c01",
	"CallbackPath": "/signin-oidc",
	"SignedOutCallbackPath ": "/signout-callback-oidc"
	// "ClientSecret": "add secret to the user secrets"
},

Creating a Teams meeting using Microsoft Graph

The OnlineMeeting class from Microsoft.Graph is used to create the teams meeting. In this demo, we added a begin and an end DateTime in UTC and the name (Subject) of the meeting. We want that all invited attendees can bypass the lobby and enter directly into the meeting. This is implemented with the LobbyBypassSettings property. The attendees are added to the meeting using the Upn property and setting this with the email of each attendee. The organizer is automatically set to the identity signed in.

public OnlineMeeting CreateTeamsMeeting(
	string meeting, DateTimeOffset begin, DateTimeOffset end)
{

	var onlineMeeting = new OnlineMeeting
	{
		StartDateTime = begin,
		EndDateTime = end,
		Subject = meeting,
		LobbyBypassSettings = new LobbyBypassSettings
		{
			Scope = LobbyBypassScope.Everyone
		}
	};

	return onlineMeeting;
}

public OnlineMeeting AddMeetingParticipants(
	OnlineMeeting onlineMeeting, List<string> attendees)
{
	var meetingAttendees = new List<MeetingParticipantInfo>();
	foreach(var attendee in attendees)
	{
		if(!string.IsNullOrEmpty(attendee))
		{
			meetingAttendees.Add(new MeetingParticipantInfo
			{
				Upn = attendee.Trim()
			});
		}
	}

	if(onlineMeeting.Participants == null)
	{
		onlineMeeting.Participants = new MeetingParticipants();
	};

	onlineMeeting.Participants.Attendees = meetingAttendees;

	return onlineMeeting;
}

A simple service is used to implement the GraphServiceClient instance which is used to send the Microsoft Graph requests. This uses the Microsoft Graph as described by the docs.

public async Task<OnlineMeeting> CreateOnlineMeeting(
	OnlineMeeting onlineMeeting)
{
	return await _graphServiceClient.Me
		.OnlineMeetings
		.Request()
		.AddAsync(onlineMeeting);
}

public async Task<OnlineMeeting> UpdateOnlineMeeting(
	OnlineMeeting onlineMeeting)
{
	return await _graphServiceClient.Me
		.OnlineMeetings[onlineMeeting.Id]
		.Request()
		.UpdateAsync(onlineMeeting);
}

public async Task<OnlineMeeting> GetOnlineMeeting(
	string onlineMeetingId)
{
	return await _graphServiceClient.Me
		.OnlineMeetings[onlineMeetingId]
		.Request()
		.GetAsync();
}

A Razor page is used to create a new Microsoft Teams online meeting. The two services are added to the class and a HTTP Post method implements the form request from the Razor page. This method creates the Microsoft Teams meeting using the services and redirects to the created Razor page with the ID of the meeting.

[AuthorizeForScopes(Scopes = new string[] { "User.read", "Mail.Send", "Mail.ReadWrite", "OnlineMeetings.ReadWrite" })]
public class CreateTeamsMeetingModel : PageModel
{
	private readonly AadGraphApiDelegatedClient _aadGraphApiDelegatedClient;
	private readonly TeamsService _teamsService;

	public string JoinUrl { get; set; }

	[BindProperty]
	public DateTimeOffset Begin { get; set; }
	[BindProperty]
	public DateTimeOffset End { get; set; }
	[BindProperty]
	public string AttendeeEmail { get; set; }
	[BindProperty]
	public string MeetingName { get; set; }

	public CreateTeamsMeetingModel(AadGraphApiDelegatedClient aadGraphApiDelegatedClient,
		TeamsService teamsService)
	{
		_aadGraphApiDelegatedClient = aadGraphApiDelegatedClient;
		_teamsService = teamsService;
	}

	public async Task<IActionResult> OnPostAsync()
	{
		if (!ModelState.IsValid)
		{
			return Page();
		}

		var meeting = _teamsService.CreateTeamsMeeting(MeetingName, Begin, End);

		var attendees = AttendeeEmail.Split(';');
		List<string> items = new();
		items.AddRange(attendees);
		var updatedMeeting = _teamsService.AddMeetingParticipants(
		  meeting, items);

		var createdMeeting = await _aadGraphApiDelegatedClient.CreateOnlineMeeting(updatedMeeting);

		JoinUrl = createdMeeting.JoinUrl;

		return RedirectToPage("./CreatedTeamsMeeting", "Get", new { meetingId = createdMeeting.Id });
	}

	public void OnGet()
	{
		Begin = DateTimeOffset.UtcNow;
		End = DateTimeOffset.UtcNow.AddMinutes(60);
	}
}

Sending Emails to attendees using Microsoft Graph

The Created Razor page displays the meeting JoinUrl and some details of the Teams meeting. The page implements a form which can send emails to all the attendees using Microsoft Graph. The EmailService class implements the email logic to send plain mails or HTML mails using the Microsoft Graph.

using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.IO;

namespace TeamsAdminUI.GraphServices
{
    public class EmailService
    {
        MessageAttachmentsCollectionPage MessageAttachmentsCollectionPage = new();

        public Message CreateStandardEmail(string recipient, string header, string body)
        {
            var message = new Message
            {
                Subject = header,
                Body = new ItemBody
                {
                    ContentType = BodyType.Text,
                    Content = body
                },
                ToRecipients = new List<Recipient>()
                {
                    new Recipient
                    {
                        EmailAddress = new EmailAddress
                        {
                            Address = recipient
                        }
                    }
                },
                Attachments = MessageAttachmentsCollectionPage
            };

            return message;
        }

        public Message CreateHtmlEmail(string recipient, string header, string body)
        {
            var message = new Message
            {
                Subject = header,
                Body = new ItemBody
                {
                    ContentType = BodyType.Html,
                    Content = body
                },
                ToRecipients = new List<Recipient>()
                {
                    new Recipient
                    {
                        EmailAddress = new EmailAddress
                        {
                            Address = recipient
                        }
                    }
                },
                Attachments = MessageAttachmentsCollectionPage
            };

            return message;
        }

        public void AddAttachment(byte[] rawData, string filePath)
        {
            MessageAttachmentsCollectionPage.Add(new FileAttachment
            {
                Name = Path.GetFileName(filePath),
                ContentBytes = EncodeTobase64Bytes(rawData)
            });
        }

        public void ClearAttachments()
        {
            MessageAttachmentsCollectionPage.Clear();
        }

        static public byte[] EncodeTobase64Bytes(byte[] rawData)
        {
            string base64String = System.Convert.ToBase64String(rawData);
            var returnValue = Convert.FromBase64String(base64String);
            return returnValue;
        }
    }
}

The CreatedTeamsMeetingModel class is used to implement the Razor page logic to display some meeting details and send emails using a form post request. The OnGetAsync uses the meetingId to request the Teams meeting using Microsoft Graph and displays the data in the UI. The OnPostAsync method sends emails to all attendees.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;
using TeamsAdminUI.GraphServices;
using Microsoft.Graph;

namespace TeamsAdminUI.Pages
{
    public class CreatedTeamsMeetingModel : PageModel
    {
        private readonly AadGraphApiDelegatedClient _aadGraphApiDelegatedClient;
        private readonly EmailService _emailService;

        public CreatedTeamsMeetingModel(
            AadGraphApiDelegatedClient aadGraphApiDelegatedClient,
            EmailService emailService)
        {
            _aadGraphApiDelegatedClient = aadGraphApiDelegatedClient;
            _emailService = emailService;
        }

        [BindProperty]
        public OnlineMeeting Meeting {get;set;}

        [BindProperty]
        public string EmailSent { get; set; }

        public async Task<ActionResult> OnGetAsync(string meetingId)
        {
            Meeting = await _aadGraphApiDelegatedClient.GetOnlineMeeting(meetingId);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(string meetingId)
        {
            Meeting = await _aadGraphApiDelegatedClient.GetOnlineMeeting(meetingId);
            foreach (var attendee in Meeting.Participants.Attendees)
            {
                var recipient = attendee.Upn.Trim();
                var message = _emailService.CreateStandardEmail(recipient, Meeting.Subject, Meeting.JoinUrl);
                await _aadGraphApiDelegatedClient.SendEmailAsync(message);
            }

            EmailSent = "Emails sent to all attendees, please check your mailbox";
            return Page();
        }

    }
}

The created Razor page implements the HTML display logic and adds a form to send the emails. The JoinUrl is displayed as this is what you need to open the meeting a Microsoft Teams application.

@page "{handler?}"
@model TeamsAdminUI.Pages.CreatedTeamsMeetingModel
@{
}

<h4>Teams Meeting Created: @Model.Meeting.Subject</h4>
<hr />

<h4>Meeting Id:</h4>

<p>@Model.Meeting.Id</p>

<h4>JoinUrl</h4>

<p>@Model.Meeting.JoinUrl</p>

<h4>Participants</h4>

@foreach(var attendee in Model.Meeting.Participants.Attendees)
{
	<p>@attendee.Upn</p>
}

<form method="post">
    <div class="form-group">
        <input type="hidden" value="@Model.Meeting.Id" />
        <button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> Send Mail to attendees</button>
    </div>
</form>

<p>@Model.EmailSent</p>

Testing

When the application is started, you can create a new Teams meeting with the required details. The logged in user must have an account with access to Office and be on the same tenant as the Azure App registration setup for the Microsoft Graph permissions. The Teams meeting is organized using the identity that signed in because we used the delegated permissions.

Once the meeting is created, the created Razor page is opened with the details. You can send an email to all attendees or use the JoinUrl directly to open up the Teams meeting.

Creating Teams meetings and sending emails in ASP.NET Core is really useful and I will do a few following up posts to this as there is so much more you can do here once this is integrated.

Links:

https://docs.microsoft.com/en-us/graph/api/application-post-onlinemeetings

https://github.com/AzureAD/microsoft-identity-web

Send Emails using Microsoft Graph API and a desktop client

https://www.office.com/?auth=2

https://aad.portal.azure.com/

https://admin.microsoft.com/Adminportal/Home

13 comments

  1. Hello,
    Absolutely great blog here.
    Thanks for your easy-to-understand example.

  2. […] Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph (Damien Bowden) […]

  3. […] User Agent Logic with Blazor and BabylonJS and Blazor – The Player Camera (Cody Merritt Anhorn) Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph (Damien Bowden) Azure Table Storage with ASP.NET Core (Muhammed Saleem) Creating a Game in Blazor […]

  4. I wish someone would create an example of an Azure AD Single Page App using cookie authentication to the BFF 🙂

  5. […] Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph – Damien Bowden […]

  6. […] Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph […]

  7. hi damien, can I do it in a web api project?

    1. Hi Johnfrits

      yes, using AAD, you can use the OBO flow and this will work. If you use Azure B2C or need a admin solution with a dedicated user, then you need the application authorization solution, I will blog this on Monday 🙂

      Greetings Damien

  8. […] Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph (delegated) […]

  9. […] Creating Microsoft Teams meetings in ASP.NET Core using Microsoft Graph (delegated) […]

Leave a Reply to TAFF Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: