Freigeben über


在 SharePoint 專案中使用 Azure 自訂宣告提供者,第 3 部

英文原文已於 2012 年 2 月 21 日星期二發佈

在本系列的第 1 部 中,我簡要說明了本專案的宗旨,大體上是要使用 Windows Azure 資料表儲存體作為 SharePoint 自訂宣告提供者的資料存放區。此宣告提供者將會使用 CASI 套件 (可能為英文網頁)從 Windows Azure 擷取所需的資料,以提供人員選擇 (例如通訊錄) 及輸入控制項名稱解析功能。

第 2 部中,我將會帶您認識所有於雲端運行的元件,像是用於 Azure 資料表儲存體以及佇列的資料類別、負責讀取佇列中的項目並填入資料表儲存體的工作角色,以及可讓用戶端應用程式能於佇列中建立新項目並處理所有標準 SharePoint 人員選擇工作的 WCF 前端,以提供一份可支援之宣告類型的清單、搜尋宣告值及解析宣告等。

在這系列的最後一部中,我將會帶您看過 SharePoint 這一端所用到的各種不同元件。其中包含一個以 CASI 套件所建立的自訂元件,用來新增項目至佇列,以及呼叫 Azure 資料表儲存體。同時也會包含我們的自訂宣告提供者,使用 CASI 套件元件配合 Azure 功能與 SharePoint 連接。

