教程:使用外部租户在 .NET MAUI 应用中将用户登录
本教程是一个系列教程的最后一部分,演示如何在 .NET 多平台应用 UI (.NET MAUI) shell 中添加登录和注销代码,以及如何在 Windows 平台上运行该应用。 在本系列教程的第 2 部分中,你创建了一个 .NET MAUI shell 应用,通过 MSAL 帮助程序类添加了 MSAL SDK 支持,安装了所需的库,并包含了图像资源。 最后一步演示了如何在 .NET MAUI shell 中添加登录和注销代码,以及如何在 Windows 平台上运行该应用。
本教程介绍如何执行下列操作:
- 添加登录和注销代码。
- 修改应用 Shell。
- 添加特定于平台的代码。
- 添加应用设置。
- 运行并测试 .NET MAUI shell 应用。
先决条件
添加登录和注销代码
.NET MAUI 应用的用户界面 (UI) 由映射到每个目标平台的本机控件的对象构成。 用于创建 .NET MAUI 应用 UI 的主控件组包括页面、布局和视图。
添加主视图页
后续步骤将对代码进行组织,以便定义 main view
。
从不再需要 MainPage.xaml 和 MainPage.xaml.cs 的项目中删除这两者。 在“解决方案资源管理器”窗格中,找到 MainPage.xaml 的条目,右键单击它并选择“删除”。
右键单击“SignInMaui”项目,然后选择“新增”>“文件夹”。 将文件夹命名为 Views。
右键单击“视图”。
选择“添加”>“新项...”。
在模板列表中选择“.NET MAUI”。
选择“.NET MAUI ContentPage (XAML)”模板。 将文件命名为 MainView.xaml。
选择 添加 。
MainView.xaml 文件将在新的文档选项卡中打开,显示表示页面 UI 的所有 XAML 标记。 将 XAML 标记替换为以下标记:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SignInMaui.Views.MainView" Title="Microsoft Entra External ID" > <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center"> <Image Source="external_id.png" SemanticProperties.Description="External ID" HeightRequest="200" HorizontalOptions="Center" /> <Label Text="CIAM" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Button x:Name="SignInButton" Text="Sign In" SemanticProperties.Hint="Sign In" Clicked="OnSignInClicked" HorizontalOptions="Center" IsEnabled="False"/> </VerticalStackLayout> </ScrollView> </ContentPage>
保存文件。
让我们将页面上 XAML 控件的关键部分细分一下:
<ContentPage>
是 MainView 类的根对象。<VerticalStackLayout>
是 ContentPage 的子对象。 此布局控件将其子控件逐一垂直排列。<Image>
会显示一张图像,在本例中,它使用你之前下载的 azureactive_directory.png_。<Label>
控件显示文本。- 用户可按下
<Button>
来引发Clicked
事件。 可运行代码来响应Clicked
事件。 Clicked="OnSignInClicked"
:按钮的Clicked
事件分配给OnSignInClicked
事件处理程序,将在代码隐藏文件中定义该处理程序。 将在下一步骤中创建此代码。
处理 OnSignInClicked 事件
下一步是为按钮的 Clicked
事件添加代码。
在 Visual Studio 的“解决方案资源管理器”窗格中,展开 MainView.xaml 文件以显示其代码隐藏文件 MainView.xaml.cs。 打开 MainView.xaml.cs,并将文件的内容替换为以下代码:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views { public partial class MainView : ContentPage { public MainView() { InitializeComponent(); IAccount cachedUserAccount = PublicClientSingleton.Instance.MSALClientHelper.FetchSignedInUserFromCache().Result; _ = Dispatcher.DispatchAsync(async () => { if (cachedUserAccount == null) { SignInButton.IsEnabled = true; } else { await Shell.Current.GoToAsync("claimsview"); } }); } private async void OnSignInClicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); await Shell.Current.GoToAsync("claimsview"); } protected override bool OnBackButtonPressed() { return true; } } }
MainView
类是一个内容页面,负责显示应用的主视图。 在构造函数中,它使用 PublicClientSingleton
实例中的 MSALClientHelper
来检索缓存的用户帐户,如果未找到缓存的用户帐户,则启用“登录”按钮。
单击“登录”按钮时,它会调用 AcquireTokenSilentAsync
方法来以无提示方式获取令牌,并使用 Shell.Current.GoToAsync
方法导航到 claimsview
页面。 此外,会重写 OnBackButtonPressed
方法以返回 true,表示已为此视图禁用后退按钮。
添加声明视图页
后续步骤将对代码进行组织,以便定义 ClaimsView
页面。 该页面将显示在 ID 令牌中找到的用户声明。
在 Visual Studio 的“解决方案资源管理器”窗格中,右键单击“视图”。
选择“添加”>“新项...”。
在模板列表中选择“.NET MAUI”。
选择“.NET MAUI ContentPage (XAML)”模板。 将文件命名为 ClaimsView.xaml。
选择 添加 。
ClaimsView.xaml 文件将在新的文档选项卡中打开,显示表示页面 UI 的所有 XAML 标记。 将 XAML 标记替换为以下标记:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SignInMaui.Views.ClaimsView" Title="ID Token View"> <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <VerticalStackLayout> <Label Text="CIAM" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" FontSize="26" Padding="0,0,0,20" HorizontalOptions="Center" /> <Label Padding="0,20,0,0" VerticalOptions="Center" HorizontalOptions="Center" FontSize="18" Text="Claims found in ID token" /> <ListView ItemsSource="{Binding IdTokenClaims}" x:Name="Claims"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid Padding="0, 0, 0, 0"> <Label Grid.Column="1" Text="{Binding}" HorizontalOptions="Center" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button x:Name="SignOutButton" Text="Sign Out" HorizontalOptions="Center" Clicked="SignOutButton_Clicked" /> </VerticalStackLayout> </ContentPage>
此 XAML 标记代码表示 .NET MAUI 应用中声明视图的 UI 布局。 它首先使用标题定义
ContentPage
并禁用后退按钮行为。在
VerticalStackLayout
中,有几个Label
元素显示静态文本,后跟一个名为Claims
的ListView
,它绑定到名为IdTokenClaims
的集合以显示在 ID 令牌中找到的声明。 每个声明都使用DataTemplate
在ViewCell
内呈现,并在网格中居中显示为Label
。最后,布局底部中间有一个
Sign Out
按钮,单击时会触发SignOutButton_Clicked
事件处理程序。
处理 ClaimsView 数据
下一步是添加代码来处理 ClaimsView
数据。
在 Visual Studio 的“解决方案资源管理器”窗格中,展开 ClaimsView.xaml 文件以显示其代码隐藏文件 ClaimsView.xaml.cs。 打开 ClaimsView.xaml.cs,并将文件的内容替换为以下代码:
using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views; public partial class ClaimsView : ContentPage { public IEnumerable<string> IdTokenClaims { get; set; } = new string[] {"No claims found in ID token"}; public ClaimsView() { BindingContext = this; InitializeComponent(); _ = SetViewDataAsync(); } private async Task SetViewDataAsync() { try { _ = await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); IdTokenClaims = PublicClientSingleton.Instance.MSALClientHelper.AuthResult.ClaimsPrincipal.Claims.Select(c => c.Value); Claims.ItemsSource = IdTokenClaims; } catch (MsalUiRequiredException) { await Shell.Current.GoToAsync("claimsview"); } } protected override bool OnBackButtonPressed() { return true; } private async void SignOutButton_Clicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.SignOutAsync().ContinueWith((t) => { return Task.CompletedTask; }); await Shell.Current.GoToAsync("mainview"); } }
ClaimsView.xaml.cs 代码表示 .NET MAUI 应用中声明视图的代码隐藏。 它首先导入必要的命名空间,并定义用于扩展
ContentPage
的ClaimsView
类。IdTokenClaims
属性是可枚举的字符串,最初设置为单个字符串,指示找不到声明。ClaimsView
构造函数将绑定上下文设置为当前实例,初始化视图组件,并异步调用SetViewDataAsync
方法。SetViewDataAsync
方法尝试以无提示方式获取令牌,从身份验证结果中检索声明,并将IdTokenClaims
属性设置为在名为Claims
的ListView
中显示这些声明。 如果发生MsalUiRequiredException
,指示身份验证需要用户交互,则应用将导航到声明视图。OnBackButtonPressed
方法会重写后退按钮行为以始终返回 true,从而防止用户从此视图中返回。SignOutButton_Clicked
事件处理程序使用PublicClientSingleton
实例将用户注销,完成后导航到main view
。
修改应用 Shell
AppShell
类定义应用的视觉层次结构,即用于创建应用 UI 的 XAML 标记。 更新 AppShell
,使其了解 Views
。
在“解决方案资源管理器”窗格中双击
AppShell.xaml
文件来打开 XAML 编辑器。 将 XAML 标记替换为以下代码:<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="SignInMaui.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:SignInMaui.Views" Shell.FlyoutBehavior="Disabled"> <ShellContent Title="Home" ContentTemplate="{DataTemplate local:MainView}" Route="MainPage" /> </Shell>
XAML 代码定义一个
AppShell
类,该类禁用浮出控件行为,并将主内容设置为标题为Home
的ShellContent
元素和指向MainView
类的内容模板。在 Visual Studio 的“解决方案资源管理器”窗格中,展开 AppShell.xaml 文件以显示其代码隐藏文件 AppShell.xaml.cs。 打开 AppShell.xaml.cs,并将文件的内容替换为以下代码:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.Views; namespace SignInMaui; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute("mainview", typeof(MainView)); Routing.RegisterRoute("claimsview", typeof(ClaimsView)); } }
更新
AppShell.xaml.cs
文件,使其包含MainView
和ClaimsView
的必要路由注册。 通过调用InitializeComponent()
方法,可确保AppShell
类的初始化。RegisterRoute()
方法将mainview
和claimsview
路由与其各自的视图类型(MainView
和ClaimsView
)相关联。
添加特定于平台的代码
.NET MAUI 应用项目包含一个 Platforms 文件夹,其中每个子文件夹表示一个 .NET MAUI 可以面向的平台。 若要提供特定于应用程序的行为,以补充默认的应用程序类,需要修改 Platforms/Windows/App.xaml.cs
。
将文件内容替换为以下代码:
using SignInMaui.MSALClient;
using Microsoft.Identity.Client;
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace SignInMaui.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
// configure redirect URI for your application
PlatformConfig.Instance.RedirectUri = $"msal{PublicClientSingleton.Instance.MSALClientHelper.AzureAdConfig.ClientId}://auth";
// Initialize MSAL
IAccount existinguser = Task.Run(async () => await PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()).Result;
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
var app = SignInMaui.App.Current;
PlatformConfig.Instance.ParentWindow = ((MauiWinUIWindow)app.Windows[0].Handler.PlatformView).WindowHandle;
}
}
在代码中,配置应用程序的重定向 URI 并初始化 MSAL,然后设置应用程序的父窗口。 此外,可以重写 OnLaunched
方法来处理启动事件并检索父窗口句柄。
添加应用设置
设置允许将配置应用行为的数据与代码分离,从而允许在不重新生成应用的情况下更改行为。 MauiAppBuilder
提供 ConfigurationManager
以在 .NET MAUI 应用中配置设置。 让我们将 appsettings.json
文件添加为 EmbeddedResource
。
若要创建 appsettings.json
,请执行以下步骤:
在 Visual Studio 的“解决方案资源管理器”窗格中,右键单击“SignInMaui”项目 >“添加”>“新项...”。
选择“Web”>“JavaScript JSON 配置文件”。 命名文件
appsettings.json
。选择 添加 。
选择 appsettings.json
在“属性”窗格中,将“生成操作”设置为“嵌入的资源”。
在“属性”窗格中,将“复制到输出目录”设置为“始终复制”。
将
appsettings.json
文件的内容替换为以下代码:{ "AzureAd": { "Authority": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/", "ClientId": "Enter_the_Application_Id_Here", "CacheFileName": "msal_cache.txt", "CacheDir": "C:/temp" }, "DownstreamApi": { "Scopes": "openid offline_access" } }
在
appsettings.json
中,找到占位符:- 查找
Enter_the_Tenant_Subdomain_Here
并将其替换为目录(租户)子域。 例如,如果租户主域为contoso.onmicrosoft.com
,请使用contoso
。 如果没有租户名称,请了解如何读取租户详细信息。 Enter_the_Application_Id_Here
,并将其替换为之前注册的应用的应用程序(客户端)ID。
- 查找
使用自定义 URL 域(可选)
使用自定义域可完全标记身份验证 URL。 从用户的角度来看,用户在身份验证过程中仍留在你的域中,而不是重定向到 ciamlogin.com 域名。
按照以下步骤使用自定义域:
使用为外部租户中的应用启用自定义 URL 域中的步骤为外部租户启用自定义 URL 域。
打开 appsettings.json 文件:
- 将
Authority
属性的值更新为 https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here。 请将Enter_the_Custom_Domain_Here
替换为你的自定义 URL 域,并将Enter_the_Tenant_ID_Here
替换为你的租户 ID。 如果没有租户 ID,请了解如何读取租户详细信息。 - 添加值为 [Enter_the_Custom_Domain_Here] 的
knownAuthorities
属性。
- 将
对 appsettings.json 文件进行更改后,如果自定义 URL 域为 login.contoso.com 且租户 ID 为 aaaabbbb-0000-cccc-1111-dddd2222eeee,则文件应类似于以下代码片段:
{
"AzureAd": {
"Authority": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
"ClientId": "Enter_the_Application_Id_Here",
"CacheFileName": "msal_cache.txt",
"CacheDir": "C:/temp",
"KnownAuthorities": ["login.contoso.com"]
},
"DownstreamApi": {
"Scopes": "openid offline_access"
}
}
运行并测试 .NET MAUI 桌面应用
根据设计,.NET MAUI 应用可在多个操作系统和设备上运行。 你需要选择要用于测试和调试应用的目标。
在 Visual Studio 工具栏中,将“调试目标”设置为要用于调试和测试的设备。 以下步骤演示如何将“调试目标”设置为 Windows:
- 选择“调试目标”下拉列表。
- 选择“框架”
- 选择“net7.0-windows...”
按 F5 运行应用,或者选择 Visual Studio 顶部的播放按钮。
现在,可以测试示例 .NET MAUI 桌面应用程序了。 运行应用程序后,桌面应用程序窗口将自动显示:
在显示的桌面窗口中,选择“登录”按钮。 随即将打开一个浏览器窗口,提示你登录。
在登录过程中,系统会提示你授予各种权限(以允许应用程序访问数据)。 成功登录并同意后,应用程序屏幕会显示主页。