Schreiben einer ASP.NET-MVC-Web-App zum Abrufen von Outlook-Mail, -Kalender und -KontaktenWrite an ASP.NET MVC Web app to get Outlook mail, calendar, and contacts

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer einfachen ASP.NET-MVC-C#-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.The purpose of this guide is to walk through the process of creating a simple ASP.NET MVC C# app that retrieves messages in Office 365 or Outlook.com. The source code in this repository is what you should end up with if you follow the steps outlined here.

In diesem Lernprogramm wird die Microsoft-Authentifizierungsbibliothek (MSAL) für OAuth2-Aufrufe und die Microsoft Graph-Clientbibliothek für Aufrufe der Mail-API verwendet.This tutorial will use the Microsoft Authentication Library (MSAL) to make OAuth2 calls and the Microsoft Graph Client Library to call the Mail API. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte.Microsoft recommends using Microsoft Graph to access Outlook mail, calendar, and contacts. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das in den Graph-Endpunkten nicht verfügbar ist.This guide will use the Microsoft Graph to access Outlook mail. Microsoft recommends using the Microsoft Graph to access Outlook mail, calendar, and contacts. You should use the Outlook APIs directly (via https://outlook.office.com/api) only if you require a feature that is not available on the Graph endpoints. Eine Version dieses Beispiels mit Verwendung der Outlook-APIs finden Sie in dieser Verzweigung.For a version of this sample that uses the Outlook APIs, see this branch.

In diesem Leitfaden wird davon ausgegangen, dass Sie bereits Visual Studio 2013 oder Visual Studio 2015 installiert haben und auf Ihrem Entwicklungscomputer ausführen.This guide assumes that you already have Visual Studio 2013 or Visual Studio 2015 installed and working on your development machine.

Erstellen der AppCreate the app

Lassen Sie uns direkt loslegen. Erstellen Sie in Visual Studio eine neue Visual C#-ASP.NET-Webanwendung mithilfe von .NET Framework 4.5. Nennen Sie die Anwendung dotnet-tutorial.Let's dive right in! In Visual Studio, create a new Visual C# ASP.NET Web Application using .NET Framework 4.5. Name the application dotnet-tutorial.

Das Fenster „Neues Projekt“ in Visual Studio.

Wählen Sie die Vorlage MVC aus. Klicken Sie auf die Schaltfläche Authentifizierung ändern, und wählen Sie „Keine Authentifizierung“ aus. Deaktivieren Sie das Kontrollkästchen „Host in der Cloud“. Das Dialogfeld sollte wie folgt aussehen:Select the MVC template. Click the Change Authentication button and choose "No Authentication". Un-select the "Host in the cloud" checkbox. The dialog should look like the following.

Das Fenster zur Vorlagenauswahl in Visual Studio.

Klicken Sie auf „OK“, damit Visual Studio das Projekt erstellt. Führen Sie danach das Projekt aus, um sicherzustellen, dass alles ordnungsgemäß funktioniert, indem Sie F5 drücken oder Debuggen starten aus dem Menü Debuggen auswählen. Es sollte ein geöffneter Browser angezeigt werden, in dem die vordefinierte ASP.NET-Startseite angezeigt wird. Schließen Sie den Browser.Click OK to have Visual Studio create the project. Once that's done, run the project to make sure everything's working properly by pressing F5 or choosing Start Debugging from the Debug menu. You should see a browser open displaying the stock ASP.NET home page. Close your browser.

Da wir nun bestätigt haben, dass die App funktioniert, können wir uns an die echte Arbeit machen.Now that we've confirmed that the app is working, we're ready to do some real work.

Entwerfen der AppDesigning the app

Unsere App ist sehr einfach. Wenn ein Benutzer die Website besucht, sieht er eine Schaltfläche zum Anmelden und Anzeigen seiner E-Mails. Beim Klicken auf die Schaltfläche gelangt er zur Azure-Anmeldeseite, wo er sich mit seinem Office 365- oder Outlook.com-Konto anmelden kann und Zugriff auf unsere App erhält. Schließlich wird er zurück zu unserer App geleitet, die eine Liste der neuen E-Mails im Posteingang des Benutzers anzeigt.Our app will be very simple. When a user visits the site, they will see a button to log in and view their email. Clicking that button will take them to the Azure login page where they can login with their Office 365 or Outlook.com account and grant access to our app. Finally, they will be redirected back to our app, which will display a list of the most recent email in the user's inbox.

Beginnen wir nun, indem wir die vordefinierte Startseite durch eine einfachere ersetzen. Öffnen Sie die Datei ./Views/Home/Index.cshtml. Ersetzen Sie den vorhandenen Code durch den folgenden Code.Let's begin by replacing the stock home page with a simpler one. Open the ./Views/Home/Index.cshtml file. Replace the existing code with the following code.

Inhalte der Datei ./Views/Home/Index.cshtmlContents of the ./Views/Home/Index.cshtml file

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>ASP.NET MVC Tutorial</h1>
    <p class="lead">This sample app uses the Mail API to read messages in your inbox.</p>
    <p><a href="#" class="btn btn-primary btn-lg">Click here to login</a></p>
</div>

Dies ist im Wesentlichen eine Umfunktionierung des jumbotron-Element aus der vordefinierten Startseite; alle anderen Elemente werden entfernt. Die Schaltfläche hat noch keine Funktion, die Startseite sollte aber folgendermaßen aussehen.This is basically repurposing the jumbotron element from the stock home page, and removing all of the other elements. The button doesn't do anything yet, but the home page should now look like the following.

Startseite der Beispiel-App.

Wir werden jetzt auch die vordefinierte Fehlerseite ändern, damit eine Fehlermeldung übergeben und angezeigt werden kann. Ersetzen Sie den Inhalt von ./Views/Shared/Error.cshtml durch den folgenden Code.Let's also modify the stock error page so that we can pass an error message and display it. Replace the contents of ./Views/Shared/Error.cshtml with the following code.

Inhalte der Datei ./Views/Shared/Error.cshtmlContents of the ./Views/Shared/Error.cshtml file

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Error</title>
</head>
<body>
    <hgroup>
        <h1>Error.</h1>
        <h2>An error occurred while processing your request.</h2>
    </hgroup>
    <div class="alert alert-danger">@ViewBag.Message</div>
    @if (!string.IsNullOrEmpty(ViewBag.Debug))
    {
    <pre><code>@ViewBag.Debug</code></pre>
    }
</body>
</html>

Fügen Sie schließlich eine Aktion zu der HomeController-Klasse hinzu, um die Fehleransicht aufzurufen.Finally add an action to the HomeController class to invoke the error view.

Die Error-Aktion in ./Controllers/HomeController.csThe Error action in ./Controllers/HomeController.cs

public ActionResult Error(string message, string debug)
{
    ViewBag.Message = message;
    ViewBag.Debug = debug;
    return View("Error");
}

Registrieren der AppRegister the app

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:New app registrations should be created and managed in the new Application Registration Portal to be compatible with Outlook.com. Only create new app registrations in the Azure Management Portal if your app:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oderUses the OAuth2 Client Credentials Grant Flow, or
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)Needs to access other Office 365 workloads besides Outlook (such as OneDrive for Business or SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können.Bear in mind that apps registered using the Azure Management Portal will not be compatible with Outlook.com, and will not be able to dynamically request permissions scopes. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365.Existing app registrations that were created in the Azure Management Portal will continue to work for Office 365 only. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.These registrations do not show up in the Application Registration Portal and must be managed in the Azure Management Portal.

KontoanforderungenAccount requirements

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:In order to use the Application Registration Portal, you need either an Office 365 work or school account, or a Microsoft account. If you don't have either of these, you have a number of options:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.Sign up for a new Microsoft account here.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:You can obtain an Office 365 subscription in a couple of different ways:

REST-API-VerfügbarkeitREST API availability

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.The REST API is currently enabled on all Office 365 accounts that have Exchange Online, and all Outlook.com accounts.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID und einen geheimen Schlüssel abzurufen.Head over to the Application Registration Portal to quickly get an application ID and secret.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.Using the Sign in link, sign in with either your Microsoft account (Outlook.com), or your work or school account (Office 365).
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie dotnet-tutorial für den Namen ein, und klicken Sie auf Anwendung erstellen.Click the Add an app button. Enter dotnet-tutorial for the name and click Create application.
  3. Suchen Sie den Abschnitt Anwendungsgeheimnisse, und klicken Sie auf die Schaltfläche Neues Kennwort generieren. Kopieren Sie nun das Kennwort, und speichern Sie es an einem sicheren Ort. Nachdem Sie das Kennwort kopiert haben, klicken Sie auf Ok.Locate the Application Secrets section, and click the Generate New Password button. Copy the password now and save it to a safe place. Once you've copied the password, click Ok.
  4. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Web aus und geben Sie dann http://localhost:<PORT> ein; ersetzen Sie dabei <PORT> durch die Portnummer, die Ihr Projekt verwendet, unter Umleitungs-URIs.Locate the Platforms section, and click Add Platform. Choose Web, then enter http://localhost:<PORT>, replacing <PORT> with the port number your project is using under Redirect URIs.

    Tipp

    Die Portnummer, die Ihr Projekt verwendet, finden Sie, indem Sie das Projekt im Projektmappen-Explorer auswählen und dann den Wert der URL unter Entwicklungsserver im Fenster Eigenschaften überprüfen.You can find the port number your project is using by selecting the project in Solution Explorer, then checking the value of URL under Development Server in the Properties window.

  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die App-ID, und speichern Sie sie zusammen mit dem Kennwort, das Sie zuvor kopiert haben. Wir benötigen diese Werte bald.Click Save to complete the registration. Copy the Application Id and save it along with the password you copied earlier. We'll need those values soon.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.Here's what the details of your app registration should look like when you are done.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2Implementing OAuth2

Unser Ziel in diesem Abschnitt ist es, den Link auf unserer Homepage so einzurichten, dass der OAuth2-Autorisierungscodegenehmigungs-Fluss mit Azure AD initiiert wird. Der Einfachheit halber verwenden wir die Microsoft-Authentifizierungsbibliothek (MSAL), um unsere OAuth-Anforderungen zu verarbeiten.Our goal in this section is to make the link on our home page initiate the OAuth2 Authorization Code Grant flow with Azure AD. To make things easier, we'll use the Microsoft Authentication Library (MSAL) to handle our OAuth requests.

Zunächst erstellen wir eine separate Konfigurationsdatei, die die OAuth-Einstellungen für die App enthält.First, let's create a separate config file to hold the OAuth settings for the app. Klicken Sie mit der rechten Maustaste auf die dotnet-tutorial-Projektmappe im Projektmappen-Explorer, wählen Sie Hinzufügen und dann Neues Element. Wählen Sie Webkonfigurationsdatei, und geben Sie dann AzureOauth.config im Feld Name ein.Right-click the dotnet-tutorial solution in Solution Explorer, choose Add, then New Item.... Select Web Configuration File, then enter AzureOauth.config in the Name field. Klicken Sie auf Hinzufügen.Click Add.

Öffnen Sie die Datei AzureOauth.config, und ersetzen Sie ihren Inhalt durch Folgendes:Open the AzureOauth.config file and replace its entire contents with the following.

<appSettings>
    <add key="ida:AppID" value="YOUR APP ID" />
    <add key="ida:AppPassword" value="YOUR APP PASSWORD" />
    <add key="ida:RedirectUri" value="http://localhost:10800" />
    <add key="ida:AppScopes" value="User.Read Mail.Read" />
</appSettings>

Ersetzen Sie den Wert des ida:AppID-Schlüssels durch die oben generierte Anwendungs-ID, und ersetzen Sie den Wert des ida:AppPassword-Schlüssels durch das oben generierte Kennwort. Wenn der Wert der URI-Umleitung anders lautet, müssen Sie den Wert von ida:RedirectUri aktualisieren.Replace the value of the ida:AppID key with the application ID you generated above, and replace the value of the ida:AppPassword key with the password you generated above. If the value of your redirect URI is different, be sure to update the value of ida:RedirectUri.

Öffnen Sie jetzt die Datei Web.config.Now open the Web.config file. Suchen Sie die Zeile mit dem <appSettings>-Element, und ändern Sie es in Folgendes:Find the line with the <appSettings> element, and change it to the following.

<appSettings file="AzureOauth.config">

Dies führt dazu, dass ASP.NET die Schlüssel aus der AzureOauth.config-Datei zur Laufzeit hinzufügt.This will cause ASP.NET to add the keys from the AzureOauth.config file at runtime. Dadurch, dass sich diese Werte in einer separaten Datei befinden, ist es weniger wahrscheinlich, dass sie aus Versehen an die Quellcodeverwaltung übergeben werden.By keeping these values in a separate file, we make it less likely that we'll accidentally commit them to source control.

Der nächste Schritt besteht darin, die OWIN-Middleware, die MSAL und die Graph-Bibliotheken von NuGet zu installieren. Wählen Sie im Visual Studio-Menü Tools den NuGet-Paket-Manager und dann die Paket-Manager-Konsole aus. Geben Sie die folgenden Befehle in der Paket-Manager-Konsole ein, um die OWIN Middleware-Bibliotheken zu installieren:The next step is to install the OWIN middleware, MSAL, and Graph libraries from NuGet. On the Visual Studio Tools menu, choose NuGet Package Manager, then Package Manager Console. To install the OWIN middleware libraries, enter the following commands in the Package Manager Console:

Install-Package Microsoft.Owin.Security.OpenIdConnect
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Host.SystemWeb

Installieren Sie als Nächstes die Microsoft-Authentifizierungsbibliothek mit dem folgenden Befehl:Next install the Microsoft Authentication Library with the following command:

Install-Package Microsoft.Identity.Client -Pre

Installieren Sie schließlich die Microsoft Graph-Clientbibliothek mit dem folgenden Befehl:Finally install the Microsoft Graph Client Library with the following command:

Install-Package Microsoft.Graph

Zurück zur CodierungBack to coding

Jetzt sind wir bereit, um uns um die Anmeldung zu kümmern. Beginnen wir mit dem Vebinden der OWIN-Middleware mit unserer App. Klicken Sie mit der rechten Maustaste auf den Ordner App_Start im Projekt-Explorer, und wählen Sie Hinzufügen und dann Neues Element aus. Wählen Sie die Vorlage für die OWIN-Startklasse aus, geben Sie der Datei den Namen Startup.cs, und klicken Sie auf Hinzufügen. Ersetzen Sie den gesamten Inhalt dieser Datei durch den folgenden Code.Now we're all set to do the sign in. Let's start by wiring the OWIN middleware to our app. Right-click the App_Start folder in Project Explorer and choose Add, then New Item. Choose the OWIN Startup Class template, name the file Startup.cs, and click Add. Replace the entire contents of this file with the following code.

using System;
using System.Configuration;
using System.IdentityModel.Claims;
using System.IdentityModel.Tokens;
using System.Threading.Tasks;
using System.Web;

using Microsoft.IdentityModel.Protocols;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;

using Owin;


[assembly: OwinStartup(typeof(dotnet_tutorial.App_Start.Startup))]

namespace dotnet_tutorial.App_Start
{
    public class Startup
    {
        public static string appId = ConfigurationManager.AppSettings["ida:AppId"];
        public static string appPassword = ConfigurationManager.AppSettings["ida:AppPassword"];
        public static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
        public static string[] scopes = ConfigurationManager.AppSettings["ida:AppScopes"]
          .Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

        public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
              new OpenIdConnectAuthenticationOptions
              {
                  ClientId = appId,
                  Authority = "https://login.microsoftonline.com/common/v2.0",
                  Scope = "openid offline_access profile email " + string.Join(" ", scopes),
                  RedirectUri = redirectUri,
                  PostLogoutRedirectUri = "/",
                  TokenValidationParameters = new TokenValidationParameters
                  {
                      // For demo purposes only, see below
                      ValidateIssuer = false

                      // In a real multitenant app, you would add logic to determine whether the
                      // issuer was from an authorized tenant
                      //ValidateIssuer = true,
                      //IssuerValidator = (issuer, token, tvp) =>
                      //{
                      //  if (MyCustomTenantValidation(issuer))
                      //  {
                      //    return issuer;
                      //  }
                      //  else
                      //  {
                      //    throw new SecurityTokenInvalidIssuerException("Invalid issuer");
                      //  }
                      //}
                  },
                  Notifications = new OpenIdConnectAuthenticationNotifications
                  {
                      AuthenticationFailed = OnAuthenticationFailed,
                      AuthorizationCodeReceived = OnAuthorizationCodeReceived
                  }
              }
            );
        }

        private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage,
          OpenIdConnectAuthenticationOptions> notification)
        {
            notification.HandleResponse();
            string redirect = "/Home/Error?message=" + notification.Exception.Message;
            if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
            {
                redirect += "&debug=" + notification.ProtocolMessage.ErrorDescription;
            }
            notification.Response.Redirect(redirect);
            return Task.FromResult(0);
        }

        private Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
        {
            notification.HandleResponse();
            notification.Response.Redirect("/Home/Error?message=See Auth Code Below&debug=" + notification.Code);
            return Task.FromResult(0);
        }
    }
}