首先讓我們很快地看一下自訂 CASI 套件元件。我不打算花太多時間在這上面,因為 CASI 套件在本部落格已經討論得很充分了。這個特定的元件在 CASI 套件系列的第 3 部分中有詳細的說明。簡單地說,我所做的就是新建一個 Windows 類別庫專案。我已新增了 CASI 套件基礎類別組件與其他必要 .NET 組件 (在第 3 部分中說明) 的參照。也已在我的專案中新增了一個「服務參考」到我在此專案第 2 部分中所建立的 WCF 端點。最後,我新增了一個新的類別到專案中,並讓它繼承 CASI 套件的基礎類別,接著加入程式碼覆寫掉 ExecuteRequest 方法。就像您在 CASI 套件系列文章中所看到的一樣,我用來覆寫 ExecuteRequest 的程式碼看起來如下:

       public class DataSource : AzureConnect.WcfConfig

       {

              public override bool ExecuteRequest()

              {

                     try

                     {

                           //create the proxy instance with bindings and endpoint the base class

                           //configuration control has created for this

                           AzureClaims.AzureClaimsClient cust =

                                  new AzureClaims.AzureClaimsClient(this.FedBinding,

                                   this.WcfEndpointAddress);

 

                           //configure the channel so we can call it with

                           //FederatedClientCredentials.

                           SPChannelFactoryOperations.ConfigureCredentials<AzureClaims.IAzureClaims>

                                   (cust.ChannelFactory,

                                   Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

                           //create a channel to the WCF endpoint using the

                           //token and claims of the current user

                           AzureClaims.IAzureClaims claimsWCF =

                                  SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

                                  <AzureClaims.IAzureClaims>(cust.ChannelFactory,

                                  this.WcfEndpointAddress,

                                  new Uri(this.WcfEndpointAddress.Uri.AbsoluteUri));

 

                           //set the client property for the base class

                           this.WcfClientProxy = claimsWCF;

                     }

                     catch (Exception ex)

                     {

                           Debug.WriteLine(ex.Message);

                     }

 

                     //now that the configuration is complete, call the method

                     return base.ExecuteRequest();

              }

       }

 

「AzureClaims」是我之前建立的「服務參考」名稱,它使用我在 Azure 中的 WCF 專案所定義的 IAzureClaims 介面。如先前在 CASI 套件系列中所解釋的,這基本上是個樣板程式碼,我只不過是代入出現在 WCF 應用程式中我的介面與類別之名稱而已。我所做的另外一件事 (在 CASI 套件系列中也有提到),是建立稱為 AzureClaimProvider.aspx 的 ASPX 頁面,我只是將我在 CASI 套件系列第 3 部分所說明的程式碼,複製並貼上,取代我的類別名稱與其可接到之端點的名稱。我自訂的 CASI 套件元件之 ASPX 頁面內的控制項標記看起來如下:

<AzWcf:DataSource runat="server" id="wcf" WcfUrl="https://spsazure.vbtoys.com/ AzureClaims.svc" OutputType="Page" MethodName="GetClaimTypes" AccessDeniedMessage="" />

 

此處要注意的重點是,我為 spsazure.vbtoys.com 建立了一個指向我位於 cloudapp.net 的 Azure 應用程式之 CNAME 記錄 (這同樣也在 CASI 套件系列的第 3 部分有說明)。我將該頁面預設要叫用的 MethodName 設成 GetClaimTypes,這個方法不需要參數,且會傳回一份我 Azure 宣告提供者所支援的宣告類型清單。因此這會是一個驗證 Azure 應用程式與 SharePoint 之間連線能力的好方法。我只要前往 https://anySharePointsite/_layouts/AzureClaimProvider.aspx,假如一切都設定正確,我就會在頁面上看到一些資料。待我部署專案之後,也就是將組件新增至「全域組件快取」並部署頁面到我所做的那個 SharePoint 版面配置目錄後,再點擊我其中一個網站的頁面,確認它有傳回資料,就代表我的 SharePoint 和 Azure 之間連線是正常的。

現在任督二脈已打通,我們「總算」來到這專案最炫的地方了,有兩件事要做:

  1. 1.       建立用以傳送有關新使用者的訊息到 Azure 佇列的「某些元件」
  2. 2.       建立自訂宣告提供者,要它使用我的自訂 CASI 套件元件來提供宣告類型、名稱解析及搜尋宣告。

做到這一步,我們應該稍停一會思考一下。針對此特定案例,我只想儘快丟出些成果,因此我的做法是建立一個新的網路應用程式,然後啟用匿名存取。相信大家都知道,光是在網路應用程式的層級上啟用,並不會讓它也在網站集合層級也啟用。所以對此案例,我也只在根網站集合中啟用了它;並授與其本網站中的所有存取權。而所有內含僅限會員使用之資訊的其他網站集合,則並未啟用匿名,所以使用者必須取得授權才能加入。

接下來要考慮的是該如何管理要使用本網站的身分。我當然是懶得做這事。我可以想出好幾種方法將帳號同步到 Azure 或之類的事,但就像我在本系列第 1 部所提到的,已經有好幾種現成的提供者能夠辦到這點,我只要讓它們發揮自身的用途就好了。什麼意思呢?就是我打算利用另一項叫做 ACS (也稱為存取控制服務) 的 Microsoft 雲端服務。簡單的說,ACS 之於 SharePoint 的角色,類似一個身分提供者。因此我的做法就是在 SharePoint 伺服器陣列與我為此 POC 所建立的 ACS 執行個體之間,打造一層信任關係。在 ACS 中我將 SharePoint 新增為一個信賴端,好讓 ACS 知道使用者一經驗證後,該傳送到哪裡。而在 ACS 中,我也設定成允許使用者以他們的 Gmail、Yahoo 或 Facebook 帳號登入。一旦使用者登入之後,ACS 就會取回一個我將要用到的宣告 (也就是電子信箱),然後傳回 SharePoint。

好了,所有該在背景進行的工作都已介紹完畢。Azure 提供資料表儲存體與佇列來處理資料,ACS 負責提供驗證服務,CASI 套件則提供資料傳輸的管路。

懂了我們的傳輸經脈之後,又該怎麼用呢?我還是希望加入會員的手續愈簡易愈好,所以我寫了一個網頁組件來新增使用者到 Azure 佇列。它的作用是檢查要求是否已經過驗證 (例如,使用者按一下某個無名氏網站中的 [登入] 連結,登入到我前面提過的其中一個提供者,ACS 也傳回了他們的宣告資訊)。假如要求未經過驗證,此網頁組件便不會有任何動作。但假設要求已通過驗證,它就會呈現一個按鈕,只要按一下,就會將該使用者的電子郵件宣告加入 Azure 佇列。這就是我說我們應該停下來思考一下的地方。對於 POC 而言這些都沒問題,都可運作。但是您可以想想別種處理這要求的方法。例如您要寫入資訊到 SharePoint 清單好了,您可以寫一個自訂的計時器工作 (其與 CASI 套件配合相當完美),然後定期處理該清單的新要求。您可以用 SPWorkItem 將要求排入佇列中稍後處理。您可將它存入清單中並新增一套工作流程,通過某些認可處理手續,等到要求取得核可,即會採用自訂的工作流程動作,叫用 CASI 套件將詳細資料傳入 Azure 佇列中。總歸一句話,彈性及自訂能力十分強大,請盡情發揮您的想像力。事實上到了某個階段,我可能還會再撰寫另一個版本的流程,將其寫入自訂清單,在某個環節非同步地進行處理,將資料加入 Azure 佇列,然後再自動新增帳號到其中一個子網站的「訪客」群組。如此一來使用者只要一登入,就可以馬上開始動作了。不過如果真要做那些的話,也是另一篇貼文囉。

所以在這樣的前提下,如上所述,使用者登入後,我會提供一個按鈕讓他們點選,然後我會使用自訂的 CASI 套件元件,叫出 WCF 端點,將資訊新增至 Azure 佇列。以面是網頁組件的程式碼,承襲 CASI 套件的優點,它蠻簡單的:

public class AddToAzureWP : WebPart

{

 

       //button whose click event we need to track so that we can

       //add the user to Azure

       Button addBtn = null;

       Label statusLbl = null;

 

       protected override void CreateChildControls()

       {

              if (this.Page.Request.IsAuthenticated)

              {

                     addBtn = new Button();

                     addBtn.Text = "Request Membership";

                     addBtn.Click += new EventHandler(addBtn_Click);

                     this.Controls.Add(addBtn);

 

                     statusLbl = new Label();

                     this.Controls.Add(statusLbl);

              }

       }

 

       void addBtn_Click(object sender, EventArgs e)

       {

              try

              {

                     //look for the claims identity

                     IClaimsPrincipal cp = Page.User as IClaimsPrincipal;

 

                     if (cp != null)

                     {

                           //get the claims identity so we can enum claims

                           IClaimsIdentity ci = (IClaimsIdentity)cp.Identity;

 

                           //look for the email claim

                           //see if there are claims present before running through this

                           if (ci.Claims.Count > 0)

                           {

                                  //look for the email address claim

                                  var eClaim = from Claim c in ci.Claims

                                         where c.ClaimType == "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"

                                         select c;

 

                                  Claim ret = eClaim.FirstOrDefault<Claim>();

 

                                  if (ret != null)

                                  {

                                         //create the string we're going to send to the Azure queue:  claim, value, and display name

                                         //note that I'm using "#" as delimiters because there is only one parameter, and CASI Kit

                                         //uses ; as a delimiter so a different value is needed.  If ; were used CASI would try and

                                         //make it three parameters, when in reality it's only one

                                         string qValue = ret.ClaimType + "#" + ret.Value + "#" + "Email";

 

                                         //create the connection to Azure and upload

                                         //create an instance of the control

                                         AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

                                         //set the properties to retrieve data; must configure cache properties since we're using it programmatically

                                         //cache is not actually used in this case though

                                         cfgCtrl.WcfUrl = AzureCCP.SVC_URI;

                                         cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

                                         cfgCtrl.MethodName = "AddClaimsToQueue";

                                         cfgCtrl.MethodParams = qValue;

                                         cfgCtrl.ServerCacheTime = 10;

                                         cfgCtrl.ServerCacheName = ret.Value;

                                         cfgCtrl.SharePointClaimsSiteUrl = this.Page.Request.Url.ToString();

 

                                         //execute the method

                                         bool success = cfgCtrl.ExecuteRequest();

 

                                         if (success)

                                         {

                                                //if it worked tell the user

                                                statusLbl.Text = "<p>Your information was successfully added.  You can now contact any of " +

                                                       "the other Partner Members or our Support staff to get access rights to Partner " +

                                                       "content.  Please note that it takes up to 15 minutes for your request to be " +

                                                       "processed.</p>";

                                         }

                                         else

                                         {

                                                statusLbl.Text = "<p>There was a problem adding your info to Azure; please try again later or " +

                                                       "contact Support if the problem persists.</p>";

                                         }

                                  }

                           }

                     }

              }

              catch (Exception ex)

              {

                     statusLbl.Text = "There was a problem adding your info to Azure; please try again later or " +

                           "contact Support if the problem persists.";

                     Debug.WriteLine(ex.Message);

              }

       }

}

 

讓我簡要說明一下這串程式碼:首先,我先確認此要求已通過驗證;如果有的話,頁面會多出一個按鈕,而我也為該按鈕的點選事件加入了一個事件處理常式。只要按鈕被點選,事件處理常式就會替我取得目前使用者的 IClaimsPrincipal 參照,接著查看該使用者的宣告集合。我會對此宣告集合執行一個 LINQ 查詢,找出電子信箱宣告,也就是我 SPTrustedIdentityTokenIssuer 的身分識別宣告。如果找到了電子信箱宣告,我就會運用該宣告的宣告類型、宣告值與易記名稱,建立一組連鎖字串。話說在本案例中,這個步驟雖然不是必要的,但我希望它的用途能更廣泛一些,索性便將它編寫成這樣。那組連鎖字串就是我在 WCF 上用以新增資料到 Azure 佇列的方法值。接著我再建立自訂 CASI 套件元件的執行個體,將其設定成會呼叫 WCF 方法來新增資料到佇列,然後我再呼叫 ExecuteRequest 方法,真正地丟出資料。

如果得到回應說資料已成功地新增到佇列中,我會通知使用者成功;假使不成功,我會告訴他們發生問題,可能得稍後再重新檢查一次。在實際的情況下,我還會增加更多的錯誤記錄,以便追蹤確實的情況與原因。不過即使照現在這樣,CASI 套件也會用 SPMonitoredScope 將任何錯誤資訊寫入 ULS 記錄檔中,因此它為此要求所進行的所有動作都會有一個相互關聯的識別碼,供我們查看所有與要求相關的活動。所以這樣看起來,我現在的條件算是滿完善的。

好的,現在整體通道的所有環節我們都走過一遍,同時也示範了資料是如何新增到 Azure 佇列,再由工作處理程序擷取出來,最後加入資料表儲存體。現在可以說我們最重要的目標已經達成,現在可以開始朲談自訂宣告提供者了。它將會用到 CASI 套件來叫出,以及查詢我正在使用的 Azure 資料表儲存體。現在讓我們來看看自訂宣告提供者最有趣的一部份吧。

首先來看看兩個類別層級屬性:

//the WCF endpoint that we'll use to connect for address book functions

//test url:  https://az1.vbtoys.com/AzureClaimsWCF/AzureClaims.svc

//production url:  https://spsazure.vbtoys.com/AzureClaims.svc

public static string SVC_URI = "https://spsazure.vbtoys.com/AzureClaims.svc";

 

//the identity claimtype value

private const string IDENTITY_CLAIM =

       "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";

 

//the collection of claim type we support; it won't change over the course of

//the STS (w3wp.exe) life time so we'll cache it once we get it

AzureClaimProvider.AzureClaims.ClaimTypeCollection AzureClaimTypes =

       new AzureClaimProvider.AzureClaims.ClaimTypeCollection();

 

首先我用一個常數代表 CASI 套件應連接的 WCF 端點。您會注意到我的測試端點與實際執行端點都已具備。當您以程式設計方式使用 CASI 套件時 (就像我們的自訂宣告提供者中的一樣),您一律必須告訴它該交談的 WCF 端點在何處。

接下來,如我之前所述,我要使用電子郵件宣告作為我的身分識別宣告。由於過程中我將會參照它好幾次,我已在類別層級代入了一個常數。

最後,我準備了一個 AzureClaimTypes 集合。我在本系列的第 2 部曾經解釋過為什麼我要使用集合,而我以類別層級儲存於此處是因為這樣我就不必在每次叫用 FillHierarchy 方法時,重新去找一次那些資訊。呼叫 Azure 並不便宜,所以我儘可能減少。

這裡是另一段程式碼:

internal static string ProviderDisplayName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

internal static string ProviderInternalName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

//*******************************************************************

//USE THIS PROPERTY NOW WHEN CREATING THE CLAIM FOR THE PICKERENTITY

internal static string SPTrustedIdentityTokenIssuerName

{

       get

       {

              return "SPS ACS";

       }

}

 

 

public override string Name

{

       get

       {

              return ProviderInternalName;

       }

}

 

我想強調這段程式碼的原因是,既然我的提供者要核發身分識別宣告,它就必須是 SPTrustedIdentityTokenIssuer 的預設提供者。要講解它的做法會有點離題了,但我曾經在我部落格的其他地方提到過。最需要記得的一件事,就是您的提供者名稱必須和 SPTrustedIdentityTokenIssuer 所用的名稱有很強的關聯性。我為 ProviderInternalName 所用的值就是我必須代入 SPTrustedIdentityTokenIssuer 的 ClaimProviderName 屬性之名稱。此外,當我為使用者建立身分識別宣告時,也必須使用 SPTrustedIdentityTokenIssuer 的名稱。所以我建立了一個叫做 SPS ACS 的 SPTrustedIdentityTokenIssuer,並將其加入了 SPTrustedIdentityTokenIssuerName 屬性。這就是我要於此處編碼這些值的原因。

因為我沒有打算在此提供者中增強任何宣告,所以沒有撰寫任何程式碼來覆寫 FillClaimTypes、FillClaimValueTypes 或 FillEntityTypes。下一段程式碼是 FillHierarchy,我會於此處告訴 SharePoint 我所支援的宣告類型為何。程式碼如下:

try

{

       if (

               (AzureClaimTypes.ClaimTypes == null) ||

               (AzureClaimTypes.ClaimTypes.Count() == 0)

       )

       {

              //create an instance of the control

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //set the properties to retrieve data; must configure cache properties since we're using it programmatically

              //cache is not actually used in this case though

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "GetClaimTypes";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = "GetClaimTypes";

              cfgCtrl.SharePointClaimsSiteUrl = context.AbsoluteUri;

 

              //execute the method

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //if it worked, get the list of claim types out

                     AzureClaimTypes =

                                                (AzureClaimProvider.AzureClaims.ClaimTypeCollection)cfgCtrl.QueryResultsObject;

              }

       }

 

       //make sure picker is asking for the type of entity we return; site collection admin won't for example

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       //at this point we have whatever claim types we're going to have, so add them to the hierarchy

       //check to see if the hierarchyNodeID is null; it will be when the control

       //is first loaded but if a user clicks on one of the nodes it will return

       //the key of the node that was clicked on.  This lets you build out a

       //hierarchy as a user clicks on something, rather than all at once

       if (

               (string.IsNullOrEmpty(hierarchyNodeID)) &&

               (AzureClaimTypes.ClaimTypes.Count() > 0)

              )

       {

              //enumerate through each claim type

              foreach (AzureClaimProvider.AzureClaims.ClaimType clm in AzureClaimTypes.ClaimTypes)

              {

                     //when it first loads add all our nodes

                     hierarchy.AddChild(new

                                                Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(

                                   ProviderInternalName, clm.FriendlyName, clm.ClaimTypeName, true));

              }

       }

}

