Veröffentlichung des Originalartikels: 21.02.2012

In Teil 1 dieser Reihe habe ich kurz die Ziele dieses Projekts umrissen, das auf einer allgemeinen Ebene in der Verwendung des Windows Azure-Tabellenspeichers als Datenspeicher für einen benutzerdefinierten SharePoint-Anspruchsanbieter besteht. Der Anspruchsanbieter verwendet das CASI Kit zum Abrufen der Daten von Windows Azure, die benötigt werden, um die Personenauswahl (d. h. das Adressbuch) und die Namensauflösung für das Eingabesteuerelement bereitzustellen.

In Teil 2 bin ich alle Komponenten durchgegangen, die in der Cloud ausgeführt werden – die Datenklassen für die Arbeit mit Azure-Tabellenspeicher und Warteschlangen, eine Workerrolle zum Lesen von Elementen aus Warteschlangen und Auffüllen des Tabellenspeichers sowie ein WCF-Front-End, mit dem eine Clientanwendung neue Elemente in der Warteschlagen erstellen und alle standardmäßigen Aktionen mit der SharePoint-Personenauswahl ausführen kann: eine Liste der unterstützten Anspruchstypen bereitstellen, nach Anspruchswerten suchen und Ansprüche auflösen.

In diesem letzten Teil der Reihe gehen wir alle Komponenten durch, die auf der SharePoint-Seite verwendet werden. Er enthält eine mit dem CASI Kit erstellte benutzerdefinierte Komponente, mit der Elemente zur Warteschlange hinzugefügt und Aufrufe an den Azure-Tabellenspeicher ausgegeben werden. Zudem enthält er den benutzerdefinierten Anspruchsanbieter, der die CASI Kit-Komponente verwendet, um SharePoint mit diesen Azure-Funktionen zu verbinden.

Lassen Sie uns zu Beginn einen kurzen Blick auf die benutzerdefinierte CASI Kit-Komponente werfen. Ich werde nicht viel Zeit darauf verschwenden, da das CASI Kit in diesem Blog ausführlich behandelt wird. Diese spezielle Komponente wird in Teil 3 der CASI Kit-Reihe beschrieben. Kurz gesagt habe ich ein neues Windows-Klassenbibliotheksprojekt erstellt. Ich habe Verweise auf die CASI Kit-Basisklassenassembly und die anderen erforderlichen .NET-Assemblys (die ich in Teil 3 beschreibe) hinzugefügt. Ich habe dem Projekt einen Dienstverweis auf den WCF-Endpunkt, den ich in Teil 2 dieses Projekts erstellt habe, hinzugefügt. Schließlich habe ich dem Projekt eine neue Klasse hinzugefügt und ließ sie die CASI Kit-Basisklasse erben. Und ich habe den Code zum Überschreiben der ExecuteRequest-Methode hinzugefügt. Wie Sie hoffentlich in der CASI Kit-Reihe gesehen haben, sieht der Code zum Überschreiben von ExecuteRequest folgendermaßen aus:

       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 ist der Name des erstellten Dienstverweises, und er verwendet die IAzureClaims-Schnittstelle, die ich in meinem WCF-Projekt in Azure definiert habe. Wie bereits in der CASI Kit-Reihe erläutert wurde, sind dies im Wesentlichen Codebausteine, Ich habe lediglich den Namen meiner Schnittstelle und Klasse eingefügt, der in der WCF-Anwendung verfügbar gemacht wird. Außerdem habe ich, wie ebenfalls in der CASI Kit-Reihe beschrieben ist, eine ASPX-Seite namens AzureClaimProvider.aspx erstellt. Ich habe nur den in Teil 3 der CASI Kit-Reihe beschriebenen Code kopiert und eingefügt und den Namen meiner Klasse und den Endpunkt, an dem sie erreichbar ist, ersetzt. Das Steuerelementtag auf der ASPX-Seite für die benutzerdefinierte CASI Kit-Komponente sieht so aus:

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

 