Wir fügen nun eine SignIn-Aktion zu der HomeController-Klasse hinzu. Öffnen Sie die Datei .\Controllers\HomeController.cs. Fügen Sie die folgenden Zeilen im oberen Bereich der Datei hinzu:Let's continue by adding a SignIn action to the HomeController class. Open the .\Controllers\HomeController.cs file. At the top of the file, add the following lines:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.Graph;

Fügen Sie jetzt eine neue Methode namens SignIn zu der HomeController-Klasse hinzu.Now add a new method called SignIn to the HomeController class.

Die SignIn-Aktion in ./Controllers/HomeController.csSignIn action in ./Controllers/HomeController.cs

public void SignIn()
{
    if (!Request.IsAuthenticated)
    {
        // Signal OWIN to send an authorization request to Azure
        HttpContext.GetOwinContext().Authentication.Challenge(
            new AuthenticationProperties { RedirectUri = "/" },
            OpenIdConnectAuthenticationDefaults.AuthenticationType);
    }
}

Nun aktualisieren wir schließlich die Startseite so, dass die Anmeldeschaltfläche die SignIn-Aktion aufruft.Finally, let's update the home page so that the login button invokes the SignIn action.

Aktualisierte Inhalte der Datei ./Views/Home/Index.cshtmlUpdated contents of the ./Views/Home/Index.cshtml file

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>ASP.NET MVC Tutorial</h1>
    <p class="lead">This sample app uses the Mail API to read messages in your inbox.</p>
    <p><a href="@Url.Action("SignIn", "Home", null, Request.Url.Scheme)" class="btn btn-primary btn-lg">Click here to login</a></p>