catch (Exception ex)

{

       Debug.WriteLine("Error filling hierarchy: " + ex.Message);

}

 

在這裡我要檢查看看是否已取得我支援的宣告類型清單了。如果還沒,就會建立 CASI 套件自訂控制項的執行個體,呼叫 WCF 擷取宣告類型;我的作法是呼叫 WCF 類別上的 GetClaimTypes 方法。如果取回了資料,我就會代入先前說過的類別層級變數 (稱為 AzureClaimTypes),然後將其新增到我支援的宣告類型階層。

下一個我們會看到的方法是 FillResolve。FillResolve 方法有兩個不同的特徵,但作用卻相異。在一個案例中,我們的宣告值和類型是固定的,SharePoint 只負責驗證它是否有效。在另一個例子裡,使用者直接將某些值輸入 SharePoint 的輸入控制項,所以它事實上的作用就和搜尋宣告是一樣的。有鑑於此,我將分別討論它們兩者。

當宣告是固定的情況下,SharePoint 會要驗證它的值,我會呼叫一個名叫 GetResolveResults 的自訂方法。在該方法中,我還會加上該要求發出位置的 URI,以及 SharePoint 驗證所要尋找的宣告類型與宣告值。現在,GetResolveResults 應會類似如下:

//Note that claimType is being passed in here for future extensibility; in the

