2018 年 1 月

第 33 卷,第 1 期

本文章是由機器翻譯。

Office - 使用 Microsoft Graph 和 Azure Functions 建置組織的 API

Mike Ammerlaan |2018 年 1 月

如果您認為您的組織為應用程式開發介面,將它外觀為何? 

您可能會開始與人員 — 組織的核心,以及適用的角色與功能達成。這類人員經常會分組為定義並與虛擬小組完成工作及專案中。您將圖層上資源,包括讓人運作及它們用來完成工作的工具。接著新增處理程序,並使用活動,這些可能是應用程式開發介面中的方法?"WidgetMarketingTeam.runCampaign() 」 可能是過於末來而多半極簡單的但儘管如此,API,以您的組織,您會得到很好深入了解您的組織運作方式與無法轉換產能藉由建置更有效率程序和工具。

索引鍵提供每個資源以一致的方式,和互連邏輯上,如此您就可以建立完整的處理程序,以配合的方式每個人和小組想要使用。更多的 Api 您可以將結合在一起並連接,更有用截至目前為止您建置的產品 net 組可能是 — 大於其各部分的總和。

因此,我們提供 Microsoft Graph — 的 API,跨組織內的索引鍵資料集,且可讓您一起提取所需的所有轉換工作的執行方式。此外,Microsoft Graph 搭配取用者服務,例如 OneDrive 和郵件 (Outlook.com),讓您轉換個人生產力。

解決問題的應用程式開發介面蔓延到

為組織的使用中的軟體系統集而有所不同 (implementation)。對於開發人員,每個提供唯一的結構,通常與一組不同的應用程式開發介面、 驗證需求和樣式的互動。通常,軟體專案的主要挑戰只橋接這些不同的系統,以提供較高層級的深入解析,而且可包含抽象化出不同的應用程式開發介面和精通個別的驗證配置。

個別應用程式開發介面,從不同的產品小組在過去,-microsoft 在這個案例中,會以不同的方式運作,需要跨產品整合。即使在五年以前,取得使用者的完整設定檔和相片的程序需要圖說文字 (若要取得個人資訊) 的 Exchange 應用程式開發介面和 SharePoint 應用程式開發介面 (若要從使用者的受管理的設定檔取得相片)。每個具有自己的驗證、 API 配置,並且有不同需求。如果您然後想要經理的相關資訊嗎?會包含查詢取得的組織階層的第三個系統。這些作業所有可能提取在一起,但它們只需要。

Microsoft Graph 而誕生若要解決此問題。透過統一的資料和驗證,以及讓系統更一致,net Api 集變得更容易且更適合使用。Microsoft Graph 綜覽不同系統從組織的代表索引鍵的 facet 和公司中的函式。自前的兩年,Microsoft Graph 已經繼續成長中強大的功能和功能,其中它真的可以做為基礎的 API,為您的組織。

Microsoft Graph 的核心是一組使用者,通常所有員工中組織的帳戶。簡化的集中式群組是新興的概念,Microsoft graph,通常從一份使用者的其他安全性群組。群組可以有一組相關聯的資源,例如 Microsoft 小組交談式工作區、 規劃工作面板和文件庫和檔案的 SharePoint 網站。從工作的各種工具,表示使用者和群組,包括檔案中所示的磁碟機 API,透過規劃應用程式開發介面、 內送郵件的使用者和群組、 連絡人、 行事曆,以及更多,工作透過圖 1

產能應用程式開發介面的 Microsoft 圖形
圖 1 的產能應用程式開發介面的 Microsoft 圖形

經過一段時間,Microsoft graph Api 透過已加入新功能。保存自訂中繼資料,以及 Microsoft Graph 中的項目新功能可以讓您能夠深,請簡化自訂這些項目。現在群組不再是只群組-群組無法與其他中繼資料描述主題、 講師和計時,代表教育機構中的類別。您可以使用此中繼資料來執行查詢,然後 — 例如,找出代表科學類別的所有群組。或者,您無法從您的系統中加入內 Microsoft Graph 的相關實體的識別碼,Microsoft Graph 到連接您的系統。