</div>

Speichern Sie Ihre Arbeit, und führen Sie die App aus. Klicken Sie auf die Schaltfläche zum Anmelden. Nach der Anmeldung sollten Sie auf die Fehlerseite umgeleitet werden, auf der ein Autorisierungscode angezeigt wird. Führen wir nun eine Aktion damit aus.Save your work and run the app. Click on the button to sign in. After signing in, you should be redirected to the error page, which displays an authorization code. Now let's do something with it.

Austauschen des Codes durch ein TokenExchanging the code for a token

Aktualisieren wir nun die OnAuthorizationCodeReceived-Funktion zum Abrufen eines Tokens. Fügen Sie in ./App_Start/Startup.cs die folgenden Zeilen im oberen Bereich der Datei hinzu:Now let's update the OnAuthorizationCodeReceived function to retrieve a token. In ./App_Start/Startup.cs, add the following line to the top of the file:

using Microsoft.Identity.Client;

Ersetzen Sie die aktuelle OnAuthorizationCodeReceived-Funktion durch Folgendes.Replace the current OnAuthorizationCodeReceived function with the following.

Aktualisierte OnAuthorizationCodeReceived-Aktion in ./App_Start/Startup.csUpdated OnAuthorizationCodeReceived action in ./App_Start/Startup.cs