//current case though, we're only using identity claims

private AzureClaimProvider.AzureClaims.UniqueClaimValue GetResolveResults(string siteUrl,

       string searchPattern, string claimType)

{

       AzureClaimProvider.AzureClaims.UniqueClaimValue result = null;

 

       try

       {

              //create an instance of the control

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //set the properties to retrieve data; must configure cache properties since we're using it programmatically

              //cache is not actually used in this case though

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "ResolveClaim";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = claimType + ";" + searchPattern;

              cfgCtrl.MethodParams = IDENTITY_CLAIM + ";" + searchPattern;

              cfgCtrl.SharePointClaimsSiteUrl = siteUrl;

 

              //execute the method

              bool success = cfgCtrl.ExecuteRequest();

 

              //if the query encountered no errors then capture the result

              if (success)

                     result = (AzureClaimProvider.AzureClaims.UniqueClaimValue)cfgCtrl.QueryResultsObject;

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

 

       return result;

}

 

這裡我要建立一個自訂 CASI 套件控制項的執行個體,然後呼叫 WCF 上的 ResolveClaim 方法。該方法需要兩個參數,我將其以分號分隔 (因為這是 CASI 套件區分不同參數值的方法) 的形式傳入。接著執行要求,若它找到相符的對象,就會傳回一個 UniqueClaimValue;若找不到,就會傳回 null。回到我的 FillResolve 方法這邊,程式碼看起來會如下:

protected override void FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<PickerEntity> resolved)