Die wesentlichen Punkte sind hier, dass ich einen CNAME-Eintrag für spsazure.vbtoys.com erstellt habe, der auf meine Azure-Anwendung unter cloudapp.net (ebenfalls in Teil 3 der CASI Kit-Reihe beschrieben) verweist. Ich habe den standardmäßigen MethodName, den die Seite aufrufen wird, auf GetClaimTypes festgelegt. Diese Methode hat keine Parameter und gibt eine Liste der Anspruchstypen zurück, die von meinem Azure-Anspruchsanbieter unterstützt werden. Mit dieser Methode lässt sich die Konnektivität zwischen der Azure-Anwendung und SharePoint gut testen. Ich kann einfach zu http://anySharePointsite/_layouts/AzureClaimProvider.aspx wechseln, und wenn alles richtig konfiguriert ist, werden Daten auf der Seite angezeigt. Nachdem ich das Projekt bereitgestellt habe, indem ich die Assembly zum globalen Assemblycache hinzugefügt und die Seite im Verzeichnis _layouts von SharePoint bereitgestellt habe, habe ich genau das getan: ich habe auf einer meiner Websites auf die Seite zugegriffen und überprüft, ob Daten zurückgegeben wurden. So wusste ich, dass die Verbindung zwischen SharePoint und Azure funktioniert.

Nachdem nun die Grundstruktur installiert ist, komme ich endlich zum interessanten Teil des Projekts, der aus zwei Schritten besteht:

  1. 1.       Erstellen „einer Komponente“, die Informationen über neue Benutzer in die Azure-Warteschlage übermittelt
  2. 2.       Erstellen eines benutzerdefinierten Anspruchsanbieters, der mithilfe der benutzerdefinierten CASI Kit-Komponente Anspruchstypen, Namensauflösung und Suche nach Ansprüchen bereitstellt.

Dies ist eine gute Gelegenheit, um anzuhalten und etwas zurück zu gehen. In diesem speziellen Fall wollte ich möglichst schnell zu einem Ergebnis kommen. Daher habe ich eine neue Webanwendung erstellt und anonymen Zugriff ermöglicht. Wie Sie sicher wissen, wird sie allein durch Aktivieren auf der Webanwendungsebene NICHT auf der Websitesammlungsebene aktiviert. Daher habe ich sie für dieses Szenario zusätzlich nur in der Stammwebsitesammlung aktiviert und Zugriff auf alles in der Website gewährt. Für alle anderen Websitesammlungen, die Informationen nur für Mitglieder enthalten würden, ist der anonyme Zugriff NICHT aktiviert, sodass Benutzern Rechte für die Teilnahme erteilt werden müssen.

Die nächste Frage ist, wie die Identitäten, die die Website verwenden werden, verwaltet werden. Damit möchte ich eigentlich nichts zu tun haben. Unter Umständen bräuchte ich eine Vielzahl von Methoden zum Synchronisieren von Konten mit Azure oder etwas Ähnliches, aber wie ich in Teil 1 der Reihe erläutert habe, gibt es dafür schon eine Menge von Anbietern. Ich werde sie also einfach ihre Arbeit tun lassen. Ich meine damit, dass ich einen anderen Microsoft Cloud-Dienst namens ACS (Access Control Service) verwende. Kurz gesagt fungiert ACS als Identitätsanbieter für SharePoint. Ich habe also einfach eine Vertrauensstellung zwischen meiner SharePoint-Farm und der Instanz von ACS, die ich für diese Machbarkeitsstudie erstellt habe, eingerichtet. In ACS habe ich SharePoint als vertrauende Seite hinzugefügt, sodass ACS weiß, wohin Benutzer nach der Authentifizierung zu senden sind. ACS habe ich außerdem so konfiguriert, dass sich Benutzer mit ihren Google Mail-, Yahoo- oder Facebook-Konten anmelden können. Nach der Anmeldung erhält ACS einen einzelnen Anspruch zurück, den ich verwenden werde (E-Mail-Adresse), und sendet ihn zurück an SharePoint.

So, das ist der ganze Hintergrund für die Grundstruktur – Azure stellt Tabellenspeicher und Warteschlangen für die Arbeit mit den Daten bereit, ACS bietet Authentifizierungsdienste und CASI Kit liefert die Grundstruktur für die Daten.