// Note the function signature is changed!
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
    ConfidentialClientApplication cca = new ConfidentialClientApplication(
        appId, redirectUri, new ClientCredential(appPassword), null, null);

    string message;
    string debug;

    try
    {
        var result = await cca.AcquireTokenByAuthorizationCodeAsync(notification.Code, scopes);
        message = "See access token below";
        debug = result.AccessToken;
    }
    catch (MsalException ex)
    {
        message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
        debug = ex.Message;
    }

    notification.HandleResponse();
    notification.Response.Redirect("/Home/Error?message=" + message + "&debug=" + debug);
}

Speichern Sie die Änderungen, und starten Sie die App neu. Dieses Mal sollte nach der Anmeldung ein Zugriffstoken angezeigt werden. Da wir nun ein Zugriffstoken abrufen können, benötigen wir eine Möglichkeit, um das Token zu speichern, damit die App es verwenden kann. Die MSAL-Bibliothek definiert eine TokenCache-Schnittstelle, die wir zum Speichern der Token verwenden können. Da es sich um ein Lernprogramm handelt, implementieren wir nun einen grundlegenden Cache, der in der Sitzung gespeichert wird.Save your changes and restart the app. This time, after you sign in, you should see an access token displayed. Now that we can retrieve the access token, we need a way to save the token so the app can use it. The MSAL library defines a TokenCache interface, which we can use to store the tokens. Since this is a tutorial, we'll just implement a basic cache stored in the session.

Erstellen Sie einen neuen Ordner im Projekts namens TokenStorage. Fügen Sie eine Klasse in diesem Ordner mit dem Namen SessionTokenCache hinzu, und ersetzen Sie den Inhalt dieser Datei durch den folgenden Code (geliehen von https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-v2)).Create a new folder in the project called TokenStorage. Add a class in this folder named SessionTokenCache, and replace the contents of that file with the following code (borrowed from https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-v2).

Inhalte der Datei ./TokenCache/SessionTokenCache.csContents of the ./TokenCache/SessionTokenCache.cs file

using System.Threading;
using System.Web;

using Microsoft.Identity.Client;

namespace dotnet_tutorial.TokenStorage
{
    // Adapted from https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-v2
    public class SessionTokenCache
    {
        private static ReaderWriterLockSlim sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        string userId = string.Empty;
        string cacheId = string.Empty;
        HttpContextBase httpContext = null;

        TokenCache tokenCache = new TokenCache();

        public SessionTokenCache(string userId, HttpContextBase httpContext)
        {
            this.userId = userId;
            cacheId = userId + "_TokenCache";
            this.httpContext = httpContext;
            Load();
        }

        public TokenCache GetMsalCacheInstance()
        {
            tokenCache.SetBeforeAccess(BeforeAccessNotification);
            tokenCache.SetAfterAccess(AfterAccessNotification);
            Load();
            return tokenCache;
        }

        public bool HasData()
        {
            return (httpContext.Session[cacheId] != null && ((byte[])httpContext.Session[cacheId]).Length > 0);
        }

        public void Clear()
        {
            httpContext.Session.Remove(cacheId);
        }

        private void Load()
        {
            sessionLock.EnterReadLock();
            tokenCache.Deserialize((byte[])httpContext.Session[cacheId]);
            sessionLock.ExitReadLock();
        }

        private void Persist()
        {
            sessionLock.EnterReadLock();

            // Optimistically set HasStateChanged to false. 
            // We need to do it early to avoid losing changes made by a concurrent thread.
            tokenCache.HasStateChanged = false;

            httpContext.Session[cacheId] = tokenCache.Serialize();
            sessionLock.ExitReadLock();
        }