{

       //make sure picker is asking for the type of entity we return; site collection admin won't for example

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //look for matching claims

              AzureClaimProvider.AzureClaims.UniqueClaimValue result =

                     GetResolveResults(context.AbsoluteUri, resolveInput.Value,

                     resolveInput.ClaimType);

 

              //if we found a match then add it to the resolved list

              if (result != null)

              {

                     PickerEntity pe = GetPickerEntity(result.ClaimValue, result.ClaimType,

                     SPClaimEntityTypes.User, result.DisplayName);

                           resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

首先我要確定此要求是針對一項「使用者」宣告,因為那是我的提供者會傳回的唯一宣告類型。假如要求並非「使用者」宣告,我就停止進行。接下來我呼叫方法來解析宣告,如果得到的結果並非 null,就進行處理。為了要進行處理,我先呼叫我寫的另一個自訂方法,稱為 GetPickerEntity。在此處我傳入宣告類型與宣告值,以建立一個身分識別宣告,然後將其傳回的 PickerEntity 和一系列傳入方法中的 PickerEntity 執行個體加到一起。關於 GetPickerEntity 方法我就不再贅述,因為這篇文章已經有夠長了,況且我在部落格的別處還有專文討論。

現在讓我們談談其他的 FillResolve 方法吧。我之前說過,它的作用基本上類似搜尋,因此我打算在這裡合併 FillResolve 和 FillSearch 方法。這兩種方法都會呼叫一個我自訂的方法,名為 SearchClaims,看起來如下:

private AzureClaimProvider.AzureClaims.UniqueClaimValueCollection SearchClaims(string claimType, string searchPattern,

       string siteUrl)

{

                    

       AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

              new AzureClaimProvider.AzureClaims.UniqueClaimValueCollection();

 

       try

       {

              //create an instance of the control

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //set the properties to retrieve data; must configure cache properties since we're using it programmatically

              //cache is not actually used in this case though

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "SearchClaims";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = claimType + ";" + searchPattern;

              cfgCtrl.MethodParams = claimType + ";" + searchPattern + ";200";

              cfgCtrl.SharePointClaimsSiteUrl = siteUrl;

 

              //execute the method

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //if it worked, get the array of results

                     results =

 (AzureClaimProvider.AzureClaims.UniqueClaimValueCollection)cfgCtrl.QueryResultsObject;

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine("Error searching claims: " + ex.Message);

       }

 

       return results;

}

 

在這個方法中,我只是建立一個自訂的 CASI 套件控制項而已,就和您在本文其他處看到的一樣。我會呼叫 WCF 上的 SearchClaims 方法,然後傳入我要搜尋的宣告類型、我要在該宣告類型中尋找的宣告值,以及要傳回的記錄數上限。您可能還記得在第 2 部中,SearchClaims 只會對傳入的搜尋模式進行 BeginsWith,所以如果使用者很多的話,結果很容易就超過 200 筆。然而 200 就是人員選擇可顯示的符合結果數上限,所以我也只要這麼多就夠了。如果您真的以為使用者會翻閱超過 200 筆結果只為尋找 1 筆,聽我說,不太可能!

現在我們取回 UniqueClaimValues 集合了,來看看該如何使用我們在自訂宣告提供者中的兩個覆寫方法。首先,FillResolve 方法如下:

protected override void FillResolve(Uri context, string[] entityTypes, string resolveInput, List<PickerEntity> resolved)

{

       //this version of resolve is just like a search, so we'll treat it like that

       //make sure picker is asking for the type of entity we return; site collection admin won't for example

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //do the search for matches

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, resolveInput, context.AbsoluteUri);

 

              //go through each match and add a picker entity for it

              foreach (AzureClaimProvider.AzureClaims.UniqueClaimValue cv in results.UniqueClaimValues)

              {

                     PickerEntity pe = GetPickerEntity(cv.ClaimValue, cv.ClaimType, SPClaimEntityTypes.User, cv.DisplayName);

                     resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

它只會呼叫 SearchClaims 方法,並為每筆取回的結果 (如果有的話),建立一個新的 PickerEntity,並將其加入傳入覆寫的同類項目。稍後它們全都會出現在 SharePoint 的輸入控制項中。FillSearch 運用方式如下:

protected override void FillSearch(Uri context, string[] entityTypes, string searchPattern, string hierarchyNodeID, int maxCount, SPProviderHierarchyTree searchTree)

{

       //make sure picker is asking for the type of entity we return; site collection admin won't for example

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //do the search for matches

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, searchPattern, context.AbsoluteUri);

 

              //if there was more than zero results, add them to the picker

              if (results.UniqueClaimValues.Count() > 0)

              {

                     foreach (AzureClaimProvider.AzureClaims.UniqueClaimValue cv in results.UniqueClaimValues)

                     {

                           //node where we'll stick our matches

                           Microsoft.SharePoint.WebControls.SPProviderHierarchyNode matchNode = null;

 

                           //get a picker entity to add to the dialog

                           PickerEntity pe = GetPickerEntity(cv.ClaimValue, cv.ClaimType, SPClaimEntityTypes.User, cv.DisplayName);

 

                           //add the node where it should be displayed too

                           if (!searchTree.HasChild(cv.ClaimType))

                           {

                                  //create the node so we can show our match in there too

                                  matchNode = new

                                         SPProviderHierarchyNode(ProviderInternalName,

                                         cv.DisplayName, cv.ClaimType, true);

 

                                  //add it to the tree

                                  searchTree.AddChild(matchNode);

                           }

                           else

                                  //get the node for this team

                                  matchNode = searchTree.Children.Where(theNode =>

                                         theNode.HierarchyNodeID == cv.ClaimType).First();

 

                           //add the match to our node

                           matchNode.AddEntity(pe);

                     }

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

在 FillSearch 中我又再次呼叫 SearchClaims 方法。我會針對每個取回的 UniqueClaimValue (如果有的話),檢查看看是否已將宣告類型加入結果階層節點了。同樣地,雖然在此例中,我永遠只會傳回一種宣告類型 (電子郵件帳號),但我希望將它寫成更具擴充性,以便您日後增加更多宣告類型使用。所以如果階層節點不存在就會新增;而如果存在就會將其找出來。接著再將我用 UniqueClaimValue 所建立的 PickerEntity,加到階層節點。差不多就是這様了。

我不打算講解 FillSchema 方法,或是每個自訂宣告提供者都一定有的那四種布林 (Boolean) 屬性覆寫。因為對於本案例來說,它們沒什麼特別之處,而我部落格的別篇文章中已說明其基本概念。我也不會討論用以登錄此自訂宣告提供者的功能接收器。同樣的,對這個專案而言它沒什麼特別的,而且我在別處提過了。將所有內容整合之後,請確定您的自訂宣告提供者組件與自訂 CASI 套件元件,皆已登錄到伺服器陣列中每部伺服器的「全域組件快取」中,然後您必須設定讓 SPTrustedIdentityTokenIssuer 使用您的自訂宣告提供者作為預設的提供者 (做法請見本部落格別篇文章)。

以上就是基本案例的完整說明。當您在 SharePoint 網站中嘗試新增使用者時 (實際上是電子郵件宣告),就會叫用此自訂宣告提供者,為您取得所支援之宣告類型的清單,接著當您在輸入控制項中輸入某個值時,或是用人員選擇搜尋某個值時,也會再度叫用該提供者。不論是哪種情況,自訂宣告提供者都會使用自訂的 CASI 套件控制項,發出已驗證過的呼叫給 Windows Azure,請求與我們的 WCF 對話,而 WCF 又會使用我們自訂的資料類別,從 Azure 資料表儲存體擷取資料。我們會再將傳回的結果解開,呈現給使用者。綜合以上內容,就是我們功能完備的 SharePoint 與 Azure「外部網路大補帖」,歡迎直接服用或依需求修改。自訂CASI 套件元件的原始程式碼、將使用者登錄至 Azure 佇列中的網頁組件,以及自訂宣告提供者,都隨附於此文章中。希望本文對您有用,也希望您讀完後會得到鼓勵,自己動腦想想看,要如何將這些不同的服務組合在一塊,打造最適合您的解決方案。以下是最終方案的幾張螢幕擷取畫面:

匿名使用者看到的根網站:

這是通過驗證之後看起來的樣子;請注意網頁組件現在顯示出 [申請會員] (Request Membership) 了:

以下是人員選擇作用中的範例圖,在搜尋開頭為 sp 的宣告值之後的樣子:

 

這是翻譯後的部落格文章。英文原文請參閱 The Azure Custom Claim Provider for SharePoint Project Part 3