Wie wird nun die ganze beschriebene Grundstruktur verwendet? Wir wollten, dass der Vorgang, ein Mitglied zu werden, relativ schmerzlos vonstatten geht. Daher schrieb ich ein Webpart, mit dem Benutzer der Azure-Warteschlange hinzugefügt werden. Es überprüft, ob die Anforderung authentifiziert ist (d. h. ob der Benutzer auf den Link zum Anmelden geklickt hat, der auf einer anonymen Website angezeigt wird, sich bei einem der oben erwähnten Anbieter angemeldet hat und ACS die Anspruchsinformationen zurückgesendet hat). Wenn die Anforderung nicht authentifiziert ist, tut das Webpart gar nichts. Ist die Anforderung jedoch authentifiziert, wird eine Schaltfläche gerendert. Wenn Sie auf diese klicken, wird der E-Mail-Anspruch des Benutzers in die Azure-Warteschlange eingefügt. Dies ist der Teil, von dem ich sagte, wir sollten einen Schritt zurücktreten und darüber nachdenken. Für eine Machbarkeitsstudie ist das alles OK, es funktioniert. Es sind jedoch andere Möglichkeiten denkbar, um diese Anforderung zu verarbeiten. Vielleicht schreiben Sie die Informationen z. B. in eine SharePoint-Liste. Sie könnten einen benutzerdefinierten Zeitgeberauftrag schreiben (mit dem das CASI Kit sehr gut funktioniert) und regelmäßig neue Anforderungen aus dieser Liste verarbeiten. Sie könnten SPWorkItem verwenden, um die Anforderungen in eine Warteschlange einzureihen und später zu verarbeiten. Sie könnten sie in einer Liste speichern und einen benutzerdefinierten Workflow hinzufügen, der vielleicht einen Genehmigungsvorgang durchläuft und anschließend über eine benutzerdefinierte Workflowaktion das CASI Kit aufruft, um die Details in die Azure-Warteschlange hochzuladen. Kurz gesagt – hier ist eine Menge Funktionalität, Flexibilität und Anpassung möglich – es bleibt Ihrer Phantasie überlassen. Möglicherweise verfasse ich irgendwann eine andere Version, die die Informationen in eine benutzerdefinierte Liste schreibt, asynchron verarbeitet, die Daten der Azure-Warteschlange hinzufügt und dann automatisch das Konto zur Gruppe Besucher in einer der Unterwebsites hinzufügt, damit der Benutzer angemeldet wird und sofort loslegen kann. Aber das wäre dann ein anderer Blogbeitrag.

Damit ist alles gesagt – wie oben beschrieben lasse ich den Benutzer auf eine Schaltfläche klicken, wenn er angemeldet ist, und dann verwende ich meine benutzerdefinierte CASI Kit-Komponente, um den WCF-Endpunkt aufzurufen und die Informationen der Azure-Warteschlange hinzuzufügen. Hier ist der Code für das Webpart – dank des CASI Kits ziemlich einfach:

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 == "http://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);

              }

       }

}

 

Ein kurzer Überblick über den Code: Zuerst wird sichergestellt, dass die Anforderung authentifiziert ist; wenn ja, wird die Schaltfläche zur Seite hinzugefügt und ein Ereignishandler für das Klickereignis der Schaltfläche hinzugefügt. Im Ereignishandler der Schaltfläche wird ein IClaimsPrincipal-Verweis auf den aktuellen Benutzer abgerufen und dann die Anspruchsauflistung des Benutzers betrachtet. Ich führe eine LINQ-Abfrage für die Anspruchsauflistung aus, um nach dem E-Mail-Anspruch zu suchen, der dem Identitätsanspruch für SPTrustedIdentityTokenIssuer entspricht. Wenn der E-Mail-Anspruch gefunden wird, erstelle ich eine verkettete Zeichenfolge aus dem Anspruchstyp, Anspruchswert und Anzeigenamen für den Anspruch. Dies ist in diesem Szenario nicht unbedingt erforderlich, aber da der Code in einem allgemeineren Szenario verwendbar sein soll, habe ich ihn so geschrieben. Die verkettete Zeichenfolge ist der Wert für die Methode in der WCF-Anwendung, die der Azure-Warteschlange Daten hinzufügt. Anschließend wird eine Instanz der benutzerdefinierten CASI Kit-Komponente erstellt und so konfiguriert, dass die WCF-Methode zum Hinzufügen von Daten zur Warteschlange aufgerufen wird. Dann rufe ich die ExecuteRequest-Methode auf, um die Daten tatsächlich abzuschicken.

Wenn die Antwort angibt, dass die Daten erfolgreich der Warteschlange hinzugefügt wurden, lasse ich den Benutzer dies wissen; andernfalls teile ich ihm mit, dass ein Problem aufgetreten ist und er es später erneut versuchen soll. In einem realen Szenario hätte ich natürlich mehr Fehlerprotokolldaten zur Verfügung und könnte genau zurückverfolgen, was warum passiert ist. Aber auch in diesem Fall schreibt das CASI Kit alle Fehlerinformationen in SPMonitoredScope in die ULS-Protokolle, sodass jede Aktion für die Anforderung eine eindeutige Korrelations-ID besitzt, mit der alle Aktivitäten bezüglich der Anforderung angezeigt werden können. Somit sind wir nun eigentlich schon ziemlich weit gekommen.

