在此部分中,你将 Microsoft Graph 合并到应用程序中。In this section you will incorporate Microsoft Graph into the application. 对于此应用程序,你将使用 适用于 .NET 的 Microsoft Graph 客户端库调用 Microsoft Graph。For this application, you will use the Microsoft Graph Client Library for .NET to make calls to Microsoft Graph.

获取日历视图Get a calendar view

日历视图是用户日历中两个时间点之间发生的一组事件。A calendar view is a set of events from the user's calendar that occur between two points of time. 你将使用它获取当前一周的用户事件。You'll use this to get the user's events for the current week.

  1. 打开 ./Controllers/CalendarController.cs, 将以下函数添加到 CalendarController 类。Open ./Controllers/CalendarController.cs and add the following function to the CalendarController class.

    private DateTime GetUtcStartOfWeekInTimeZone(DateTime today, string timeZoneId)
    {
        // Time zone returned by Graph could be Windows or IANA style
        // TimeZoneConverter can take either
        TimeZoneInfo userTimeZone = TZConvert.GetTimeZoneInfo(timeZoneId);
    
        // 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, userTimeZone);
    }
    
  2. 添加以下函数以处理从 Microsoft Graph 调用返回的异常。Add the following function to handle exceptions returned from Microsoft Graph calls.

    private async Task HandleGraphException(Exception exception)
    {
        if (exception is MicrosoftIdentityWebChallengeUserException)
        {
            _logger.LogError(exception, "Consent required");
            // This exception indicates consent is required.
            // Return a 403 with "consent_required" in the body
            // to signal to the tab it needs to prompt for consent
            HttpContext.Response.ContentType = "text/plain";
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            await HttpContext.Response.WriteAsync("consent_required");
        }
        else if (exception is ServiceException)
        {
            var serviceException = exception as ServiceException;
            _logger.LogError(serviceException, "Graph service error occurred");
            HttpContext.Response.ContentType = "text/plain";
            HttpContext.Response.StatusCode = (int)serviceException.StatusCode;
            await HttpContext.Response.WriteAsync(serviceException.Error.ToString());
        }
        else
        {
            _logger.LogError(exception, "Error occurred");
            HttpContext.Response.ContentType = "text/plain";
            HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            await HttpContext.Response.WriteAsync(exception.ToString());
        }
    }
    
  3. 将现有的 Get 函数替换为以下内容。Replace the existing Get function with the following.

    [HttpGet]
    public async Task<IEnumerable<Event>> Get()
    {
        // This verifies that the access_as_user scope is
        // present in the bearer token, throws if not
        HttpContext.VerifyUserHasAnyAcceptedScope(apiScopes);
    
        // To verify that the identity libraries have authenticated
        // based on the token, log the user's name
        _logger.LogInformation($"Authenticated user: {User.GetDisplayName()}");
    
        try
        {
            // Get the user's mailbox settings
            var me = await _graphClient.Me
                .Request()
                .Select(u => new {
                    u.MailboxSettings
                })
                .GetAsync();
    
            // Get the start and end of week in user's time
            // zone
            var startOfWeek = GetUtcStartOfWeekInTimeZone(
                DateTime.Today, me.MailboxSettings.TimeZone);
            var endOfWeek = startOfWeek.AddDays(7);
    
            // Set the start and end of the view
            var viewOptions = new List<QueryOption>
            {
                new QueryOption("startDateTime", startOfWeek.ToString("o")),
                new QueryOption("endDateTime", endOfWeek.ToString("o"))
            };
    
            // Get the user's calendar view
            var results = await _graphClient.Me
                .CalendarView
                .Request(viewOptions)
                // Send user time zone in request so date/time in
                // response will be in preferred time zone
                .Header("Prefer", $"outlook.timezone=\"{me.MailboxSettings.TimeZone}\"")
                // Get max 50 per request
                .Top(50)
                // Only return fields app will use
                .Select(e => new
                {
                    e.Subject,
                    e.Organizer,
                    e.Start,
                    e.End,
                    e.Location
                })
                // Order results chronologically
                .OrderBy("start/dateTime")
                .GetAsync();
    
            return results.CurrentPage;
        }
        catch (Exception ex)
        {
            await HandleGraphException(ex);
            return null;
        }
    }
    

    查看更改。Review the changes. 此函数的新版本:This new version of the function:

    • 返回 IEnumerable<Event> 而不是 stringReturns IEnumerable<Event> instead of string.
    • 使用 Microsoft Graph 获取用户的邮箱设置。Gets the user's mailbox settings using Microsoft Graph.
    • 使用用户的时区计算本周的开始和结束。Uses the user's time zone to calculate the start and end of the current week.
    • 获取日历视图Gets a calendar view
      • 使用函数包含标头,这将导致返回的事件的开始时间和结束时间转换为 .Header() Prefer: outlook.timezone 用户的时区。Uses the .Header() function to include a Prefer: outlook.timezone header, which causes the returned events to have their start and end times converted to the user's timezone.
      • 使用 .Top() 函数最多请求 50 个事件。Uses the .Top() function to request at most 50 events.
      • 使用 .Select() 函数仅请求应用使用的字段。Uses the .Select() function to request just the fields used by the app.
      • 使用 OrderBy() 函数按开始时间对结果进行排序。Uses the OrderBy() function to sort the results by the start time.
  4. 保存更改并重新启动该应用。Save your changes and restart the app. 刷新 Microsoft Teams 中的选项卡。Refresh the tab in Microsoft Teams. 应用显示事件的 JSON 列表。The app displays a JSON listing of the events.

