Projekt „Benutzerdefinierter Azure-Anspruchsanbieter für SharePoint“ – Teil 3

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 https://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 == "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

                     &nbs