Wir sind nun alle Teile der Grundstruktur durchgegangen, und ich habe gezeigt, wie Daten in die Azure-Warteschlange eingetragen und von dort von einem Workerprozess abgerufen und dem Tabellenspeicher hinzugefügt werden. Dies ist das eigentliche Ziel, denn nun können wir den benutzerdefinierten Anspruchsanbieter durchgehen. Er verwendet das CASI Kit, um den verwendeten Azure-Tabellenspeicher aufzurufen und abzufragen. Lassen Sie uns die interessantesten Aspekte des benutzerdefinierten Anspruchsanbieters betrachten.

Zuerst betrachten wir einige Attribute auf Klassenebene:

//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 =

       "http://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();

 

Zunächst verwende ich eine Konstante, die auf den WCF-Endpunkt verweist, mit dem das CASI Kit eine Verbindung herstellen soll. Sie werden feststellen, dass ich sowohl meinen Testendpunkt als auch meinen Produktionsendpunkt verwende. Wenn Sie das CASI Kit wie wir hier im benutzerdefinierten Anspruchsanbieter programmgesteuert verwenden, müssen Sie ihm immer mitteilen, wo sich der WCF-Endpunkt befindet, mit dem es kommunizieren soll.

Anschließend verwende ich, wie bereits beschrieben, den E-Mail-Anspruch als Identitätsanspruch. Da ich im Anbieter häufiger darauf verweise, habe ich ihn einer Konstanten auf Klassenebene zugewiesen.

Schließlich verwende ich eine Auflistung von AzureClaimTypes. Ich habe in Teil 2 der Reihe erklärt, warum ich eine Auflistung verwende, und ich speichere sie hier auf der Klassenebene, damit ich die Informationen nicht bei jedem Aufruf der FillHierarchy-Methode erneut abrufen muss. Aufrufe an Azure sind nicht billig, daher minimiere ich sie soweit wie möglich.

Hier ist der nächste Codeabschnitt:

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;

       }

}

 

Ich wollte diesen Code aus folgendem Grund erläutern: Da der Anbieter Identitätsansprüche ausgibt, MUSS er der Standardanbieter für SPTrustedIdentityTokenIssuer sein. Wie dies erreicht wird, geht über das Thema dieses Beitrags hinaus, ich habe dies jedoch an anderer Stelle in meinem Blog behandelt. Sie müssen vor allem daran denken, dass eine starke Beziehung zwischen dem für Ihren Anbieter verwendeten Namen und dem Namen für den SPTrustedIdentityTokenIssuer bestehen muss. Den Wert, den ich für ProviderInternalName verwendet habe, muss ich der ClaimProviderName-Eigenschaft für SPTrustedIdentityTokenIssuer zuweisen. Zudem muss ich den Namen des SPTrustedIdentityTokenIssuer beim Erstellen von Identitätsansprüchen für Benutzer verwenden. Daher habe ich einen SPTrustedIdentityTokenIssuer namens "SPS ACS" erstellt und meiner SPTrustedIdentityTokenIssuerName-Eigenschaft hinzugefügt. Deswegen habe ich diese Werte hier codiert.

Da in diesem Anbieter keine Anspruchserweiterung erfolgt, habe ich keinen Code zum Überschreiben von FillClaimTypes, FillClaimValueTypes oder FillEntityTypes erstellt. Der nächste Codeabschnitt enthält die FillHierarchy-Methode, in der ich SharePoint die unterstützten Anspruchstypen mitteile. Dies ist der Code dafür:

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);

}

 

Ich prüfe hier also, ob ich die Liste der unterstützten Anspruchstypen bereits erfasst habe. Wenn nicht, erstelle ich eine Instanz meines benutzerdefinierten CASI Kit-Steuerelements und rufe WCF auf, um die Anspruchstypen abzurufen. Hierzu rufe ich die GetClaimTypes-Methode der WCF-Klasse auf. Wenn ich Daten zurückerhalte, weise ich sie der zuvor beschriebenen Variablen auf Klassenebene AzureClaimTypes zu und füge sie der Hierarchie der unterstützten Anspruchstypen hinzu.