        // Triggered right before ADAL needs to access the cache. 
        private void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            // Reload the cache from the persistent store in case it changed since the last access. 
            Load();
        }

        // Triggered right after ADAL accessed the cache.
        private void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if the access operation resulted in a cache update
            if (tokenCache.HasStateChanged)
            {
                Persist();
            }
        }
    }
}

Aktualisieren wir nun die OnAuthorizationCodeReceived-Funktion, um den neuen Cache zu verwenden.Now let's update the OnAuthorizationCodeReceived function to use our new cache.

Fügen Sie die folgenden Zeilen im oberen Bereich der Datei Startup.cs hinzu:Add the following lines to the top of the Startup.cs file:

using dotnet_tutorial.TokenStorage;

Ersetzen Sie die aktuelle OnAuthorizationCodeReceived-Funktion durch die folgende.Replace the current OnAuthorizationCodeReceived function with this one.

Neue OnAuthorizationCodeReceived in ./App_Start/Startup.csNew OnAuthorizationCodeReceived in ./App_Start/Startup.cs

private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
    // Get the signed in user's id and create a token cache
    string signedInUserId = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
    SessionTokenCache tokenCache = new SessionTokenCache(signedInUserId, 
        notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase);

    ConfidentialClientApplication cca = new ConfidentialClientApplication(
        appId, redirectUri, new ClientCredential(appPassword), tokenCache.GetMsalCacheInstance(), null);

    try
    {
        var result = await cca.AcquireTokenByAuthorizationCodeAsync(notification.Code, scopes);
    }
    catch (MsalException ex)
    {
        string message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
        string debug = ex.Message;
        notification.HandleResponse();
        notification.Response.Redirect("/Home/Error?message=" + message + "&debug=" + debug);
    }
}

Da es in dieser Sitzung ums Speichern geht, fügen wir auch eine SignOut-Aktion zu der HomeController-Klasse hinzu. Fügen Sie die folgenden Zeilen im oberen Bereich der Datei HomeController.cs hinzu:Since we're saving stuff in the session, let's also add a SignOut action to the HomeController class. Add the following lines to the top of the HomeController.cs file:

using dotnet_tutorial.TokenStorage;

Fügen Sie dann die SignOut-Aktion hinzu.Then add the SignOut action.

Die SignOut-Aktion in ./Controllers/HomeController.csSignOut action in ./Controllers/HomeController.cs

public void SignOut()
{
    if (Request.IsAuthenticated)
    {
        string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;

        if (!string.IsNullOrEmpty(userId))
        {
            // Get the user's token cache and clear it
            SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);
            tokenCache.Clear();
        }
    }
    // Send an OpenID Connect sign-out request. 
    HttpContext.GetOwinContext().Authentication.SignOut(
        CookieAuthenticationDefaults.AuthenticationType);
    Response.Redirect("/");
}

Wenn Sie nun die App neu starten und sich anmelden, werden Sie bemerken, dass Sie die App zurück zur Startseite leitet, ohne sichtbares Ergebnis. Aktualisieren wir nun die Startseite so, dass diese sich in Abhängigkeit davon ändert, ob der Benutzer angemeldet ist oder nicht.Now if you restart the app and sign in, you'll notice that the app redirects back to the home page, with no visible result. Let's update the home page so that it changes based on if the user is signed in or not.

Zuerst wollen wir die Index-Aktion in HomeController.cs aktualisieren, um den Anzeigenamen des Benutzers anzuzeigen. Ersetzen Sie die vorhandene Index-Funktion durch den folgenden Code.First let's update the Index action in HomeController.cs to get the user's display name. Replace the existing Index function with the following code.

Aktualisierte Index-Aktion in ./Controllers/HomeController.csUpdated Index action in ./Controllers/HomeController.cs

public ActionResult Index()
{
    if (Request.IsAuthenticated)
    {
        string userName = ClaimsPrincipal.Current.FindFirst("name").Value;
        string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
        if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(userId))
        {
            // Invalid principal, sign out
            return RedirectToAction("SignOut");
        }

        // Since we cache tokens in the session, if the server restarts
        // but the browser still has a cached cookie, we may be
        // authenticated but not have a valid token cache. Check for this
        // and force signout.
        SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);
        if (!tokenCache.HasData())
        {
            // Cache is empty, sign out
            return RedirectToAction("SignOut");
        }

        ViewBag.UserName = userName;
    }
    return View();
}

Ersetzen Sie als Nächstes den vorhandenen Index.cshtml-Code durch den folgenden Code.Next, replace the existing Index.cshtml code with the following.

Aktualisierte Inhalte der Datei ./Views/Home/Index.cshtmlUpdated contents of ./Views/Home/Index.cshtml

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>ASP.NET MVC Tutorial</h1>
    <p class="lead">This sample app uses the Mail API to read messages in your inbox.</p>
    @if (Request.IsAuthenticated)
    {
        <p>Welcome, @(ViewBag.UserName)!</p>
    }
    else
    {
        <p><a href="@Url.Action("SignIn", "Home", null, Request.Url.Scheme)" class="btn btn-primary btn-lg">Click here to login</a></p>
    }
</div>

Zum Schluss fügen wir nun einige Menüelemente zur Navigationsleiste hinzu, wenn der Benutzer angemeldet ist. Öffnen Sie die Datei ./Views/Shared/_Layout.cshtml. Suchen Sie die folgenden Codezeilen:Finally, let's add some new menu items to the navigation bar if the user is signed in. Open the ./Views/Shared/_Layout.cshtml file. Locate the following lines of code:

<ul class="nav navbar-nav">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

Ersetzen Sie diese Zeilen durch den folgenden Code:Replace those lines with this code:

<ul class="nav navbar-nav">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    @if (Request.IsAuthenticated)
    {
        <li>@Html.ActionLink("Inbox", "Inbox", "Home")</li>
        <li>@Html.ActionLink("Sign Out", "SignOut", "Home")</li>
    }
</ul>