Microsoft Graph 也超出提供建立、 讀取、 更新和刪除 (CRUD) Api 的核心物件。主要功能是一層的使用者工作時,會產生在幕後的深入資訊。比方說,雖然圖形包含完整的組織階層與群組的集合,這些可能不一定形成小組的運作方式的最佳表示。透過分析的工作,您可以取得一份使用者可能會連線的最密切相關的人員 (虛擬小組) 和檔案。此外,常用的公用程式,例如用於尋找可用的會議的時間之間的一組使用者,都可以做為方法。

Azure 的函式

Microsoft Graph 存在使用和自訂的更廣泛的系統和程序。為簡單的 REST API 和結合各種 Sdk,Microsoft Graph 的設計是直接使用。合理的選擇來建立處理程序及整合 Microsoft graph 是 Azure 函式 (functions.azure.com),可以讓您加入程式碼時只支付累加式程式碼,所以您需要的位置正確指出哪個區塊使用。Azure 的函式都支援的語言,包含 C# 和 Node.js 開發。

最近一組新的 Azure 功能與整合可讓您更輕鬆地連接到 Microsoft Graph。Azure 函式繫結擴充功能,現在可供預覽 Azure 函式 2.0 執行階段,會自動執行的一些常見使用 Microsoft Graph,包括驗證和使用 webhook 訂閱的工作。

讓我們看看開始與 Microsoft Graph 使用的範例。

建立工作,透過 Azure 函式

假設您想要有管理員檢閱並核准的小組成員所執行的動作。使用者工作是要求使用者執行動作的一種方法,來轉換和追蹤人力的動作。在此情況下,我想要實作會將工作指派給使用者的管理員建立的簡單 Web 服務。 

在任何 Microsoft Graph 專案中的第一個點通常是圖表總管。可讓您快速模型 Microsoft Graph 呼叫應用程式的網站瀏覽其結果,而完全無法相信您可能會執行所有的圖表總管。可從developer.microsoft.com/graph,圖表總管可讓您使用唯讀示範租用戶或登入您自己的租用戶。您可以使用您的組織帳戶登入,並直接存取您自己的資料。我們建議使用開發人員租用戶,可從 Office 開發人員程式在dev.office.com/devprogram。這會提供不同的租用戶,其中您可以放心進行實驗與您的開發。

在此情況下,您可以輸入兩個簡單的 Url,以查看您將在此範例中進行的呼叫的類型。您想要檢查 「 取得使用者的管理員 」 的第一次,您可以藉由選取 [取得我的專案經理] 顯示的範例,在圖表總管中看到圖 2。這背後的 URL 會顯示在執行的查詢欄位中。