Als Nächstes betrachten wir die FillResolve-Methoden. Die FillResolve-Methoden weisen zwei verschiedene Signaturen auf, da sie zwei verschiedene Aktionen ausführen. Im ersten Szenario liegt ein bestimmter Anspruch mit Wert und Typ vor, dessen Gültigkeit von SharePoint überprüft werden soll. Im zweiten Fall hat ein Benutzer nur einen Wert in das SharePoint-Eingabesteuerelement eingegeben, und somit ist es effektiv dasselbe wie eine Suche nach Ansprüchen. Daher betrachte ich die beiden Fälle getrennt.

Im Fall eines bestimmten Anspruchs, dessen Werte von SharePoint überprüft werden sollen, rufe ich eine von mir erstellte benutzerdefinierte Methode namens GetResolveResults auf. In dieser Methode übergebe ich den Uri, an dem die Anforderung gestellt wurde, sowie den Anspruchstyp und Anspruchswert, den SharePoint überprüft haben möchte. GetResolveResults sieht dann folgendermaßen aus:

//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;

}

 

Ich erstelle hier also eine Instanz des benutzerdefinierten CASI Kit-Steuerelements und rufe dann die ResolveClaim-Methode meiner WCF-Anwendung auf. Die Methode akzeptiert zwei Parameter, daher übergebe ich sie als durch Semikolon getrennte Werte (denn so werden im CASI Kit Parameterwerte unterschieden). Dann wird die Anforderung ausgeführt, und wenn eine Übereinstimmung gefunden wird, wird ein einzelner UniqueClaimValue-Wert zurückgegeben; andernfalls ist der Rückgabewert NULL. In der FillResolve-Methode sieht der Code dann so aus:

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);

       }

}

 

Ich prüfe zuerst, ob sich die Anforderung auf einen Benutzeranspruch bezieht, da dies der einzige vom Anbieter zurückgegebene Anspruchstyp ist. Bezieht sich die Anforderung nicht auf einen Benutzeranspruch, wird die Methode verlassen. Anschließend rufe ich die Methode zum Auflösen des Anspruchs auf, und wenn ein Ergebnis ungleich NULL zurückgegeben wird, wird es verarbeitet. Zur Verarbeitung wird eine andere von mir erstellte benutzerdefinierte Methode GetPickerEntity aufgerufen. Ihr übergebe ich den Anspruchstyp und -wert, um einen Identitätsanspruch zu erstellen. Dann kann ich die zurückgegebene PickerEntity-Instanz der Liste von PickerEntity-Instanzen hinzufügen, die an die Methode übergeben wurden. Ich gehe nicht weiter auf die GetPickerEntity-Methode ein, da dieser Beitrag bereits unglaublich lang ist und ich dies schon in anderen Blogbeiträgen behandelt habe.

Sprechen wir nun über die andere FillResolve-Methode. Wie erwähnt, verhält sie sich im Wesentlichen wie eine Suche, daher kombiniere ich hier die Methoden FillResolve und FillSearch. Beide Methoden rufen eine von mir erstellte benutzerdefinierte Methode SearchClaims auf, die folgendermaßen aussieht:

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;

}

 

In dieser Methode erstelle ich wie schon an anderen Stellen in diesem Beitrag eine Instanz des benutzerdefinierten CASI Kit-Steuerelements. Ich rufe die SearchClaims-Methode meiner WCF-Anwendung auf und übergebe den Anspruchstyp, in dem gesucht werden soll, den gesuchten Anspruchswert in diesem Anspruchstyp sowie die maximale Anzahl von zurückzugebenden Datensätzen. Sie erinnern sich vielleicht aus Teil 2 der Reihe, dass SearchClaims lediglich BeginsWith für das übergebene Suchmuster ausführt. Bei einer großen Anzahl von Benutzern könnte dies leicht mehr als 200 Ergebnisse liefern. 200 ist jedoch die maximale Anzahl von Übereinstimmungen, die in der Personenauswahl angezeigt wird. Daher reicht mir das aus. Wenn Sie wirklich der Meinung sind, dass Benutzer mehr als 200 Ergebnisse durchsuchen, so halte ich das für nicht sehr wahrscheinlich.

Nun haben wir unsere Auflistung von UniqueClaimValues-Instanzen zurück. Lassen Sie uns betrachten, wie unsere beiden Überschreibungsmethoden im benutzerdefinierten Anspruchsanbieter verwendet werden. Die FillResolve-Methode sieht folgendermaßen aus:

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);

       }

}