Wenn Sie jetzt Ihre Änderungen speichern und die App neu starten, sollten auf der Startseite der Name des angemeldeten Benutzers und die beiden neuen Menüelemente in der oberen Navigationsleiste angezeigt werden, nachdem Sie sich angemeldet haben. Da wir nun den angemeldeten Benutzer und ein Zugriffstoken haben, können wir die Mail-API aufrufen.Now if you save your changes and restart the app, the home page should display the logged on user's name and show two new menu items in the top navigation bar after you sign in. Now that we've got the signed in user and an access token, we're ready to call the Mail API.

Verwenden der Mail-APIUsing the Mail API

Beginnen wir mit dem Hinzufügen einer neuen Funktion zur HomeController-Klasse, um das Zugriffstoken des Benutzers zu erhalten. In dieser Funktion verwenden wir MSAL und unseren Tokencache. Wenn ein gültiges nicht abgelaufenes Token im Cache vorhanden ist, gibt MSAL dieses zurück. Falls dieses abgelaufen ist, wird das Token von MSAL aktualisiert.Let's start by adding a new function to the HomeController class to get the user's access token. In this function we'll use MSAL and our token cache. If there is a valid non-expired token in the cache, MSAL will return it. If it is expired, MSAL will refresh the token for us.

Fügen Sie die folgenden using-Anweisungen in ./Controllers/HomeController.cs hinzu.Add the following using statements to ./Controllers/HomeController.cs.

using System.Configuration;
using System.Net.Http.Headers;

GetAccessToken-Funktion in ./Controllers/HomeController.csGetAccessToken function in ./Controllers/HomeController.cs

public async Task<string> GetAccessToken()
{
    string accessToken = null;

    // Load the app config from web.config
    string appId = ConfigurationManager.AppSettings["ida:AppId"];
    string appPassword = ConfigurationManager.AppSettings["ida:AppPassword"];
    string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
    string[] scopes = ConfigurationManager.AppSettings["ida:AppScopes"]
        .Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

    // Get the current user's ID
    string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;

    if (!string.IsNullOrEmpty(userId))
    {
        // Get the user's token cache
        SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);

        ConfidentialClientApplication cca = new ConfidentialClientApplication(
            appId, redirectUri, new ClientCredential(appPassword), tokenCache.GetMsalCacheInstance(), null);

        // Call AcquireTokenSilentAsync, which will return the cached
        // access token if it has not expired. If it has expired, it will
        // handle using the refresh token to get a new one.
        AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes, cca.Users.First());

        accessToken = result.AccessToken;
    }

    return accessToken;
}

Jetzt testen wir unsere neue Funktion. Fügen Sie eine neue Funktion zur HomeController-Klasse mit dem Namen Inbox hinzu.Now let's test our new function. Add a new function to the HomeController class called Inbox.

Die Inbox-Aktion in ./Controllers/HomeController.csInbox action in ./Controllers/HomeController.cs

public async Task<ActionResult> Inbox()
{
    string token = await GetAccessToken();
    if (string.IsNullOrEmpty(token))
    {
        // If there's no token in the session, redirect to Home
        return Redirect("/");
    }

    return Content(string.Format("Token: {0}", token));
}

Wenn Sie die App jetzt ausführen und nach der Anmeldung auf das Menüelement Posteingang klicken, wird das Zugriffstoken im Browser angezeigt. Dieses wollen wir jetzt verwenden.If you run the app now and click the Inbox menu item after logging in, you should see the access token displayed in the browser. Let's put it to use.

Aktualisieren Sie die Inbox-Funktion durch den folgenden Code.Update the Inbox function with the following code.

Aktualisierte Inbox-Aktion in ./Controllers/HomeController.csUpdated Inbox action in ./Controllers/HomeController.cs

public async Task<ActionResult> Inbox()
{
    string token = await GetAccessToken();
    if (string.IsNullOrEmpty(token))
    {
        // If there's no token in the session, redirect to Home
        return Redirect("/");
    }

    GraphServiceClient client = new GraphServiceClient(
        new DelegateAuthenticationProvider(
            (requestMessage) =>
            {
                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", token);

                return Task.FromResult(0);
            }));

    try
    {
        var mailResults = await client.Me.MailFolders.Inbox.Messages.Request()
                            .OrderBy("receivedDateTime DESC")
                            .Select(m => new { m.Subject, m.ReceivedDateTime, m.From})
                            .Top(10)
                            .GetAsync();

        string content = "";

        foreach (var msg in mailResults.CurrentPage)
        {
            content += string.Format("Subject: {0}<br/>", msg.Subject);
        }

        return Content(content);
    }
    catch (ServiceException ex)
    {
        return RedirectToAction("Error", "Home", new { message = "ERROR retrieving messages", debug = ex.Message });
    }
}

Zusammenfassung des neuen Codes in der Inbox-Funktion:To summarize the new code in the Inbox function:

  • Er erstellt ein GraphServiceClient-Objekt.It creates a GraphServiceClient object.
  • Er ändert die Header der ausgehenden Anforderung im DelegateAuthenticationProvider, indem das Zugriffstoken als Authorization-Header hinzugefügt wird.It modifies the outgoing request's headers in the DelegateAuthenticationProvider by adding the access token as an Authorization header.
  • Er gibt eine GET-Anforderung an die URL für die Nachrichten im Posteingang mit den folgenden Merkmalen aus:It issues a GET request to the URL for inbox messages, with the following characteristics:
    • Er verwendet die OrderBy()-Funktion mit einem Wert von receivedDateTime DESC, um die Ergebnisse nach ReceivedDateTime zu sortieren.It uses the OrderBy() function with a value of receivedDateTime DESC to sort the results by ReceivedDateTime.
    • Er verwendet die Select()-Funktion mit einem LINQ-Ausdruck, um die zurückgegebenen Felder auf diejenigen zu beschränken, die wir benötigen.It uses the Select() function with a LINQ expression to limit the fields returned to only those that we need.
  • Er durchläuft die Ergebnisse und druckt den Betreff.It loops over the results and prints out the subject.

