Erstellen von Xamarin-Apps mit Microsoft Graph
In diesem Lernprogramm erfahren Sie, wie Sie eine Xamarin-App erstellen, die die Microsoft Graph-API zum Abrufen von Kalenderinformationen für einen Benutzer verwendet.
Tipp
Wenn Sie es vorziehen, nur das abgeschlossene Lernprogramm herunterzuladen, können Sie das GitHub Repository herunterladen oder klonen.
Voraussetzungen
Bevor Sie mit diesem Lernprogramm beginnen, sollten Sie Visual Studio auf einem Computer installiert haben, auf dem Windows 10 mit aktiviertem Entwicklermodus ausgeführt wird. Wenn Sie nicht über Visual Studio verfügen, besuchen Sie den vorherigen Link, um Downloadoptionen zu erhalten.
Sie sollten auch Xamarin als Teil Ihrer Visual Studio-Installation installiert haben. Anweisungen zum Installieren und Konfigurieren von Xamarin finden Sie unter "Installieren von Xamarin ".
Optional sollten Sie auch Zugriff auf einen Mac haben, auf dem Visual Studio für Mac installiert ist. Wenn Sie keinen Zugriff haben, können Sie dieses Lernprogramm dennoch abschließen, aber die iOS-spezifischen Abschnitte können nicht abgeschlossen werden.
Sie sollten auch über ein persönliches Microsoft-Konto mit einem Postfach auf Outlook.com oder ein Microsoft-Geschäfts-, Schul- oder Unikonto verfügen. Wenn Sie kein Microsoft-Konto haben, gibt es einige Optionen, um ein kostenloses Konto zu erhalten:
- Sie können sich für ein neues persönliches Microsoft-Konto registrieren.
- Sie können sich für das Microsoft 365 Entwicklerprogramm registrieren, um ein kostenloses Microsoft 365 Abonnement zu erhalten.
Hinweis
Dieses Lernprogramm wurde mit Visual Studio 2019, Version 16.10.3, und Visual Studio für Mac Version 8.5.1 geschrieben. Auf beiden Computern ist das Android SDK Platform 28 installiert. Die Schritte in diesem Handbuch funktionieren möglicherweise mit anderen Versionen, die jedoch nicht getestet wurden.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Erstellen Sie eine Xamarin-App
Öffnen Sie Visual Studio, und wählen Sie Neues Projekt erstellen.
Wählen Sie im Dialogfeld "Neues Projekt erstellen " die Option "Mobile App (Xamarin.Forms)" und dann " Weiter" aus.
In the Configure a new project dialog, enter
GraphTutorial
for the Project name and Solution name, then select Create.Wichtig
Stellen Sie sicher, dass Sie genau den gleichen Namen für die Visual Studio Project eingeben, die in diesen Laboranweisungen angegeben ist. Der Visual Studio-Projektname wird Teil des Namespace im Code. Der Code in diesen Anweisungen hängt vom Namespace ab, der mit dem in diesen Anweisungen angegebenen Visual Studio-Projektnamen übereinstimmt. Wenn Sie einen anderen Projektnamen verwenden, wird der Code nicht kompiliert, es sei denn, Sie passen alle Namespaces so an, dass Sie dem von Ihnen beim Erstellen des Projekts eingegebenen Visual Studio-Projektnamen entsprechen.
Wählen Sie im Dialogfeld "Neue plattformübergreifende App " die Vorlage "Leer" aus, und wählen Sie die Plattformen aus, die Sie unter "Plattformen" erstellen möchten. Wählen Sie "OK " aus, um die Lösung zu erstellen.
Installieren von Paketen
Installieren Sie vor dem Fortfahren einige zusätzliche NuGet Pakete, die Sie später verwenden werden.
- Microsoft.Identity.Client zum Behandeln Azure AD Authentifizierung und Tokenverwaltung.
- Microsoft. Graph für Anrufe an die Microsoft-Graph.
- TimeZoneConverter für die plattformübergreifende Behandlung von Zeitzonen.
Wählen Sie Extras > NuGet-Paket-Manager > Paket-Manager-Konsole aus.
Geben Sie in der Paket-Manager-Konsole die folgenden Befehle ein:
Install-Package Microsoft.Identity.Client -Version 4.35.1 -Project GraphTutorial Install-Package Microsoft.Identity.Client -Version 4.35.1 -Project GraphTutorial.Android Install-Package Microsoft.Identity.Client -Version 4.35.1 -Project GraphTutorial.iOS Install-Package Microsoft.Graph -Version 4.1.0 -Project GraphTutorial Install-Package TimeZoneConverter -Project GraphTutorial
Entwerfen der App
Aktualisieren Sie zunächst die App
Klasse, um Variablen zum Nachverfolgen des Authentifizierungsstatus und des angemeldeten Benutzers hinzuzufügen.
Erweitern Sie im Projektmappen-Explorer das GraphTutorial-Projekt und dann die Datei "App.xaml" . Öffnen Sie die Datei "App.xaml.cs" , und fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using System.ComponentModel; using System.IO; using System.Reflection; using System.Threading.Tasks;
Fügen Sie die
INotifyPropertyChanged
Schnittstelle der Klassendeklaration hinzu.public partial class App : Application, INotifyPropertyChanged
Fügen Sie der Klasse die
App
folgenden Eigenschaften hinzu.// Is a user signed in? private bool isSignedIn; public bool IsSignedIn { get { return isSignedIn; } set { isSignedIn = value; OnPropertyChanged("IsSignedIn"); OnPropertyChanged("IsSignedOut"); } } public bool IsSignedOut { get { return !isSignedIn; } } // The user's display name private string userName; public string UserName { get { return userName; } set { userName = value; OnPropertyChanged("UserName"); } } // The user's email address private string userEmail; public string UserEmail { get { return userEmail; } set { userEmail = value; OnPropertyChanged("UserEmail"); } } // The user's profile photo private ImageSource userPhoto; public ImageSource UserPhoto { get { return userPhoto; } set { userPhoto = value; OnPropertyChanged("UserPhoto"); } } // The user's time zone public static TimeZoneInfo UserTimeZone;
Fügen Sie der Klasse die
App
folgenden Funktionen hinzu. DieSignIn
FunktionenSignOut``GetUserInfo
sind vorerst nur Platzhalter.public async Task SignIn() { await GetUserInfo(); IsSignedIn = true; } public async Task SignOut() { UserPhoto = null; UserName = string.Empty; UserEmail = string.Empty; IsSignedIn = false; } private async Task GetUserInfo() { UserPhoto = ImageSource.FromStream(() => GetUserPhoto()); UserName = "Adele Vance"; UserEmail = "adelev@contoso.com"; } private Stream GetUserPhoto() { // Return the default photo return Assembly.GetExecutingAssembly().GetManifestResourceStream("GraphTutorial.no-profile-pic.png"); }
Die
GetUserPhoto
Funktion gibt vorerst ein Standardfoto zurück. Sie können entweder Ihre eigene Datei angeben oder die im Beispiel verwendete datei aus GitHub herunterladen. Wenn Sie Ihre eigene Datei verwenden, benennen Sie sie in no-profile-pic.png um.Kopieren Sie die Datei in das Verzeichnis ./GraphTutorial/GraphTutorial .
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf die Datei, und wählen Sie "Eigenschaften" aus. Ändern Sie im Eigenschaftenfenster den Wert der Buildaktion in eine eingebettete Ressource.
App-Navigation
In diesem Abschnitt ändern Sie die Hauptseite der Anwendung in eine FlyoutPage. Dadurch wird ein Navigationsmenü bereitgestellt, in dem zwischen der Ansicht in der App gewechselt werden kann.
Öffnen Sie die Datei "MainPage.xaml " im GraphTutorial-Projekt , und ersetzen Sie den Inhalt durch Folgendes.
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> <!-- <MainPageXamlSnippet> --> <?xml version="1.0" encoding="utf-8" ?> <MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:GraphTutorial" x:Class="GraphTutorial.MainPage"> <MasterDetailPage.Master> <local:MenuPage/> </MasterDetailPage.Master> <MasterDetailPage.Detail> <NavigationPage> <x:Arguments> <local:WelcomePage/> </x:Arguments> </NavigationPage> </MasterDetailPage.Detail> </MasterDetailPage> <!-- </MainPageXamlSnippet> -->
Implementieren des Menüs
Klicken Sie mit der rechten Maustaste auf das GraphTutorial-Projekt , und wählen Sie "Hinzufügen" und dann "Neuer Ordner" aus. Benennen Sie den Ordner
Models
.Klicken Sie mit der rechten Maustaste auf den Ordner "Modelle ", und wählen Sie "Hinzufügen" und dann " Klasse"... aus. Benennen Sie die Klasse
NavMenuItem
, und wählen Sie "Hinzufügen" aus.Öffnen Sie die Datei "NavMenuItem.cs ", und ersetzen Sie den Inhalt durch Folgendes.
namespace GraphTutorial.Models { public enum MenuItemType { Welcome, Calendar, NewEvent } public class NavMenuItem { public MenuItemType Id { get; set; } public string Title { get; set; } } }
Klicken Sie mit der rechten Maustaste auf das GraphTutorial-Projekt , und wählen Sie "Hinzufügen" und dann "Neues Element"... aus. Wählen Sie "Inhaltsseite" aus , und nennen Sie die Seite
MenuPage
. Wählen Sie Hinzufügen.Öffnen Sie die Datei "MenuPage.xaml ", und ersetzen Sie den Inhalt durch Folgendes.
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> <!-- <MenuPageXamlSnippet> --> <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" ios:Page.UseSafeArea="true" Title="Menu" x:Class="GraphTutorial.MenuPage"> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness"> <On Platform="UWP" Value="10, 10, 10, 10" /> </OnPlatform> </ContentPage.Padding> <ContentPage.Content> <StackLayout VerticalOptions="Start" HorizontalOptions="Center"> <StackLayout x:Name="UserArea" /> <!-- Signed out UI --> <StackLayout IsVisible="{Binding Path=IsSignedOut, Source={x:Static Application.Current}}"> <Label Text="Sign in to get started" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Medium" Margin="10,20,10,20" /> <Button Text="Sign in" Clicked="OnSignIn" HorizontalOptions="Center" /> </StackLayout> <!-- Signed in UI --> <StackLayout IsVisible="{Binding Path=IsSignedIn, Source={x:Static Application.Current}}"> <Image Source="{Binding Path=UserPhoto, Source={x:Static Application.Current}}" HorizontalOptions="Center" Margin="0,20,0,10" /> <Label Text="{Binding Path=UserName, Source={x:Static Application.Current}}" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Small" /> <Label Text="{Binding Path=UserEmail, Source={x:Static Application.Current}}" HorizontalOptions="Center" FontAttributes="Italic" /> <Button Text="Sign out" Margin="0,20,0,20" Clicked="OnSignOut" HorizontalOptions="Center" /> <ListView x:Name="ListViewMenu" HasUnevenRows="True" HorizontalOptions="Start"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid Padding="10"> <Label Text="{Binding Title}" FontSize="20"/> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </StackLayout> </ContentPage.Content> </ContentPage> <!-- </MenuPageXamlSnippet> -->
Erweitern Sie "MenuPage.xaml " im Projektmappen-Explorer , und öffnen Sie die Datei "MenuPage.xaml.cs" . Ersetzen Sie den Inhalt durch Folgendes.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; using Xamarin.Forms.Xaml; using GraphTutorial.Models; namespace GraphTutorial { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class MenuPage : ContentPage { MainPage RootPage => Application.Current.MainPage as MainPage; List<NavMenuItem> menuItems; public MenuPage() { InitializeComponent(); // Add items to the menu menuItems = new List<NavMenuItem> { new NavMenuItem {Id = MenuItemType.Welcome, Title="Home" }, new NavMenuItem {Id = MenuItemType.Calendar, Title="Calendar" }, new NavMenuItem {Id = MenuItemType.NewEvent, Title="New event" } }; ListViewMenu.ItemsSource = menuItems; // Initialize the selected item ListViewMenu.SelectedItem = menuItems[0]; // Handle the ItemSelected event to navigate to the // selected page ListViewMenu.ItemSelected += async (sender, e) => { if (e.SelectedItem == null) return; var id = (int)((NavMenuItem)e.SelectedItem).Id; await RootPage.NavigateFromMenu(id); }; } private async void OnSignOut(object sender, EventArgs e) { var signout = await DisplayAlert("Sign out?", "Do you want to sign out?", "Yes", "No"); if (signout) { await (Application.Current as App).SignOut(); } } private async void OnSignIn(object sender, EventArgs e) { try { await (Application.Current as App).SignIn(); } catch (Exception ex) { await DisplayAlert("Authentication Error", ex.Message, "OK"); } } } }
Hinweis
Visual Studio meldet Fehler in "MenuPage.xaml.cs". Diese Fehler werden in einem späteren Schritt behoben.
Implementieren der Willkommensseite
Klicken Sie mit der rechten Maustaste auf das GraphTutorial-Projekt , und wählen Sie "Hinzufügen" und dann "Neues Element"... aus. Wählen Sie "Inhaltsseite" aus , und nennen Sie die Seite
WelcomePage
. Wählen Sie Hinzufügen. Öffnen Sie die Datei "WelcomePage.xaml ", und ersetzen Sie den Inhalt durch Folgendes.<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> <!-- <WelcomePageXamlSnippet> --> <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" Title="Home" x:Class="GraphTutorial.WelcomePage"> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness"> <On Platform="UWP" Value="10, 10, 10, 10" /> </OnPlatform> </ContentPage.Padding> <ContentPage.Content> <StackLayout> <Label Text="Graph Xamarin Tutorial App" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large" Margin="10,20,10,20" /> <!-- Signed out UI --> <StackLayout IsVisible="{Binding Path=IsSignedOut, Source={x:Static Application.Current}}"> <Label Text="Please sign in to get started" HorizontalOptions="Center" FontSize="Medium" Margin="10,0,10,20"/> <Button Text="Sign in" HorizontalOptions="Center" Clicked="OnSignIn" /> </StackLayout> <!-- Signed in UI --> <StackLayout IsVisible="{Binding Path=IsSignedIn, Source={x:Static Application.Current}}"> <Label Text="{Binding Path=UserName, Source={x:Static Application.Current}, StringFormat='Welcome \{0\}!'}" HorizontalOptions="Center" FontSize="Medium"/> </StackLayout> </StackLayout> </ContentPage.Content> </ContentPage> <!-- </WelcomePageXamlSnippet> -->
Erweitern Sie "WelcomePage.xaml " im Projektmappen-Explorer , und öffnen Sie die Datei "WelcomePage.xaml.cs" . Fügen Sie die folgende Funktion zur
WelcomePage
-Klasse hinzu:private void OnSignIn(object sender, EventArgs e) { (App.Current.MainPage as MasterDetailPage).IsPresented = true; }
Hinzufügen von Kalender- und neuen Ereignisseiten
Fügen Sie nun eine Kalenderseite und eine neue Ereignisseite hinzu. Diese werden vorerst nur Platzhalter sein.
Klicken Sie mit der rechten Maustaste auf das GraphTutorial-Projekt , und wählen Sie "Hinzufügen" und dann "Neues Element"... aus. Wählen Sie "Inhaltsseite" aus , und nennen Sie die Seite
CalendarPage
. Wählen Sie Hinzufügen.Klicken Sie mit der rechten Maustaste auf das GraphTutorial-Projekt , und wählen Sie "Hinzufügen" und dann "Neues Element"... aus. Wählen Sie "Inhaltsseite" aus , und nennen Sie die Seite
NewEventPage
. Wählen Sie Hinzufügen.
Aktualisieren von MainPage-CodeBehind
Da nun alle Seiten vorhanden sind, aktualisieren Sie den CodeBehind für "MainPage.xaml".
Erweitern Sie "MainPage.xaml " im Projektmappen-Explorer , öffnen Sie die Datei " MainPage.xaml.cs" , und ersetzen Sie den gesamten Inhalt durch Folgendes.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; using GraphTutorial.Models; namespace GraphTutorial { public partial class MainPage : MasterDetailPage { Dictionary<int, NavigationPage> MenuPages = new Dictionary<int, NavigationPage>(); public MainPage() { InitializeComponent(); MasterBehavior = MasterBehavior.Popover; // Load the welcome page at start MenuPages.Add((int)MenuItemType.Welcome, (NavigationPage)Detail); } // Navigate to the selected page public async Task NavigateFromMenu(int id) { if (!MenuPages.ContainsKey(id)) { switch (id) { case (int)MenuItemType.Welcome: MenuPages.Add(id, new NavigationPage(new WelcomePage())); break; case (int)MenuItemType.Calendar: MenuPages.Add(id, new NavigationPage(new CalendarPage())); break; case (int)MenuItemType.NewEvent: MenuPages.Add(id, new NavigationPage(new NewEventPage())); break; } } var newPage = MenuPages[id]; if (newPage != null && Detail != newPage) { Detail = newPage; if (Device.RuntimePlatform == Device.Android) await Task.Delay(100); IsPresented = false; } } } }
Speichern Sie alle Änderungen. Klicken Sie mit der rechten Maustaste auf das Projekt, das Sie ausführen möchten (Android, iOS oder UWP), und wählen Sie "Als Startup Project festlegen" aus. Drücken Sie F5, oder wählen Sie Debuggen aus, > Debuggen in Visual Studio starten.
Registrieren der App im Portal
In dieser Übung erstellen Sie eine neue systemeigene Azure AD-Anwendung mithilfe des Azure Active Directory Admin Centers.
Öffnen Sie einen Browser, und navigieren Sie zum Azure Active Directory Admin Center. Melden Sie sich mit einem persönlichen Konto (auch: Microsoft-Konto) oder einem Geschäfts- oder Schulkonto an.
Wählen Sie in der linken Navigationsleiste Azure Active Directory aus, und wählen Sie dann App-Registrierungen unter Verwalten aus.
Wählen Sie Neue Registrierung aus. Legen Sie auf der Seite Anwendung registrieren die Werte wie folgt fest.
- Legen Sie Name auf
Xamarin Graph Tutorial
fest. - Legen Sie Unterstützte Kontotypen auf Konten in allen Organisationsverzeichnissen und persönliche Microsoft-Konten fest.
- Ändern Sie unter Umleitungs-URI (optional) die Dropdownliste in öffentlichen Client (mobile & Desktop), und legen Sie den Wert auf
msauth://com.companyname.GraphTutorial
.
- Legen Sie Name auf
Wählen Sie Registrieren aus. Kopieren Sie auf der Seite "Xamarin Graph Tutorial" den Wert der Anwendungs-ID (Client-ID), und speichern Sie ihn. Sie benötigen ihn im nächsten Schritt.
Hinzufügen der Azure AD-Authentifizierung
In dieser Übung erweitern Sie die Anwendung aus der vorherigen Übung, um die Authentifizierung mit Azure AD zu unterstützen. Dies ist erforderlich, um das erforderliche OAuth-Zugriffstoken zum Aufrufen der Microsoft Graph abzurufen. In diesem Schritt integrieren Sie die Microsoft-Authentifizierungsbibliothek für .NET (MSAL) in die Anwendung.
Erweitern Sie im Projektmappen-Explorer das GraphTutorial-Projekt, und klicken Sie mit der rechten Maustaste auf den Ordner "Modelle". Wählen Sie > Klasse hinzufügen... aus. Benennen Sie die
OAuthSettings
Klasse, und wählen Sie "Hinzufügen" aus.Öffnen Sie die Datei "OAuthSettings.cs", und ersetzen Sie den Inhalt durch Folgendes.
Ersetzen Sie
YOUR_APP_ID_HERE
dies durch die Anwendungs-ID aus Ihrer App-Registrierung.Wichtig
Wenn Sie die Quellcodeverwaltung wie Git verwenden, wäre jetzt ein guter Zeitpunkt, um die
OAuthSettings.cs
Datei aus der Quellcodeverwaltung auszuschließen, um zu vermeiden, dass versehentlich Ihre App-ID offengelegt wird.
Implementieren der Anmeldung
Öffnen Sie die Datei "App.xaml.cs" im GraphTutorial-Projekt, und fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using GraphTutorial.Models; using Microsoft.Identity.Client; using Microsoft.Graph; using System.Diagnostics; using System.Linq; using System.Net.Http.Headers; using TimeZoneConverter;
Ändern Sie die Deklarationszeile der App-Klasse, um den Namenskonflikt für Anwendung zu lösen.
public partial class App : Xamarin.Forms.Application, INotifyPropertyChanged
Fügen Sie der Klasse die folgenden Eigenschaften
App
hinzu.// UIParent used by Android version of the app public static object AuthUIParent = null; // Keychain security group used by iOS version of the app public static string iOSKeychainSecurityGroup = null; // Microsoft Authentication client for native/mobile apps public static IPublicClientApplication PCA; // Microsoft Graph client public static GraphServiceClient GraphClient; // Microsoft Graph permissions used by app private readonly string[] Scopes = OAuthSettings.Scopes.Split(' ');
Erstellen Sie als Nächstes einen neuen
PublicClientApplication
im Konstruktor derApp
Klasse.public App() { InitializeComponent(); var builder = PublicClientApplicationBuilder .Create(OAuthSettings.ApplicationId) .WithRedirectUri(OAuthSettings.RedirectUri); if (!string.IsNullOrEmpty(iOSKeychainSecurityGroup)) { builder = builder.WithIosKeychainSecurityGroup(iOSKeychainSecurityGroup); } PCA = builder.Build(); MainPage = new MainPage(); }
Aktualisieren Sie die
SignIn
Funktion so, dass sie zumPublicClientApplication
Abrufen eines Zugriffstokens verwendet wird. Fügen Sie den folgenden Code über derawait GetUserInfo();
Zeile hinzu.// First, attempt silent sign in // If the user's information is already in the app's cache, // they won't have to sign in again. try { var accounts = await PCA.GetAccountsAsync(); var silentAuthResult = await PCA .AcquireTokenSilent(Scopes, accounts.FirstOrDefault()) .ExecuteAsync(); Debug.WriteLine("User already signed in."); Debug.WriteLine($"Successful silent authentication for: {silentAuthResult.Account.Username}"); Debug.WriteLine($"Access token: {silentAuthResult.AccessToken}"); } catch (MsalUiRequiredException msalEx) { // This exception is thrown when an interactive sign-in is required. Debug.WriteLine("Silent token request failed, user needs to sign-in: " + msalEx.Message); // Prompt the user to sign-in var interactiveRequest = PCA.AcquireTokenInteractive(Scopes); if (AuthUIParent != null) { interactiveRequest = interactiveRequest .WithParentActivityOrWindow(AuthUIParent); } var interactiveAuthResult = await interactiveRequest.ExecuteAsync(); Debug.WriteLine($"Successful interactive authentication for: {interactiveAuthResult.Account.Username}"); Debug.WriteLine($"Access token: {interactiveAuthResult.AccessToken}"); } catch (Exception ex) { Debug.WriteLine("Authentication failed. See exception messsage for more details: " + ex.Message); }
Dieser Code versucht zunächst, ein Zugriffstoken im Hintergrund abzurufen. Wenn sich die Informationen eines Benutzers bereits im Cache der App befinden (z. B. wenn der Benutzer die App zuvor geschlossen hat, ohne sich abmelden zu müssen), ist dies erfolgreich, und es gibt keinen Grund, den Benutzer aufzufordern. Wenn sich keine Benutzerinformationen im Cache befinden, löst die
AcquireTokenSilent().ExecuteAsync()
Funktion eineMsalUiRequiredException
. In diesem Fall ruft der Code die interaktive Funktion auf, um ein TokenAcquireTokenInteractive
abzurufen.Aktualisieren Sie die
SignOut
Funktion, um die Informationen des Benutzers aus dem Cache zu entfernen. Fügen Sie den folgenden Code am Anfang derSignOut
Funktion hinzu.// Get all cached accounts for the app // (Should only be one) var accounts = await PCA.GetAccountsAsync(); while (accounts.Any()) { // Remove the account info from the cache await PCA.RemoveAsync(accounts.First()); accounts = await PCA.GetAccountsAsync(); }
Aktualisieren des Android-Projekts zum Aktivieren der Anmeldung
Bei verwendung in einem Xamarin Android-Projekt hat die Microsoft-Authentifizierungsbibliothek einige spezifische Anforderungen für Android.
Erweitern Sie im GraphTutorial.Android-Projekt den Ordner "Eigenschaften", und öffnen Sie dann AndroidManifest.xml. Wenn Sie Visual Studio für Mac verwenden, klicken Sie auf "Steuerelement" auf AndroidManifest.xml, und wählen Sie "Öffnen mit" und dann "Quellcode-Editor" aus. Ersetzen Sie den gesamten Inhalt durch Folgendes.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.GraphTutorial"> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" /> <application android:label="GraphTutorial.Android"> <activity android:name="microsoft.identity.client.BrowserTabActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="msauth" android:host="com.companyname.GraphTutorial" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
Öffnen Sie "MainActivity.cs", und fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using Android.Content; using Microsoft.Identity.Client;
Überschreiben Sie die
OnActivityResult
Funktion, um die Steuerung an die MSAL-Bibliothek zu übergeben. Fügen Sie der Klasse FolgendesMainActivity
hinzu.protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationContinuationHelper .SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); }
Fügen Sie in der
OnCreate
Funktion die folgende Zeile hinter der ZeileLoadApplication(new App());
hinzu.App.AuthUIParent = this;
Aktualisieren des iOS-Projekts zum Aktivieren der Anmeldung
Wichtig
Da MSAL die Verwendung einer Datei "Entitlements.plist" erfordert, müssen Sie Visual Studio mit Ihrem Apple-Entwicklerkonto konfigurieren, um die Bereitstellung zu aktivieren. Wenn Sie dieses Lernprogramm im iPhone Simulator ausführen, müssen Sie "Entitlements.plist" im Feld "Benutzerdefinierte Berechtigungen" in den Einstellungen des Projekts "GraphTutorial.iOS", Build->iOS Bundle Signing, hinzufügen. Weitere Informationen finden Sie unter Gerätebereitstellung für Xamarin.iOS.
Bei Verwendung in einem Xamarin iOS-Projekt hat die Microsoft-Authentifizierungsbibliothek einige spezifische Anforderungen für iOS.
Erweitern Sie im Projektmappen-Explorer das Projekt "GraphTutorial.iOS", und öffnen Sie dann die Datei "Entitlements.plist".
Suchen Sie die Schlüsselbundberechtigung, und wählen Sie "Schlüsselbund aktivieren" aus.
Fügen Sie in Schlüsselbundgruppen einen Eintrag mit dem Format
com.companyname.GraphTutorial
hinzu.Aktualisieren Sie den Code im GraphTutorial.iOS-Projekt, um die Umleitung während der Authentifizierung zu verarbeiten. Öffnen Sie die Datei "AppDelegate.cs", und fügen Sie die folgende
using
Anweisung am Anfang der Datei hinzu.using Microsoft.Identity.Client;
Fügen Sie die folgende Zeile hinzu, um direkt vor der Zeile zu
FinishedLaunching
LoadApplication(new App());
funktionieren.// Specify the Keychain access group App.iOSKeychainSecurityGroup = NSBundle.MainBundle.BundleIdentifier;
Überschreiben Sie die
OpenUrl
Funktion, um die URL an die MSAL-Bibliothek zu übergeben. Fügen Sie der Klasse FolgendesAppDelegate
hinzu.// Handling redirect URL // See: https://github.com/azuread/microsoft-authentication-library-for-dotnet/wiki/Xamarin-iOS-specifics public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url); return true; }
Speichern des Tokens
Wenn die Microsoft-Authentifizierungsbibliothek in einem Xamarin-Projekt verwendet wird, nutzt sie den systemeigenen sicheren Speicher, um Token standardmäßig zwischenzuspeichern. Weitere Informationen finden Sie unter Tokencache-Serialisierung.
Testen der Anmeldung
Wenn Sie nun die Anwendung ausführen und auf die Schaltfläche "Anmelden" tippen, werden Sie aufgefordert, sich anzumelden. Bei erfolgreicher Anmeldung sollte das Zugriffstoken in der Ausgabe des Debuggers angezeigt werden.
Benutzerdetails abrufen
Fügen Sie der App-Klasse eine neue Funktion hinzu, um die zu
GraphServiceClient
initialisieren.private async Task InitializeGraphClientAsync() { var currentAccounts = await PCA.GetAccountsAsync(); try { if (currentAccounts.Count() > 0) { // Initialize Graph client GraphClient = new GraphServiceClient(new DelegateAuthenticationProvider( async (requestMessage) => { var result = await PCA.AcquireTokenSilent(Scopes, currentAccounts.FirstOrDefault()) .ExecuteAsync(); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); })); await GetUserInfo(); IsSignedIn = true; } else { IsSignedIn = false; } } catch (Exception ex) { Debug.WriteLine("Failed to initialized graph client."); Debug.WriteLine($"Accounts in the msal cache: {currentAccounts.Count()}."); Debug.WriteLine($"See exception message for details: {ex.Message}"); } }
Aktualisieren Sie die
SignIn
Funktion in "App.xaml.cs", um diese Funktion anstelle vonGetUserInfo
. Entfernen Sie Folgendes aus derSignIn
Funktion.await GetUserInfo(); IsSignedIn = true;
Fügen Sie am Ende der Funktion Folgendes
SignIn
hinzu.await InitializeGraphClientAsync();
Aktualisieren Sie die
GetUserInfo
Funktion, um die Details des Benutzers aus dem Microsoft Graph abzurufen. Ersetzen Sie die vorhandeneGetUserInfo
-Funktion durch Folgendes.private async Task GetUserInfo() { // Get the logged on user's profile (/me) var user = await GraphClient.Me.Request() .Select(u => new { u.DisplayName, u.Mail, u.MailboxSettings, u.UserPrincipalName }) .GetAsync(); UserPhoto = ImageSource.FromStream(() => GetUserPhoto()); UserName = user.DisplayName; UserEmail = string.IsNullOrEmpty(user.Mail) ? user.UserPrincipalName : user.Mail; try { UserTimeZone = TZConvert.GetTimeZoneInfo(user.MailboxSettings.TimeZone); } catch { // Default to local time zone UserTimeZone = TimeZoneInfo.Local; } }
Speichern Sie die Änderungen, und führen Sie die App aus. Nach der Anmeldung wird die Benutzeroberfläche mit dem Anzeigenamen und der E-Mail-Adresse des Benutzers aktualisiert.
Abrufen einer Kalenderansicht
In dieser Übung integrieren Sie die Microsoft Graph in die Anwendung. Für diese Anwendung verwenden Sie die Microsoft Graph-Clientbibliothek für .NET, um Aufrufe an Microsoft Graph zu tätigen.
Abrufen von Kalenderereignissen von Outlook
Öffnen Sie CalendarPage.xaml im GraphTutorial-Projekt, und ersetzen Sie den Inhalt durch Folgendes.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" Title="Calendar" x:Class="GraphTutorial.CalendarPage"> <ContentPage.Content> <StackLayout> <Editor x:Name="JSONResponse" IsSpellCheckEnabled="False" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/> </StackLayout> </ContentPage.Content> </ContentPage>
Öffnen Sie CalendarPage.xaml.cs, und fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using Microsoft.Graph; using System.Collections.ObjectModel; using System.ComponentModel;
Fügen Sie der Klasse die folgende Funktion
CalendarPage
hinzu, um den Anfang der aktuellen Woche in der Zeitzone des Benutzers abzurufen.private static DateTime GetUtcStartOfWeekInTimeZone(DateTime today, TimeZoneInfo timeZone) { // Assumes Sunday as first day of week int diff = System.DayOfWeek.Sunday - today.DayOfWeek; // create date as unspecified kind var unspecifiedStart = DateTime.SpecifyKind(today.AddDays(diff), DateTimeKind.Unspecified); // convert to UTC return TimeZoneInfo.ConvertTimeToUtc(unspecifiedStart, timeZone); }
Fügen Sie der Klasse die folgende Funktion
CalendarPage
hinzu, um die Ereignisse des Benutzers abzurufen und die JSON-Antwort anzuzeigen.protected override async void OnAppearing() { base.OnAppearing(); // Get start and end of week in user's time zone var startOfWeek = GetUtcStartOfWeekInTimeZone(DateTime.Today, App.UserTimeZone); var endOfWeek = startOfWeek.AddDays(7); var queryOptions = new List<QueryOption> { new QueryOption("startDateTime", startOfWeek.ToString("o")), new QueryOption("endDateTime", endOfWeek.ToString("o")) }; var timeZoneString = Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.UWP ? App.UserTimeZone.StandardName : App.UserTimeZone.DisplayName; // Get the events var events = await App.GraphClient.Me.CalendarView.Request(queryOptions) .Header("Prefer", $"outlook.timezone=\"{timeZoneString}\"") .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }) .OrderBy("start/DateTime") .Top(50) .GetAsync(); // Temporary JSONResponse.Text = App.GraphClient.HttpProvider.Serializer.SerializeObject(events.CurrentPage); }
Überlegen Sie, was der Code
OnAppearing
macht.- Die URL, die aufgerufen wird, lautet
/v1.0/me/calendarview
.- Die
startDateTime
Parameter definieren den Anfang und das Ende derendDateTime
Kalenderansicht. - Der
Prefer: outlook.timezone
Headerstart
bewirkt, dass die Ereignisse und die Ereignisse in der Zeitzone des Benutzers zurückgegebenend
werden. - Die
Select
Funktion beschränkt die für jedes Ereignis zurückgegebenen Felder auf die Felder, die die App tatsächlich verwendet. - Die
OrderBy
Funktion sortiert die Ergebnisse nach Startdatum und -uhrzeit. - Die
Top
Funktion fordert höchstens 50 Ereignisse an.
- Die
- Die URL, die aufgerufen wird, lautet
Führen Sie die App aus, melden Sie sich an, und klicken Sie im Menü auf das Navigationselement Kalender. Es sollte ein JSON-Dump der Ereignisse im Kalender des Benutzers angezeigt werden.
Anzeigen der Ergebnisse
Jetzt können Sie das JSON-Dump durch etwas ersetzen, um die Ergebnisse auf benutzerfreundliche Weise anzuzeigen.
Erstellen Sie zunächst einen Bindungswertkonverter, um die von Microsoft Graph zurückgegebenen dateTimeTimeZone-Werte in die vom Benutzer erwarteten Datums- und Uhrzeitformate zu konvertieren.
Klicken Sie im GraphTutorial-Projekt mit der rechten Maustaste auf den Ordner "Modelle", und wählen Sie "Hinzufügen" und dann "Klasse" aus. Benennen Sie die
GraphDateTimeTimeZoneConverter
Klasse, und wählen Sie "Hinzufügen" aus.Ersetzen Sie den gesamten Inhalt der Datei durch Folgendes.
using Microsoft.Graph; using System; using System.Globalization; using Xamarin.Forms; namespace GraphTutorial.Models { class GraphDateTimeTimeZoneConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is DateTimeTimeZone date) { var parsedDateAs = DateTimeOffset.Parse(date.DateTime); // Return the local date time string return $"{parsedDateAs.LocalDateTime.ToShortDateString()} {parsedDateAs.LocalDateTime.ToShortTimeString()}"; } return string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
Ersetzen Sie den gesamten Inhalt von CalendarPage.xaml durch Folgendes.
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> <!-- <CalendarPageXamlSnippet> --> <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:GraphTutorial.Models" Title="Calendar" x:Class="GraphTutorial.CalendarPage"> <ContentPage.Resources> <local:GraphDateTimeTimeZoneConverter x:Key="DateConverter" /> </ContentPage.Resources> <ContentPage.Content> <StackLayout> <ListView x:Name="CalendarList" HasUnevenRows="true" Margin="10,10,10,10"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Margin="10,10,10,10"> <Label Text="{Binding Path=Subject}" FontAttributes="Bold" FontSize="Medium" /> <Label Text="{Binding Path=Organizer.EmailAddress.Name}" FontSize="Small" /> <StackLayout Orientation="Horizontal"> <Label Text="{Binding Path=Start, Converter={StaticResource DateConverter}}" FontSize="Micro" /> <Label Text="to" FontSize="Micro" /> <Label Text="{Binding Path=End, Converter={StaticResource DateConverter}}" FontSize="Micro" /> </StackLayout> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage.Content> </ContentPage> <!-- </CalendarPageXamlSnippet> -->
Dies ersetzt die
Editor
durch eineListView
. Das zum Rendern der einzelnen Elemente verwendete ElementDataTemplate
verwendet das zum Konvertieren der Eigenschaften desGraphDateTimeTimeZoneConverter
Start
End
Ereignisses in eine Zeichenfolge.Öffnen Sie CalendarPage.xaml.cs, und entfernen Sie die folgenden Zeilen aus der
OnAppearing
Funktion.// Temporary JSONResponse.Text = JsonConvert.SerializeObject(events.CurrentPage, Formatting.Indented);
Fügen Sie an ihrer Stelle den folgenden Code hinzu.
// Add the events to the list view CalendarList.ItemsSource = events.CurrentPage.ToList();
Führen Sie die App aus, melden Sie sich an, und klicken Sie auf das Navigationselement Kalender. Die Liste der Ereignisse mit formatierten Start- und Endwerten sollte angezeigt werden.
Erstellen eines neuen Ereignisses
In diesem Abschnitt fügen Sie die Möglichkeit hinzu, Ereignisse im Kalender des Benutzers zu erstellen.
Öffnen Sie "NewEventPage.xaml", und ersetzen Sie den Inhalt durch Folgendes.
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> <!-- <NewEventPageXamlSnippet> --> <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="GraphTutorial.NewEventPage"> <ContentPage.Content> <AbsoluteLayout> <ScrollView AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All"> <StackLayout Spacing="10" Margin="10"> <Label Text="Subject" /> <Entry Text="{Binding Subject}" /> <Label Text="Attendees" /> <Entry Text="{Binding Attendees}" Placeholder="Add multiple email addresses separated by a semicolon (';')" /> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto" /> <ColumnDefinition Width="auto" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <Label Text="Start" /> <DatePicker Grid.Row="2" Date="{Binding StartDate, Mode=TwoWay}" /> <TimePicker Grid.Row="2" Grid.Column="2" Time="{Binding StartTime}" /> <Label Text="End" Grid.Row="3" /> <DatePicker Grid.Row="4" Date="{Binding EndDate, Mode=TwoWay}" /> <TimePicker Grid.Row="4" Grid.Column="2" Time="{Binding EndTime}" /> </Grid> <Label Text="Body" /> <Editor HeightRequest="200" Text="{Binding Body}" /> <Button Text="Create" Clicked="CreateEvent" IsEnabled="{Binding IsValid}" /> </StackLayout> </ScrollView> <StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All" IsVisible="{Binding IsWorking}"> <ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" IsRunning="{Binding IsWorking}" /> </StackLayout> </AbsoluteLayout> </ContentPage.Content> </ContentPage> <!-- </NewEventPageXamlSnippet> -->
Öffnen Sie "NewEventPage.xaml.cs", und fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using System.ComponentModel; using Microsoft.Graph;
Fügen Sie die INotifyPropertyChange-Schnittstelle der NewEventPage-Klasse hinzu. Ersetzen Sie die vorhandene Klassendeklaration durch Folgendes.
[XamlCompilation(XamlCompilationOptions.Compile)] public partial class NewEventPage : ContentPage, INotifyPropertyChanged { public NewEventPage() { InitializeComponent(); BindingContext = this; } }
Fügen Sie der NewEventPage-Klasse die folgenden Eigenschaften hinzu.
// Value of the Subject text box private string _subject = ""; public string Subject { get { return _subject; } set { _subject = value; OnPropertyChanged(); IsValid = true; } } // Value of the Attendees text box private string _attendees = ""; public string Attendees { get { return _attendees; } set { _attendees = value; OnPropertyChanged(); } } // Value of the Start date picker private DateTime _startDate = DateTime.Today; public DateTime StartDate { get { return _startDate; } set { _startDate = value; OnPropertyChanged(); IsValid = true; } } // Value of the Start time picker private TimeSpan _startTime = TimeSpan.Zero; public TimeSpan StartTime { get { return _startTime; } set { _startTime = value; OnPropertyChanged(); IsValid = true; } } // Value of the End date picker private DateTime _endDate = DateTime.Today; public DateTime EndDate { get { return _endDate; } set { _endDate = value; OnPropertyChanged(); IsValid = true; } } // Value of the End time picker private TimeSpan _endTime = TimeSpan.Zero; public TimeSpan EndTime { get { return _endTime; } set { _endTime = value; OnPropertyChanged(); IsValid = true; } } // Value of the Body text box private string _body = ""; public string Body { get { return _body; } set { _body = value; OnPropertyChanged(); } } // Combine the date from date picker with time from time picker private DateTimeOffset CombineDateAndTime(DateTime date, TimeSpan time) { // Use the year, month, and day from the supplied DateTimeOffset // to create a new DateTime at midnight var dt = new DateTime(date.Year, date.Month, date.Day); // Add the TimeSpan, and use the user's timezone offset return new DateTimeOffset(dt + time, App.UserTimeZone.BaseUtcOffset); } // Combined value of Start date and time pickers public DateTimeOffset Start { get { return CombineDateAndTime(StartDate, StartTime); } } // Combined value of End date and time pickers public DateTimeOffset End { get { return CombineDateAndTime(EndDate, EndTime); } } public bool IsValid { get { // Subject is required, Start must be before // End return !string.IsNullOrWhiteSpace(Subject) && DateTimeOffset.Compare(Start, End) < 0; } private set { // Only used to fire event, value // is always calculated OnPropertyChanged(); } } private bool _isWorking = false; public bool IsWorking { get { return _isWorking; } set { _isWorking = value; OnPropertyChanged(); } }
Fügen Sie den folgenden Code hinzu, um das Ereignis zu erstellen.
private async void CreateEvent(object sender, EventArgs e) { IsWorking = true; var timeZoneString = Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.UWP ? App.UserTimeZone.StandardName : App.UserTimeZone.DisplayName; // Initialize a new Event object with the required fields var newEvent = new Event { Subject = Subject, Start = new DateTimeTimeZone { DateTime = Start.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss"), TimeZone = timeZoneString }, End = new DateTimeTimeZone { DateTime = End.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss"), TimeZone = timeZoneString } }; // If there's a body, add it if (!string.IsNullOrEmpty(Body)) { newEvent.Body = new ItemBody { Content = Body, ContentType = BodyType.Text }; } if (!string.IsNullOrEmpty(Attendees)) { var attendeeList = new List<Attendee>(); // Treat any unrecognized text as a list of email addresses var emails = Attendees.Split(new[] { ';', ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (var email in emails) { try { // Validate the email address var addr = new System.Net.Mail.MailAddress(email); if (addr.Address == email) { attendeeList.Add(new Attendee { Type = AttendeeType.Required, EmailAddress = new EmailAddress { Address = email } }); } } catch { /* Invalid, skip */ } } if (attendeeList.Count > 0) { newEvent.Attendees = attendeeList; } } await App.GraphClient.Me.Events.Request().AddAsync(newEvent); await DisplayAlert("Success", "Event created.", "OK"); IsWorking = false; }
Speichern Sie die Änderungen, und führen Sie die App aus. Melden Sie sich an, wählen Sie das Menüelement "Neues Ereignis" aus, füllen Sie das Formular aus, und wählen Sie "Erstellen" aus, um dem Kalender des Benutzers ein Ereignis hinzuzufügen.
Herzlichen Glückwunsch!
Sie haben das Xamarin Microsoft Graph Lernprogramm abgeschlossen. Nachdem Sie nun über eine funktionierende App verfügen, die Microsoft Graph aufruft, können Sie experimentieren und neue Features hinzufügen. Besuchen Sie die Übersicht über Microsoft Graph, um alle Daten anzuzeigen, auf die Sie mit Microsoft Graph zugreifen können.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub-Repository.
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.