SharePoint プロジェクト用 Azure カスタム クレーム プロバイダー パート 3
原文の記事の投稿日: 2012 年 2 月 21 日 (火曜日)
このシリーズのパート 1 では、高レベルで Windows Azure テーブル記憶領域を SharePoint カスタム クレーム プロバイダーのデータ ストアとして使用するという、このプロジェクトの目標の概要を示します。クレーム プロバイダーは CASI キット (英語)を使用して、ユーザー選択 (たとえば、アドレス帳) および入力コントロールの名前解決機能を提供するために必要なデータを Windows Azure から取得します。
パート 2 では、クラウドで稼働するすべてのコンポーネントを見てきました。Azure テーブル記憶領域およびキューを操作するために使用する必要のあるデータ クラス、キューからアイテムを読み取ってテーブル記憶領域に格納するためのワーカー ロール、WCF フロント エンドなどがありました。WCF フロント エンドを利用することで、クライアント アプリケーションは、キューに新しいアイテムを作成し、サポートされるクレーム タイプの一覧の提供、クレーム値の検索、クレームの解決といった SharePoint ユーザー選択のすべての標準的な処理を行うことができました。
このシリーズの最後となるこのパートでは、SharePoint 側で使用される他のコンポーネントについて見ていくことにします。そのようなものとしては、キューにアイテムを追加したり、Azure テーブル記憶領域に対する呼び出しを行ったりするための、CASI キットを使用して構築するカスタム コンポーネントがあります。また、CASI キット コンポーネントを使用して SharePoint と Azure のクレーム機能を接続する、カスタム クレーム プロバイダーもあります。
まず始めに、カスタム 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 の _layouts ディレクトリに展開することで、プロジェクトを展開した後は (私もこのとおりのことを行いました)、いずれかのサイトでページをヒットしてデータが返されることを確認することにより、SharePoint と Azure の間の接続が機能していることがわかりました。
プラミングの配置が済んだので、最後に残っているのはプロジェクトの "お楽しみ" の部分です。ここでは 2 つのことを行います。
- 1. 新しいユーザーについての情報を Azure キューに送信する "何らかのコンポーネント" を作成します。
- 2. カスタム CASI キット コンポーネントを使用して、クレームの種類を提供し、名前を解決し、クレームを検索するカスタム クレーム プロバイダーを作成します。
ここで、少しこれまでのことをまとめておきます。ここで行いたかったことは、何かをできる限りすばやく展開することでした。そこで、新しい Web アプリケーションを作成し、匿名アクセスを有効にしました。ご存じだとは思いますが、Web アプリケーション レベルで有効にしただけでは、サイト コレクション レベルでは有効になりません。このシナリオでは、ルート サイト コレクションだけで、やはり有効にしています。サイト内のすべてのものへのアクセスを許可しました。他のすべてのサイト コレクションは、メンバー専用の情報が格納されますが、匿名アクセスは有効になっていず、ユーザーは参加する権利を許可される必要があります。
次に考えなければならないことは、サイトを使用する ID を管理する方法です。当然ですが、このようなことは行いたくありません。アカウントを Azure に同期する方法もいくつか考えましたが、このシリーズのパート 1 で説明したように、既にそれを行っているプロバイダーが数多くあるので、それには手を付けないことにします。つまり、ACS (アクセス制御サービス) と呼ばれるマイクロソフトのクラウド サービスを活用しました。要するに、ACS は SharePoint に対して ID プロバイダーのような役割を果たします。そこで、SharePoint ファームと、この POC のために作成した ACS のインスタンスの間の信頼の作成だけを行いました。ACS に SharePoint を証明書利用者として追加したので、ACS は認証が済んだユーザーをどこに送ればよいかわかっています。ACS 内では、ユーザーが Gmail、Yahoo、または Facebook のアカウントを使用してサインインできるようにも構成しました。ユーザーがサインインした後、ACS は単一のクレームを取得し (それには電子メール アドレスを使用します)、それを SharePoint に返送します。
以上が背景とプラミングのすべてです。Azure はデータを操作するためのテーブル記憶領域とキューを提供し、ACS は認証サービスを提供し、CASI キットはデータにプラミングを提供しています。
さて、説明したすべてのプラミングを、どのように使用するのでしょうか。ところで、プロセスを使いやすいものにしたかったので、次に、ユーザーを Azure キューに追加する Web パーツを作成しました。この Web パーツは、要求が認証されているかどうかを確認します (つまり、ユーザーが匿名サイトにアクセスするサインインのリンクをクリックし、前に説明したプロバイダーの 1 つにサインインし、ACS がクレーム情報を返している、ということです)。要求が認証されていない場合は、Web パーツは何も行いません。一方、要求が認証されている場合は、クリックするとユーザーの電子メール クレームを取得して Azure キューに追加するボタンを表示します。これが、少し前に戻って考える必要があるといった部分です。POC の場合は、これでも十分で問題なく動作します。しかし、この要求を処理できる他の方法についても考えてみてください。たとえば、SharePoint リストに情報を書き込むものとします。カスタム タイマー ジョブを作成し (CASI キットを利用できます)、定期的にそのリストの新しい要求を処理します。SPWorkItem を使用して、後の処理まで要求をキューに入れておきます。それをリストに格納し、何らかの承認処理を行うカスタム ワークフローを追加し、要求が承認されたら、カスタム ワークフローのアクションを使用して CASI キットを呼び出し、詳細を Azure キューまでプッシュするといったことができます。つまり、非常に強力で、柔軟で、カスタマイズが可能であり、すべてはユーザーの想像力次第です。実際、これの別のバージョンを作成することもできます。たとえば、要求をカスタム リストに書き込み、後で非同期にそれを処理し、データを Azure キューに追加してから、サブサイトの 1 つの Visitors グループにアカウントを自動的に追加すれば、ユーザーはサインアップされて操作を続けることができます。ただし、それについては別の投稿で説明します。
先に説明したように、ユーザーがボタンをクリックし、ユーザーがサインインしている場合は、カスタム CASI キットのコンポーネントを使用して WCF エンドポイントを呼び出し、Azure キューに情報を追加します。Web パーツのコードを次に示しますが、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 に対する ID クレームです。電子メール クレームが見つかった場合は、クレームの種類、クレームの値、クレームのフレンドリ名を連結した文字列を作成します。前に説明したように、このシナリオではこれは厳密には必要のないことですが、さらに一般的なシナリオでも使用できるようにするため、このように作りました。この連結された文字列は、Azure キューにデータを追加する WCF のメソッドに渡す値です。次に、カスタム CASI キット コンポーネントのインスタンスを作成し、データをキューに追加する WCF メソッドを呼び出すように構成してから、ExecuteRequest メソッドを呼び出してデータを追加します。
キューへのデータの追加が成功したことを示す応答を受け取った場合は、そのことをユーザーに伝えます。そうでない場合は、問題が発生し、後で再度確認が必要かもしれないことを伝えます。実際のシナリオでは、発生したこととその理由を正確に追跡できるように、さらにエラー ログを取得します。ここではこのままにしますが、CASI キットがすべてのエラー情報を SPMonitoredScope の ULS ログに書き込み、要求に対して行われたすべての処理に一意の相関 ID が付けられるので、要求と関連付けられているすべてのアクティビティを表示できます。したがって、実際にはすぐに対処できます。
すべてのプラミングについて説明し、データが Azure キューに追加され、そこからワーカー プロセスによって取り出されてテーブル記憶領域に追加される様子を見てきました。これでカスタム クレーム プロバイダーについてお話しできるので、それが本当の最終目標になります。次に、CASI キットを利用して、使用している Azure テーブル記憶領域のクエリを行います。カスタム クレーム プロバイダーの最も興味深い面を見ていくことにしましょう。
最初に、2 つのクラス レベル属性について説明します。
//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 エンドポイントの場所を示す必要があります。
次に、前に説明したように、電子メール クレームを ID クレームとして使用します。それをプロバイダー内で何回も参照するので、クラス レベルの定数に設定しておきます。
最後に、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;
}
}
このコードを見ていただきたかったのは、このプロバイダーは ID クレームを発行しているので、SPTrustedIdentityTokenIssuer の既定のプロバイダーである必要があるためです。それを行う方法についてはこの投稿では説明しませんが、どこか別のブログで触れたいと思います。そのことに関して憶えておく必要がある重要なことは、プロバイダーに対して使用する名前と、SPTrustedIdentityTokenIssuer に使用する名前の間に、強い関係を設ける必要があることです。ここで ProviderInternalName に使用した値は、SPTrustedIdentityTokenIssuer の ClaimProviderName プロパティに設定する必要がある名前です。また、ユーザーの ID クレームを作成するときは、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 メソッドは 2 つの異なることを行うため、シグネチャが 2 つあります。1 番目のシナリオでは、値と種類を持つ特定のクレームがあり、SharePoint ではそれが有効なクレームであることを確認する必要があります。第 2 のケースでは、ユーザーが何らかの値を SharePoint の入力コントロールに入力しており、実質的にクレームの検索と同じことを行います。そこで、これらを別々に見ていくことにします。
特定のクレームがあり、その値を検証する必要がある場合は、作成した GetResolveResults という名前のカスタム メソッドを呼び出します。このメソッドに、要求が行われた場所の URI、および有効性を検証するクレームの種類と値を渡します。この場合の 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 メソッドを呼び出します。このメソッドは 2 つのパラメーターを受け取るので、セミコロンで値を区切って渡します (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 以外の結果が返った場合はそれを処理します。そのためには、作成したもう 1 つのカスタム メソッドである GetPickerEntity を呼び出します。クレームの種類と値を渡し、ID クレームを作成した後、返された PickerEntity を、このメソッドに渡された PickerEntity のインスタンスのリストに追加します。この投稿も既にかなり長くなってしまったので、GetPickerEntity メソッドについてはここでは説明しません。別のブログ投稿にまわします。
それでは、もう 1 つの 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 件以上もスクロールして見ると考える人もいるかもしれませんが、そのようなことはまずありません。
UniqueClaimValues のコレクションができたので、カスタム クレーム プロバイダーの 2 つのオーバーライド メソッドでそれを使用する方法を示します。最初に見るのは 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 ごとに (ある場合)、results 階層ノードにクレームの種類を追加したことを確認します。この例では返されるクレームの種類は常に 1 つですが (email)、より多くのクレーム種類を使用するよう後で拡張できるように作ってあります。したがって、階層ノードが存在しない場合は追加し、存在する場合はそれを検索します。UniqueClaimValue から作成された PickerEntity を取得し、それを階層ノードに追加します。それがそこで行われていることのほとんどすべてです。
FillSchema メソッドについて、またはすべてのカスタム クレーム プロバイダーが持つ必要のある 4 つの Boolean プロパティのオーバーライドについては、ここでは説明しません。これらについてはこのシナリオに固有の特別なことはなく、基本についてはこのブログの他の投稿で説明してあります。また、このカスタム クレーム プロバイダーの登録に使用されているフィーチャー レシーバーについても、ここでは説明しません。やはり、このプロジェクト固有の特別な情報はなく、別の場所で説明しています。コンパイルが済んだら、カスタム クレーム プロバイダーおよびカスタム CASI キット コンポーネントのアセンブリをファームの各サーバーのグローバル アセンブリ キャッシュに登録し、既定のプロバイダーとしてカスタム クレーム プロバイダーを使用するように SPTrustedIdentityTokenIssuer を構成する必要があります (やはり、このブログの別の場所に説明があります)。
以上が基本的なシナリオのすべてです。SharePoint サイトにいて、新しいユーザー (実際には電子メール クレーム) を追加しようとすると、最初に、カスタム クレーム プロバイダーが呼び出されてサポートされるクレームの種類の一覧が取得された後、入力コントロールに値を入力するか、またはユーザー選択を使用して値を検索すると、再び同様のことが行われます。どちらの場合も、カスタム クレーム プロバイダーはカスタム CASI キット コントロールを使用して、Windows Azure に認証された呼び出しを行って WCF と通信し、カスタム データ クラスを使用して Azure テーブル記憶領域からデータを取得します。その後、返された結果から情報を抽出してユーザーに表示します。このようにして完成した SharePoint と Azure による "すべてを備えたエクストラネット" のターンキー システムは、そのままで使用することも、目的に合わせて変更することもできます。カスタム CASI キット コンポーネント、ユーザーを Azure キューに登録する Web パーツ、およびカスタム クレーム プロバイダーのソース コードは、この投稿にすべて添付されています。この投稿の内容が楽しく、役に立ち、これらのばらばらなサービスを結び付けて問題の解決手段を作成する手がかりになることを望んでいます。最終的なソリューションのスクリーンショットをいくつか示しておきます。
匿名ユーザーとしてのルート サイト:
ユーザーが認証された後の表示です。Web パーツに [Request Membership] ボタンが表示されていることに注意してください。
使用中のユーザー選択の例です。"sp" で始まるクレーム値を検索した後の表示です。
これはローカライズされたブログ投稿です。原文の記事は、「The Azure Custom Claim Provider for SharePoint Project Part 3」をご覧ください。