Wenn Sie die App jetzt neu starten, sollten Sie eine sehr einfache Auflistung der E-Mail-Betreffe erhalten. Für ein besseres Ergebnis können wir jedoch die MVC-Features verwenden.If you restart the app now, you should get a very basic listing of email subjects. However, we can use the features of MVC to do better than that.

Anzeigen der ErgebnisseDisplaying the results

MVC kann Ansichten basierend auf einem Modell generieren. Beginnen wir daher mit dem Erstellen einer Ansicht basierend auf dem Microsoft.Graph.Message-Objekt. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner ./Views/Home, und wählen Sie Hinzufügen und dann Ansicht aus. Geben Sie Inbox als Ansichtsnamen ein. Ändern Sie das Feld Vorlage in Empty (without model). Belassen Sie für alles andere die Standardwerte, und klicken Sie auf Hinzufügen.MVC can generate views based on a model. So let's start by creating a view based on the Microsoft.Graph.Message object. In Solution Explorer, right-click the ./Views/Home folder and choose Add, then View. Enter Inbox for the View name. Change the Template field to Empty (without model). Leave everything else as default values and click Add.

Das Dialogfeld „Ansicht hinzufügen“.

Öffnen Sie die Datei Inbox.cshtml, die erstellt wird, und ersetzen Sie den gesamten Inhalt der Datei durch den folgenden Code.Open the Inbox.cshtml file that gets created and replace the entire contents of the file with the following code.

@model IEnumerable<Microsoft.Graph.Message>

@{
    ViewBag.Title = "Inbox";
}

<h2>Inbox</h2>

<table class="table">
    <tr>
        <th>
            Subject
        </th>
        <th>
            Received
        </th>
        <th>
            From
        </th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Subject)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReceivedDateTime)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.From.EmailAddress.Name) &lt;@Html.DisplayFor(modelItem => item.From.EmailAddress.Address)&gt;
            </td>
        </tr>
    }

</table>

An diesem Punkt weist Visual Studio auf ein Problem mit item.ReceivedDateTime hin. Wir müssen einen Verweis auf System.Runtime in der Datei Web.config hinzufügen. Öffnen Sie Web.config, und suchen Sie die folgende Zeile:At this point Visual Studio will indicate a problem with item.ReceivedDateTime. We need to add a reference to System.Runtime in the Web.config file. Open Web.config and find the line:

<compilation debug="true" targetFramework="4.5" />

Ersetzen Sie diese Zeile durch den folgenden Code:Replace that line with the following:

<compilation debug="true" targetFramework="4.5">
  <assemblies>
    <add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  </assemblies>
</compilation>

Jetzt müssen wir nur noch eine Sache erledigen. Und zwar wollen wir die Inbox-Funktion so aktualisieren, dass unsere neue Ansicht verwendet wird.Just one more thing to do. Let's update the Inbox function to use our new view.

Aktualisierte Inbox-Aktion in ./Controllers/HomeController.csUpdated Inbox action in ./Controllers/HomeController.cs

public async Task<ActionResult> Inbox()
{
    string token = await GetAccessToken();
    if (string.IsNullOrEmpty(token))
    {
        // If there's no token in the session, redirect to Home
        return Redirect("/");
    }

    GraphServiceClient client = new GraphServiceClient(
        new DelegateAuthenticationProvider(
            (requestMessage) =>
            {
                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", token);

                return Task.FromResult(0);
            }));

    try
    {
        var mailResults = await client.Me.MailFolders.Inbox.Messages.Request()
                            .OrderBy("receivedDateTime DESC")
                            .Select("subject,receivedDateTime,from")
                            .Top(10)
                            .GetAsync();

        return View(mailResults.CurrentPage);
    }
    catch (ServiceException ex)
    {
        return RedirectToAction("Error", "Home", new { message = "ERROR retrieving messages", debug = ex.Message });
    }
}

Die Änderungen sind hier minimal. Anstatt eine Zeichenfolge mit den Ergebnissen zu erstellen, übergeben wir stattdessen die mailResults.CurrentPage-Auflistung an die View-Methode.The changes here are minimal. Instead of building a string with the results, we instead pass the mailResults.CurrentPage collection to the View method.

Speichern Sie die Änderungen, und führen Sie die App aus. Sie sollten jetzt eine Liste von Nachrichten erhalten, die etwa so aussieht.Save your changes and run the app. You should now get a list of messages that looks something like this.

Die Beispiel-App, die den Posteingang eines Benutzers anzeigt.

Hinzufügen von Kalender- und Kontakt-APIsAdding Calendar and Contacts APIs

Da Sie nun das Aufrufen der Outlook-Mail-API gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.Now that you've mastered calling the Outlook Mail API, doing the same for Calendar and Contacts APIs is similar and easy.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrer Sitzung gespeichert. Dieses Token ist nur für den Mail.Read-Bereich gültig. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen. Melden Sie sich unbedingt von der App ab, um die gespeicherten Token zu entfernen, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.If you've followed along with the tutorial, you probably have an access token saved in your session. That token will only be valid for the Mail.Read scope. In order to call the Calendar or Contacts API, we will need to add new scopes. Be sure sign out of the app to get rid of the saved tokens so that you can start the login process from the beginning to get a new access token.