Sie ruft die SearchClaims-Methode auf und erstellt für jedes Ergebnis (sofern Ergebnisse zurückgegeben werden) eine neue PickerEntity-Instanz und fügt sie der übergebenen Liste hinzu. Alle Instanzen werden im Eingabesteuerelement in SharePoint angezeigt. Die FillSearch-Methode verwendet sie folgendermaßen:

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);

       }

}

 

In FillSearch wird erneut die SearchClaims-Methode aufgerufen. Für jeden zurückerhaltenen UniqueClaimValue-Wert (falls Werte zurückgegeben werden) prüfe ich, ob der Anspruchstyp dem Knoten in der Ergebnishierarchie hinzugefügt wurde. Wie gesagt, in diesem Fall gebe ich immer nur einen Anspruchstyp zurück (E-Mail), aber ich wollte den Code erweiterbar schreiben, damit Sie später mehrere Anspruchstypen verwenden können. Ich füge den Hierarchieknoten hinzu, wenn er noch nicht vorhanden ist, oder suche ihn, falls doch. Ich nehme die PickerEntity-Instanz, die ich aus der UniqueClaimValue-Instanz erstellt habe, und füge sie dem Hierarchieknoten hinzu. Und das war's im Wesentlichen.

Ich behandle weder die FillSchema-Methode noch eine der vier booleschen Eigenschaftenüberschreibungen, die jeder benutzerdefinierte Anspruchsanbieter aufweisen muss. Sie enthalten in diesem Szenario nichts Besonderes, und ich habe die Grundlagen in anderen Beiträgen zu diesem Blog behandelt. Auch auf den Funktionsempfänger, mit dem dieser benutzerdefinierte Anspruchsanbieter registriert wird, gehe ich nicht ein. Auch dieser enthält in diesem Projekt nichts Außergewöhnliches, und ich habe ihn schon an anderer Stelle behandelt. Nach dem Compilieren müssen Sie nur sicherstellen, dass die Assembly für den benutzerdefinierten Anspruchsanbieter und die benutzerdefinierte CASI Kit-Komponente im globalen Assemblycache jedes Servers in der Farm registriert sind. Und Sie müssen SPTrustedIdentityTokenIssuer so konfigurieren, dass der benutzerdefinierte Anspruchsanbieter als Standardanbieter verwendet wird (auch dies ist an anderer Stelle in diesem Blog beschrieben).

Dies ist das ganze Basisszenario. Wenn Sie auf der SharePoint-Website versuchen, einen neuen Benutzer hinzuzufügen (E-Mail-Anspruch), wird der benutzerdefinierte Anspruchsanbieter zunächst aufgerufen, um eine Liste der unterstützten Anspruchstypen abzurufen, und dann erneut, wenn Sie einen Wert in das Eingabesteuerelement eingeben oder mit der Personenauswahl einen Wert suchen. In jedem Fall verwendet der benutzerdefinierte Anspruchsanbieter das benutzerdefinierte CASI Kit-Steuerelement, um einen authentifizierten Aufruf an Windows Azure abzusetzen, um mit unserer WCF-Anwendung zu kommunizieren. Diese verwendet dann unsere benutzerdefinierten Datenklassen, um Daten aus dem Azure-Tabellenspeicher abzurufen. Sie gibt das Ergebnis zurück, und wir packen es aus und präsentieren es dem Benutzer. Damit verfügen Sie über die gesamte gebrauchsfertige SharePoint- und Azure-Lösung für ein „Extranet in der Box“, die Sie direkt verwenden oder an Ihre Anforderungen anpassen können. Der Quellcode für die benutzerdefinierte CASI Kit-Komponente, das Webpart zur Registrierung des Benutzers in einer Azure-Warteschlange sowie den benutzerdefinierten Anspruchsanbieter ist an diesen Beitrag angefügt. Ich hoffe, Sie haben Freude daran, finden ihn nützlich und können sich nun vorstellen, wie Sie diese separaten Dienste zusammenbinden können, um Lösungen für Ihre Probleme zu entwickeln. Hier sind einige Screenshots der endgültigen Lösung:

Stammwebsite als anonymer Benutzer:

So sieht es aus, nachdem Sie sich authentifiziert haben; beachten Sie, dass von dem Webpart nun die Schaltfläche Request Membership angezeigt wird:

Hier sehen Sie die Personenauswahl in Aktion, und zwar nach der Suche nach Anspruchswerten, die mit „sp“ beginnen:

 

Es handelt sich hierbei um einen übersetzten Blogbeitrag. Sie finden den Originalartikel unter The Azure Custom Claim Provider for SharePoint Project Part 3.