显示结果Display the results

现在,你可以以更用户友好的方式显示事件列表。Now you can display the list of events in a more user friendly way.

  1. 打开 ./Pages/Index.cshtml, 在标记中添加以下 <script> 函数。Open ./Pages/Index.cshtml and add the following functions inside the <script> tag.

    function renderSubject(subject) {
      if (!subject || subject.length <= 0) {
        subject = '<No subject>';
      }
    
      return $('<div/>', {
          class: 'ms-fontSize-18 ms-fontWeight-bold',
          text: subject
      });
    }
    
    function renderOrganizer(organizer) {
      return $('<div/>', {
        class: 'ms-fontSize-14 ms-fontWeight-semilight',
        text: organizer.emailAddress.name
      }).append($('<i/>', {
        class: 'ms-Icon ms-Icon--PartyLeader',
        style: 'margin-right: 10px;'
      }));
    }
    
    function renderTimeSpan(start, end) {
      return $('<div/>', {
        class: 'ms-fontSize-14 ms-fontWeight-semilight',
        text: `${formatDateTime(start.dateTime)} - ${formatDateTime(end.dateTime)}`
      }).append($('<i/>', {
        class: 'ms-Icon ms-Icon--DateTime2',
        style: 'margin-right: 10px;'
      }));
    }
    
    function formatDateTime(dateTime) {
      const date = new Date(dateTime);
    
      // Format like 10/14/2020 4:00 PM
      let hours = date.getHours();
      const minutes = date.getMinutes();
      const ampm = hours >= 12 ? 'PM' : 'AM';
      hours = hours % 12;
      hours = hours ? hours : 12;
      const minStr = minutes < 10 ? `0${minutes}` : minutes;
    
      return `${date.getMonth()+1}/${date.getDate()}/${date.getFullYear()} ${hours}:${minStr} ${ampm}`;
    }
    
    function renderLocation(location) {
      if (!location || location.displayName.length <= 0) {
        return null;
      }
    
      return $('<div/>', {
        class: 'ms-fontSize-14 ms-fontWeight-semilight',
        text: location.displayName
      }).append($('<i/>', {
        class: 'ms-Icon ms-Icon--MapPin',
        style: 'margin-right: 10px;'
      }));
    }
    
  2. 将现有的 renderCalendar 函数替换为以下内容。Replace the existing renderCalendar function with the following.

    function renderCalendar(events) {
      $('#tab-container').empty();
    
      // Add title
      $('<div/>', {
        class: 'tab-title ms-fontSize-42',
        text: 'Week at a glance'
      }).appendTo('#tab-container');
    
      // Render each event
      events.map(event => {
        const eventCard = $('<div/>', {
          class: 'event-card ms-depth-4',
        });
    
        eventCard.append(renderSubject(event.subject));
        eventCard.append(renderOrganizer(event.organizer));
        eventCard.append(renderTimeSpan(event.start, event.end));
    
        const location = renderLocation(event.location);
        if (location) {
          eventCard.append(location);
        }
    
        eventCard.appendTo('#tab-container');
      });
    }
    
  3. 保存更改并重新启动该应用。Save your changes and restart the app. 刷新 Microsoft Teams 中的选项卡。Refresh the tab in Microsoft Teams. 应用在用户日历上显示事件。The app displays events on the user's calendar.

    显示用户日历的应用屏幕截图