Für die Kalender-API:For Calendar API:

  1. Aktualisieren Sie den Wert ida:AppScopes in AzureOauth.config, um den Calendars.Read-Bereich einzuschließen.Update the ida:AppScopes value in AzureOauth.config to include the Calendars.Read scope.

    <add key="ida:AppScopes" value="User.Read Mail.Read Calendars.Read" />
    
  2. Fügen Sie eine Ansicht für Ereignisse mit dem Namen Calendar hinzu, die die Empty (without model)-Vorlage verwendet. Fügen Sie Calendar.cshtml den folgenden Code hinzu:Add a view for events called Calendar that uses the Empty (without model) template. Add the following code to Calendar.cshtml:

    @model IEnumerable<Microsoft.Graph.Event>
    
    @{
        ViewBag.Title = "Calendar";
    }
    
    <h2>Calendar</h2>
    
    <table class="table">
        <tr>
            <th>
                Subject
            </th>
            <th>
                Start
            </th>
            <th>
                End
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Subject)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Start.DateTime) (@Html.DisplayFor(modelItem => item.Start.TimeZone))
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.End.DateTime) (@Html.DisplayFor(modelItem => item.End.TimeZone))
            </td>
        </tr>
    }
    
    </table>
    
  3. Fügen Sie ein Nagivationselement für die Kalenderansicht zu ./Views/Shared/_Layout.cshtml hinzu.Add a navigation item for the Calendar view to ./Views/Shared/_Layout.cshtml.

    @if (Request.IsAuthenticated)
    {
        <li>@Html.ActionLink("Inbox", "Inbox", "Home")</li>
        <li>@Html.ActionLink("Calendar", "Calendar", "Home")</li>
        <li>@Html.ActionLink("Sign Out", "SignOut", "Home")</li>
    }
    
  4. Fügen Sie der HomeController-Klasse eine Calendar-Funktion hinzu.Add a Calendar function to the HomeController class.

    public async Task<ActionResult> Calendar()
    {
        string token = await GetAccessToken();
        if (string.IsNullOrEmpty(token))
        {
            // If there's no token in the session, redirect to Home
            return Redirect("/");
        }
    
        GraphServiceClient client = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                (requestMessage) =>
                {
                    requestMessage.Headers.Authorization =
                        new AuthenticationHeaderValue("Bearer", token);
    
                    return Task.FromResult(0);
                }));
    
        try
        {
            var eventResults = await client.Me.Events.Request()
                                .OrderBy("start/dateTime DESC")
                                .Select("subject,start,end")
                                .Top(10)
                                .GetAsync();
    
            return View(eventResults.CurrentPage);
        }
        catch (ServiceException ex)
        {
            return RedirectToAction("Error", "Home", new { message = "ERROR retrieving events", debug = ex.Message });
        }
    }
    
  5. Klicken Sie nach der Anmeldung bei der App auf das Element Kalender im oberen Nagivationsmenü.After logging into the app, click the Calendar item in the top navigation menu.

Für die Kontakte-API:For Contacts API:

  1. Aktualisieren Sie den Wert ida:AppScopes in AzureOauth.config, um den Contacts.Read-Bereich einzuschließen.Update the ida:AppScopes value in AzureOauth.config to include the Contacts.Read scope.

    <add key="ida:AppScopes" value="User.Read Mail.Read Contacts.Read" />
    
  2. Fügen Sie eine Ansicht für Ereignisse mit dem Namen Contacts hinzu, die die Empty (without model)-Vorlage verwendet. Fügen Sie Contacts.cshtml den folgenden Code hinzu:Add a view for events called Contacts that uses the Empty (without model) template. Add the following code to Contacts.cshtml:

    @model IEnumerable<Microsoft.Graph.Contact>
    
    @{
        ViewBag.Title = "Contacts";
    }
    
    <h2>Contacts</h2>
    
    <table class="table">
        <tr>
            <th>
                DisplayName
            </th>
            <th>
                Email Address
            </th>
            <th>
                Mobile Phone
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.DisplayName)
            </td>
            <td>
                @Html.Partial("EmailAddresses", item.EmailAddresses)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.MobilePhone)
            </td>
        </tr>
    }
    
    </table>
    
  3. Klicken Sie mit der rechten Maustaste auf den Ordner Ansichten\Freigegeben, wählen Sie Hinzufügen und dann Ansicht. Stellen Sie sicher, dass Sie die Option Als Teilansicht erstellen ausgewählt haben.Right-click the Views\Shared folder, choose Add then View.... Be sure to select the Create as a partial view option. Fügen Sie eine Ansicht für E-Mail-Adressen mit dem Namen EmailAddresses hinzu, die die Empty (without model)-Vorlage verwendet.Add view for email addresses called EmailAddresses that uses the Empty (without model) template. Fügen Sie EmailAddresses.cshtml den folgenden Code hinzu:Add the following code to EmailAddresses.cshtml:

    @model IEnumerable<Microsoft.Graph.EmailAddress>
    
    @foreach(var item in Model)
    {
        <p>@Html.DisplayFor(modelItem => item.Address)</p>
    }
    
  4. Fügen Sie ein Nagivationselement für die Kontakteansicht zu ./Views/Shared/_Layout.cshtml hinzu.Add a navigation item for the Contacts view to ./Views/Shared/_Layout.cshtml.

    @if (Request.IsAuthenticated)
    {
        <li>@Html.ActionLink("Inbox", "Inbox", "Home")</li>
        <li>@Html.ActionLink("Contacts", "Contacts", "Home")</li>
        <li>@Html.ActionLink("Sign Out", "SignOut", "Home")</li>
    }
    
  5. Fügen Sie der HomeController-Klasse eine Contacts-Funktion hinzu.Add a Contacts function to the HomeController class.

    public async Task<ActionResult> Contacts()
    {
        string token = await GetAccessToken();
        if (string.IsNullOrEmpty(token))
        {
            // If there's no token in the session, redirect to Home
            return Redirect("/");
        }
    
        GraphServiceClient client = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                (requestMessage) =>
                {
                    requestMessage.Headers.Authorization =
                        new AuthenticationHeaderValue("Bearer", token);
    
                    return Task.FromResult(0);
                }));
    
        try
        {
            var contactResults = await client.Me.Contacts.Request()
                                .OrderBy("displayName")
                                .Select("displayName,emailAddresses,mobilePhone")
                                .Top(10)
                                .GetAsync();
    
            return View(contactResults.CurrentPage);
        }
        catch (ServiceException ex)
        {
            return RedirectToAction("Error", "Home", new { message = "ERROR retrieving contacts", debug = ex.Message });
        }
    }
    
  6. Klicken Sie nach der Anmeldung bei der App auf das Element Kontakte im oberen Nagivationsmenü.After logging into the app, click the Contacts item in the top navigation menu.