.NET Core (Part 2)

Neil HaddleyMarch 15, 2023

Creating a Blazor App that calls Microsoft Graph.

Microsoft Graph is a REST API that can be used to access: Microsoft 365, Azure Active Directory, Windows and Dynamics 365.

Microsoft Graph explorer is a developer tool that lets you learn about Microsoft Graph APIs.

Microsoft Graph Explorer

Microsoft Graph Explorer

Blazor Server app

I wanted to create a Blazor Server app that would allow users to sign in using their M365/Azure Active Directory credentials.

I wanted the Blazor Server app to display the logged in user's name and their photograph.

I created an Azure application (app) registration.

Azure Active Directory

Azure Active Directory

New registration

New registration

Single tenant

Single tenant

redirect uri set to localhost...

redirect uri set to localhost...

App registration created

App registration created

Update Authentication

Update Authentication

Add a client secret

Add a client secret

Secret will be valid for 180 days

Secret will be valid for 180 days

Copy the value

Copy the value

Blazor Server

Blazor is a web framework for building Razor components.

Razor components run server-side in ASP.NET Core.

(Razor components run client-side in the browser using WebAssembly)

A blazor server application can be generated using the dotnet command line tool.

$ dotnet new blazorserver -o <project name>

In this case I wanted to create a blazor server application that would authenticate users using Azure Active Directory (the App registration created above) and call Microsoft Graph to access the user's profile and the user's profile photograph.

$ dotnet new blazorserver --auth SingleOrg --calls-graph -o haddley-blazor-graph --client-id "5df669b6-f661-473c-9f5d-100f792d16c7" --tenant-id "2788913d-04ad-47a2-ac42-4b02caa6a4be" --domain "p8lf.onmicrosoft.com" -f net7.0

dotnet new blazorserver ...

dotnet new blazorserver ...

dotnet run (navigate to localhost)

dotnet run (navigate to localhost)

The generated Blazor Server app (includes integration with Azure Active Directory and Microsoft Graph)

The generated Blazor Server app (includes integration with Azure Active Directory and Microsoft Graph)

I copied the ClientSecret to appsettings.json

I copied the ClientSecret to appsettings.json

I am now able to login to the Blazor Server app using Azure Active Directory credentials

I am now able to login to the Blazor Server app using Azure Active Directory credentials

I provided the password

I provided the password

I skipped Multi factor authentication (for now)

I skipped Multi factor authentication (for now)

Do I want to stay signed in?

Do I want to stay signed in?

I navigate to the Show profile page

I navigate to the Show profile page

/showprofile page

/showprofile page

Key move is call to injected GraphServiceClient (to access Microsoft Graph)

Key move is call to injected GraphServiceClient (to access Microsoft Graph)

I updated navigation...

I updated navigation...

... to include a link to a /showphoto page

... to include a link to a /showphoto page

The /showphoto page will need to make a "my photo" call to Microsoft Graph

The /showphoto page will need to make a "my photo" call to Microsoft Graph

I copied the "Me.Photo.Content" expression/path to the new /showphoto page.

I copied the "Me.Photo.Content" expression/path to the new /showphoto page.

Show photo page running

Show photo page running

Program.cs

Notice that the Blazor Server project includes a Program.cs file with contents similar what you would expect to see in a Model-View-Controller project.

A Blazor Server project and a Model-View-Controller project are both ASP.NET Core projects and it is possible to mix and match.

Program.cs (part 1)

Program.cs (part 1)

Program.cs (part 2)

Program.cs (part 2)

ShowPhoto.razor

TEXT
1@page "/showphoto"
2
3@using Microsoft.Identity.Web
4@using Microsoft.Graph
5@inject Microsoft.Graph.GraphServiceClient GraphServiceClient
6@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
7
8<h1>My Photo</h1>
9
10<p>This component demonstrates fetching data from a service.</p>
11
12@if (imgDataURL == null)
13{
14    <p><em>Loading...</em></p>
15}
16else
17{
18    <img src=@imgDataURL />
19}
20
21@code {
22    String? imgDataURL;
23
24    protected override async Task OnInitializedAsync()
25    {
26        try
27        {
28            Stream photo = await GraphServiceClient.Me.Photo.Content.Request().GetAsync();
29
30            if (photo != null)
31            {
32                MemoryStream ms = new MemoryStream();
33                photo.CopyTo(ms);
34                byte[] buffer = ms.ToArray();
35                string result = Convert.ToBase64String(buffer);
36                imgDataURL = string.Format("data:image/png;base64,{0}", result);
37            }
38            else
39            {
40                imgDataURL = "";
41            }
42        }
43        catch (Exception ex)
44        {
45            ConsentHandler.HandleException(ex);
46        }
47    }
48}

Program.cs

TEXT
1using Microsoft.AspNetCore.Authentication;
2using Microsoft.AspNetCore.Authentication.OpenIdConnect;
3using Microsoft.Identity.Web;
4using Microsoft.Identity.Web.UI;
5using Microsoft.AspNetCore.Authorization;
6using Microsoft.AspNetCore.Components;
7using Microsoft.AspNetCore.Components.Web;
8using Microsoft.AspNetCore.Mvc.Authorization;
9using Graph = Microsoft.Graph;
10using haddley_blazor_graph.Data;
11
12var builder = WebApplication.CreateBuilder(args);
13
14// Add services to the container.
15var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ');
16
17builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
18    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
19        .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
20            .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
21            .AddInMemoryTokenCaches();
22builder.Services.AddControllersWithViews()
23    .AddMicrosoftIdentityUI();
24
25builder.Services.AddAuthorization(options =>
26{
27    // By default, all incoming requests will be authorized according to the default policy
28    options.FallbackPolicy = options.DefaultPolicy;
29});
30
31builder.Services.AddRazorPages();
32builder.Services.AddServerSideBlazor()
33    .AddMicrosoftIdentityConsentHandler();
34builder.Services.AddSingleton<WeatherForecastService>();
35
36var app = builder.Build();
37
38// Configure the HTTP request pipeline.
39if (!app.Environment.IsDevelopment())
40{
41    app.UseExceptionHandler("/Error");
42    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
43    app.UseHsts();
44}
45
46app.UseHttpsRedirection();
47
48app.UseStaticFiles();
49
50app.UseRouting();
51
52app.MapControllers();
53app.MapBlazorHub();
54app.MapFallbackToPage("/_Host");
55
56app.Run();