選取的結果取得我的專案經理
圖 2 結果的選取 [取得我的專案經理

作業的第二個部分是建立規劃工作。在圖表總管中,您可以展開範例,若要加入的規劃工作的範例的集合。在此範例會將,請參閱建立規劃工作的作業 (的 POST https://graph.microsoft.com/v1.0/planner/tasks)。

既然您了解所涉及的 Web 服務要求,您可以建立使用 Azure 函式的函式。

若要開始,建立新的 Azure 函式應用程式。一般情況下,您會想要遵循的指示aka.ms/azfnmsgraph來開始這項來達成。簡單地說,因為新的 Azure 函式繫結延伸模組功能處於預覽狀態,則您將需要 Azure 函式應用程式切換到 2.0 preview ("beta") 的執行階段。您還需要安裝 Microsoft 圖形擴充功能,以及設定應用程式服務驗證。

當您設定 Microsoft Graph 應用程式註冊,您必須新增一些其他的權限以支援此範例中讀取管理員資訊和工作,包括:

  • CRUD 使用者工作與專案 (Tasks.ReadWrite)
  • 檢視使用者的基本設定檔 (設定檔)
  • 讀取和寫入所有的群組 (Group.ReadWrite.All)
  • 讀取所有使用者的基本設定檔 (User.ReadBasic.All)

您會想要充分利用 Azure 函式繫結延伸模組來處理驗證,請確定您有已驗證的存取權杖,您可以存取 Microsoft Graph Api 的 Microsoft 圖形。若要這樣做,您將建立的標準 HTTP C# 觸發程序。在下方整合,選取 [進階編輯器] 並使用所示的繫結圖 3。這將需要使用者登入、 驗證及核准您的應用程式,才可以使用。

圖 3 建立 HTTP 觸發程序來處理驗證

{
  "bindings": [
    {
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "type": "token",
      "direction": "in",
      "name": "accessToken",
      "resource": "https://graph.microsoft.com",
      "identity": "userFromRequest"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

函式的程式碼所示圖 4。請注意,您必須設定呼叫 PlanId,具有您想要用於您的工作計劃者計劃的識別項的函式應用程式的環境變數。這可以透過完成應用程式設定為函式的應用程式。

圖 4 張貼工作指派給使用者的管理員 Azure 函式的來源

#r "Newtonsoft.Json"
using System.Net;
using System.Threading.Tasks;
using System.Configuration;
using System.Net.Mail;
using System.IO;
using System.Web;
using System.Text;
using Newtonsoft.Json.Linq;
public static HttpResponseMessage Run(HttpRequestMessage req, string accessToken, TraceWriter log)
{
  log.Info("Processing incoming task creation requests.");
  // Retrieve data from query string
  // Expected format is taskTitle=task text&taskBucket=bucket
  // title&taskPriority=alert
  var values = HttpUtility.ParseQueryString(req.RequestUri.Query);
  string taskTitle = values["taskTitle"];
  string taskBucket = values["taskBucket"];
  string taskPriority = values["taskPriority"];
  if (String.IsNullOrEmpty(taskTitle))
  {
    log.Info("Incomplete request received - no title.");
    return new HttpResponseMessage(HttpStatusCode.BadRequest);
  }
  string planId = System.Environment.GetEnvironmentVariable("PlanId");
  // Retrieve the incoming users' managers ID
  string managerJson = GetJson(
    "https://graph.microsoft.com/v1.0/me/manager/", accessToken, log);
    dynamic manager = JObject.Parse(managerJson);
  string managerId = manager.id;
  string appliedCategories = "{}";
  if (taskPriority == "alert" || taskPriority == "1")
  {
    appliedCategories = "{ \"category1\": true }";
  }
  else
  {
    appliedCategories = "{ \"category2\": true }";
  }
  string now =  DateTime.UtcNow.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz");
  string due =  DateTime.UtcNow.AddDays(5).ToString(
    "yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz");
  string bucketId = "";
  // If the incoming request wants to place a task in a bucket,
  // find the bucket ID to add it to
  if (!String.IsNullOrEmpty(taskBucket))
  {
    // Retrieve a list of planner buckets so that you can match
    // the task to a bucket, where possible
    string bucketsJson = GetJson(
      "https://graph.microsoft.com/v1.0/planner/plans/" + planId +
      "/buckets", accessToken, log);
    if (!String.IsNullOrEmpty(bucketsJson))
    {
      dynamic existingBuckets = JObject.Parse(bucketsJson);
      taskBucket = taskBucket.ToLower();
      foreach (var bucket in existingBuckets.value)
      {
        var existingBucketTitle = bucket.name.ToString().ToLower();
        if (taskBucket.IndexOf(existingBucketTitle) >= 0)
        {
          bucketId = ", \"bucketId\": \"" + bucket.id.ToString() + "\"";
        }
      }
    }
  }
  string jsonOutput = String.Format(" {{ \"planId\": \"{0}\", \"title\": \"{1}\", \"orderHint\": \" !\", \"startDateTime\": \"{2}\", \"dueDateTime\": \"{6}\", \"appliedCategories\": {3}, \"assignments\": {{ \"{4}\": {{ \"@odata.type\": \"#microsoft.graph.plannerAssignment\",  \"orderHint\": \" !\"  }} }}{5} }}",
    planId, taskTitle, now, appliedCategories, managerId, bucketId, due);
  log.Info("Creating new task: " + jsonOutput);
  PostJson("https://graph.microsoft.com/v1.0/planner/tasks",
    jsonOutput, accessToken, log);
  return new HttpResponseMessage(HttpStatusCode.OK);
}
private static string GetJson(string url, string token, TraceWriter log)
{
  HttpWebRequest hwr = (HttpWebRequest)WebRequest.CreateHttp(url);
  log.Info("Getting Json from endpoint '" + url + "'");
  hwr.Headers.Add("Authorization", "Bearer " + token);
  hwr.ContentType = "application/json";
  WebResponse response = null;
  try
  {
    response = hwr.GetResponse();
    using (Stream stream = response.GetResponseStream())
    {
      using (StreamReader sr = new StreamReader(stream))
      {
        return sr.ReadToEnd();
      }
     }
  }
  catch (Exception e)
  {
    log.Info("Error: " + e.Message);
  }
  return null;
}
private static string PostJson(string url, string body, string token, TraceWriter log)
{
  HttpWebRequest hwr = (HttpWebRequest)WebRequest.CreateHttp(url);
  log.Info("Posting to endpoint " + url);
  hwr.Method = "POST";
  hwr.Headers.Add("Authorization", "Bearer " + token);
  hwr.ContentType = "application/json";
  var postData = Encoding.UTF8.GetBytes(body.ToString());
  using (var stream = hwr.GetRequestStream())
  {
  stream.Write(postData, 0, postData.Length);
  }
  WebResponse response = null;
  try
  {
    response = hwr.GetResponse();
    using (Stream stream = response.GetResponseStream())
    {
      using (StreamReader sr = new StreamReader(stream))
      {
        return sr.ReadToEnd();
      }
    }
  }
  catch (Exception e)
  {
    log.Info("Error: " + e.Message);
  }
  return null;
}

這個範例會示範如何您可以拉一起的一段與一個驗證權杖的程式碼中的資料 (使用者的管理員和計劃者工作在此情況下) 的不同集合。建立和指派工作是磁碟機活動的常見方式跨小組,讓動態建立的工作,並運用現有的計劃者經驗的功能非常有用。它不是相當"widgetMarketingTeam.launchCampaign ()",但至少可以查看來建立的工作會收到的小組已取得焦點,結構化啟動的入門集。

處理在 OneDrive 中的檔案

您可以執行的另一個工作是處理使用者的 OneDrive 存在的檔案。在本例中,您利用 Azure 函式繫結延伸模組針對 Microsoft Graph 執行準備檔案,以供使用的工作。您將它傳遞至認知的服務應用程式開發介面執行語音辨識。這是資料處理可以取得更多的值超出檔 OneDrive 和 SharePoint 的實用方式的範例。

若要開始,您會遵循一些相同的步驟,如同先前的範例,包括設定函式應用程式和 Azure Active Directory 註冊。請注意,您在此範例使用 Azure Active Directory 應用程式註冊必須擁有 「 讀取所有檔案,該使用者可以存取"(Files.Read.All) 權限。您還需要有認知 Services 語音 API 金鑰,您可以從取得aka.ms/tryspeechapi

如往常一般,開始使用 Azure 函式繫結延伸模組,並設定新 HTTP C# 觸發程序。在您的函式的 [整合] 索引標籤中,使用中顯示的繫結標記圖 5連接您的函式繫結延伸。在此情況下,繫結延伸模組會傳回 myOneDriveFile 參數結合 Azure onedrive 繫結延伸函式中。

圖 5 設定新的觸發程序,來取得 OneDrive 上的檔案

{
  "bindings": [
    {
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "myOneDriveFile",
      "type": "onedrive",
      "direction": "in",
      "path": "{query.filename}",
      "identity": "userFromRequest",
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

現在,它是時間的程式碼,這將顯示於圖 6

圖 6 記錄從一個磁碟機的音訊檔案

#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using System.Configuration;
using Newtonsoft.Json.Linq;
public static  async Task<HttpResponseMessage> Run(HttpRequestMessage req,
  Stream myOneDriveFile, TraceWriter log)
{
  // Download the contents of the audio file
  log.Info("Downloading audio file contents...");
  byte[] audioBytes;
  audioBytes = StreamToBytes(myOneDriveFile);
  // Transcribe the file using cognitive services APIs
  log.Info($"Retrieving the cognitive services access token...");
  var accessToken =
    System.Environment.GetEnvironmentVariable("SpeechApiKey");
  var bingAuthToken = await FetchCognitiveAccessTokenAsync(accessToken);
  log.Info($"Transcribing the file...");
  var transcriptionValue = await RequestTranscriptionAsync(
    audioBytes, "en-us", bingAuthToken, log);
  HttpResponseMessage hrm = new HttpResponseMessage(HttpStatusCode.OK);
  if (null != transcriptionValue)
  {
    hrm.Content = new StringContent(transcriptionValue, Encoding.UTF8, "text/html");
  }
  else
  {
    hrm.Content = new StringContent("Content could not be transcribed.");
  }
  return hrm;
}
private static async Task<string> RequestTranscriptionAsync(byte[] audioBytes,
  string languageCode, string authToken, TraceWriter log)
{
  string conversation_url = $"https://speech.platform.bing.com/speech/recognition/conversation/cognitiveservices/v1?language={languageCode}";
  string dictation_url = $"https://speech.platform.bing.com/speech/recognition/dictation/cognitiveservices/v1?language={languageCode}";
  HttpResponseMessage response = null;
  string responseJson = "default";
  try
  {
    response = await PostAudioRequestAsync(conversation_url, audioBytes, authToken);
    responseJson = await response.Content.ReadAsStringAsync();
    JObject data = JObject.Parse(responseJson);
    return data["DisplayText"].ToString();
  }
  catch (Exception ex)
  {
    log.Error($"Unexpected response from transcription service A: {ex.Message} |" +
      responseJson + "|" + response.StatusCode  + "|" +
      response.Headers.ToString() +"|");
    return null;
  }
}
private static async Task<HttpResponseMessage> PostAudioRequestAsync(
  string url, byte[] bodyContents, string authToken)
{
  var payload = new ByteArrayContent(bodyContents);
  HttpResponseMessage response;
  using (var client = new HttpClient())
  {
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + authToken);
    payload.Headers.TryAddWithoutValidation("content-type", "audio/wav");
    response = await client.PostAsync(url, payload);
  }
  return response;
}
private static byte[] StreamToBytes(Stream stream)
{
  using (MemoryStream ms = new MemoryStream())
  {
    stream.CopyTo(ms);
    return ms.ToArray();
  }
}
private static async Task<string> FetchCognitiveAccessTokenAsync(
  string subscriptionKey)
{
  string fetchUri = "https://api.cognitive.microsoft.com/sts/v1.0";
  using (var client = new HttpClient())
  {
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
    UriBuilder uriBuilder = new UriBuilder(fetchUri);
    uriBuilder.Path += "/issueToken";
    var response = await client.PostAsync(uriBuilder.Uri.AbsoluteUri, null);
    return await response.Content.ReadAsStringAsync();
  }
}

就地以此函式,使用者已登入其 Azure 功能之後, 他們就可以指定檔案名稱參數。如果有檔案。WAV 檔名包含英文和內容中,這會取得 transcribed 成英文文字。這使用 Azure 函式實作,因為您的函式將通常會產生成本只有在呼叫,以便提供彈性的方式來擴充您擁有 Microsoft 圖形中的資料。

Azure 函數 + Microsoft Graph

此處所提供的兩個範例示範如何建置人性化和技術的程序,Microsoft Graph 中的資料之上。結合 Microsoft Graph,並且能夠跨工作負載的涵蓋範圍 (例如組織階層和工作,做為工作範例,在本文中使用的情況下),您可以建立並將值加入整個組織。Microsoft Graph 和 Azure 函式結合,可讓您建置完整的 API,以您的組織,並轉換所有的產能。開始建置解決方案,為您的組織中造訪developer.microsoft.com/graph,與使用 Azure 功能functions.azure.com


Mike Ammerlaan是主管行銷 Microsoft Office 生態系統的小組的產品的協助人員建置與 Office 365 更吸引人的解決方案。在此之前,他曾在 Microsoft 擔任程式管理員 18 多年來,開發產品如 SharePoint、 Excel、 Yammer、 Bing 地圖服務和戰鬥飛行模擬器。

非常感謝下列 Microsoft 技術專家檢閱這篇文章:  作者 Ryan、 Matthew 韓和 Dan 銀級


MSDN Magazine 論壇中的這篇文章的討論