Article d’origine publié le mardi 21 février 2012

Dans la Partie 1 de cette série de billets, j’ai brièvement décrit l’objectif de ce projet, qui, à un niveau élevé, est d’utiliser le stockage tabulaire Windows Azure comme un magasin de données pour un fournisseur de revendications personnalisées SharePoint. Le fournisseur de revendications va utiliser le kit CASI pour récupérer les données dont il a besoin dans Windows Azure afin de fournir les fonctionnalités de sélecteur de personnes (c’est-à-dire de carnet d’adresses) et de résolution de nom du contrôle de la saisie.

Dans la Partie 2, j’explore tous les composants qui s’exécutent dans le Cloud – les classes de données qui sont utilisées pour travailler avec les files d’attente et le stockage en table Azure, un rôle de travail pour lire des éléments dans des files d’attente et remplir le stockage tabulaire et un serveur frontal WCF qui permet à une application cliente de créer de nouveaux éléments dans la file d’attente et faire tout le travail standard du sélecteur de personnes SharePoint – fournir une liste des types de revendications pris en charge, rechercher les valeurs de revendication et résoudre les revendications.

Dans ce dernier billet de cette série, nous allons explorer les différents composants utilisés du côté SharePoint, qui incluent un composant personnalisé créé à l’aide du kit CASI pour ajouter des éléments à la file d’attente et procéder aux appels au stockage en table Azure. Il y a également notre fournisseur de revendications personnalisées, qui utilisera le composant du kit CASI pour connecter SharePoint à ces fonctions Azure.

Tout d’abord, jetons un coup d’œil au composant du kit CASI personnalisé. Je ne vais pas y passer beaucoup de temps ici car le kit CASI est largement couvert dans ce blog. Ce composant particulier est décrit dans la partie 3 de la série de billets sur le kit CASI. Brièvement, j’ai créé un projet de nouvelle bibliothèque de classes Windows. J’ai ajouté des références à l’assembly de la classe de base du kit CASI et aux autres assemblys .NETS requis (que je décris dans la partie 3). J’ai ajouté dans mon projet une référence de service au point de terminaison WCF que j’ai créé dans la partie 2 de ce projet. Enfin, j’ai ajouté une nouvelle classe au projet, qui hérite de la classe de base du kit CASI, et j’ai ajouté le code pour substituer la méthode ExecuteRequest. Comme vous l’avez vu j’espère dans la série sur le kit CASI, voici ce à quoi ressemble mon code de substitution de la méthode ExecuteRequest :

       public class DataSource : AzureConnect.WcfConfig

       {

              public override bool ExecuteRequest()

              {

                     try

                     {

                           //crée l’instance proxy avec les liaisons et le point de terminaison que

                           //le contrôle de configuration de la classe de base a créés à cet effet

                           AzureClaims.AzureClaimsClient cust =

                                  new AzureClaims.AzureClaimsClient(this.FedBinding,

                                   this.WcfEndpointAddress);

 

                           //configurer le canal pour que nous puissions l’appeler avec

                           //FederatedClientCredentials.

                           SPChannelFactoryOperations.ConfigureCredentials<AzureClaims.IAzureClaims>

                                   (cust.ChannelFactory,

                                   Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

                           //créer un canal vers le point de terminaison WCF en utilisant le

                           //jeton et les revendications de l’utilisateur actuel

                           AzureClaims.IAzureClaims claimsWCF =

                                  SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

                                  <AzureClaims.IAzureClaims>(cust.ChannelFactory,

                                  this.WcfEndpointAddress,

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

 

                           //définir la propriété client pour la classe de base

                           this.WcfClientProxy = claimsWCF;

                     }

                     catch (Exception ex)

                     {

                           Debug.WriteLine(ex.Message);

                     }

 

                     //une fois la configuration terminée, appeler la méthode

                     return base.ExecuteRequest();

              }

       }

 

AzureClaims est le nom de la référence de service que j’ai créée et qui utilise l’interface IAzureClaims que j’ai définie dans mon projet WCF dans Azure. Comme expliqué précédemment dans la série de billets sur le kit CASI, il s’agit essentiellement de code réutilisable, j’ai juste inclus le nom de mon interface et la classe qui est exposée dans l’application WCF. Ensuite, comme expliqué aussi dans la série de billets sur le kit CASI, j’ai créé une page ASPX appelée AzureClaimProvider.aspx. J’ai juste copié et collé le code décrit dans la Partie 3 de la série de billets sur le kit CASI et substitué le nom de ma classe et le point de terminaison où l’atteindre. La balise de contrôle dans la page ASPX pour mon composant personnalisé du kit CASI ressemble à ceci :

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

 

Les points principaux à noter ici sont que j’ai créé un enregistrement CNAME pour « spsazure.vbtoys.com », qui pointe sur mon application Azure à cloudapp.net (c’est aussi décrit dans la Partie 3 de la série de le billets sur le kit CASI). J’ai défini la valeur par défaut MethodName que la page va invoquer comme GetClaimTypes, qui est une méthode qui ne prend aucun paramètre et retourne la liste de types de revendications que mon fournisseur de revendications Azure prend en charge. Cela en fait un bon test pour valider la connectivité entre mon application Azure et SharePoint. Je peux tout simplement accéder à http://tout_site_SharePoint/_layouts/AzureClaimProvider.aspx et si tout est configuré correctement, je verrai quelques données dans la page. Une fois que j’ai déployé mon projet en ajoutant l’assembly au Global Assembly Cache et en déployant la page sur le répertoire _layouts de SharePoint, j’ai accédé à la page dans l’un de mes sites et vérifié qu’elle retournait des données, j’ai ainsi su que ma connexion entre SharePoint et Azure fonctionnait.

Maintenant que la connexion est en place, j’accède enfin à la partie « sympa » du projet, qui consiste à faire les deux choses suivantes :

  1. 1.       Créer « un composant » qui enverra les informations sur les nouveaux utilisateurs à ma file d’attente Azure.
  2. 2.       Créer un fournisseur de revendications personnalisées qui utilisera mon composant du kit CASI personnalisé pour fournir des types de revendications, la résolution des noms et la recherche des revendications.

À ce stade, il convient de faire un arrêt et de prendre un peu de recul. Dans ce cas particulier, je voulais juste mettre en œuvre quelque chose aussi rapidement que possible. J’ai donc créé une nouvelle application Web et j’ai activé l’accès anonyme. Comme je suis sûr que vous le savez tous, cette activation au niveau de l’application Web seulement ne l’active PAS au niveau de la collection de sites. Donc pour ce scénario, je l’ai aussi activé sur la collection de sites racine seulement ; j’ai accordé l’accès à tout sur le site. Sur toute autre collection de sites, qui contiendrait des informations réservées aux membres uniquement, l’accès anonyme n’est PAS activé afin que seuls les utilisateurs auxquels les droits adéquats sont octroyés puissent y accéder.

La chose à déterminer maintenant est la façon de gérer les identités qui vont utiliser le site. Évidemment, ce n’est pas dans mon intention. Je pourrais inventer un certain nombre de méthodes pour synchroniser des comptes dans Azure ou quelque chose de bizarre comme ça, mais comme je l’ai expliqué dans la partie 1 de cette série de billets, il y a tout un tas de fournisseurs qui le font déjà et je vais donc les laisser continuer à le faire. Ce que je veux dire par là, c’est que j’ai tiré avantage d’un autre service de cloud Microsoft appelé le service de contrôle d’accès (ACS, Access Control Service). En bref, ACS agit comme un fournisseur d’identité pour SharePoint. J’ai donc juste créé une approbation entre ma batterie SharePoint et l’instance d’ACS que j’ai créée pour cette validation de principe. Dans ACS, j’ai ajouté SharePoint comme une partie de confiance, donc ACS sait où envoyer les utilisateurs une fois qu’ils se sont authentifiés. Dans ACS, je l’ai aussi configuré pour permettre aux utilisateurs de s’inscrire à l’aide de leurs comptes Gmail, Yahoo ou Facebook. Une fois inscrits, ACS obtient une revendication unique en retour (que j’utiliserai) – adresse de messagerie – et la renvoie à SharePoint.

Voici donc tout le contexte de la connexion – Azure offre le stockage en table et les files d’attente pour travailler avec les données, ACS fournit les services d’authentification, et le kit CASI fournit la connexion proprement dite aux données.

Mais, comment allons-nous utiliser toute cette infrastructure décrite ? Bien, j’ai toujours voulu que le processus pour devenir membre soit simple, j’ai donc écrit un composant WebPart pour ajouter des utilisateurs à ma file d’attente Azure. Ce composant vérifie si la demande est authentifiée (c’est-à-dire, si l’utilisateur a cliqué sur le lien Se connecter que l’on obtient sur un site anonyme, s’est inscrit auprès de l’un des fournisseurs que j’ai mentionnés plus haut, et qu’ACS m’a renvoyé leurs informations de revendication). Si la demande n’est pas authentifiée, le composant WebPart ne fait rien. Mais si elle est authentifiée, il affiche un bouton qui, si on clique dessus, se procure la revendication de messagerie de l’utilisateur et l’ajoute à la file d’attente Azure. C’est à ce stade que je dis qu’il faut prendre un peu de recul et réfléchir. Pour une validation de principe, c’est parfait, cela fonctionne. Toutefois, vous pouvez envisager d’autres façons de traiter cette demande. Par exemple, vous pourriez écrire les informations dans une liste SharePoint. Vous pourriez écrire un travail de minuteur personnalisé (avec lequel le kit CASI fonctionne très bien) et traiter périodiquement les nouvelles demandes à partir de cette liste. Vous pourriez utiliser SPWorkItem pour mettre en file d’attente les demandes à traiter plus tard. Vous pourriez les stocker dans une liste et ajouter un flux de travail personnalisé qui pourrait passer par un processus d’approbation ; une fois la demande approuvée, une action du flux de travail personnalisé peut être utilisée pour appeler le kit CASI afin que les détails soient placés dans la file d’attente Azure. En bref, beaucoup de puissance, de souplesse et de personnalisation sont possibles ici – ce qui laisse libre cours à votre imagination. À un moment donné, je pourrais écrire une autre version qui l’écrit dans une liste personnalisée, la traite de façon asynchrone à un certain moment, ajoute les données à la file d’attente Azure, puis ajoute automatiquement le compte au groupe des visiteurs dans l’un des sous-sites, afin que l’utilisateur soit inscrit et prêt à accéder. Mais ce serait l’objet d’un autre billet.

Donc, tout cela étant dit, comme décrit plus haut je me contente de laisser l’utilisateur cliquer sur un bouton, s’il est inscrit, et ensuite j’utiliserai mon composant du kit CASI personnalisé pour appeler le point de terminaison WCF et ajouter les informations à la file d’attente Azure. Voici le code du composant WebPart – assez simple, avec l’aimable autorisation du kit CASI :

public class AddToAzureWP : WebPart

{

 

       //bouton dont nous devons suivre l’événement clic pour

       //ajouter l’utilisateur à Azure

       Button addBtn = null;

       Label statusLbl = null;

 

       protected override void CreateChildControls()

       {

              if (this.Page.Request.IsAuthenticated)

              {

                     addBtn = new Button();

                     addBtn.Text = "Devenir membre";

                     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

              {

                     //rechercher l’identité basée sur les revendications

                     IClaimsPrincipal cp = Page.User as IClaimsPrincipal;

 

                     if (cp != null)

                     {

                           //obtenir l’identité basée sur les revendications afin de pouvoir énumérer les revendications

                           IClaimsIdentity ci = (IClaimsIdentity)cp.Identity;

 

                           //rechercher la revendication de messagerie

                           //vérifier s’il existe des revendications avant de continuer

                           if (ci.Claims.Count > 0)

                           {

                                  //rechercher la revendication d’adresse de messagerie

                                  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)

                                  {

                                         //créer la chaîne que nous allons envoyer à la file d’attente Azure : revendication, valeur et nom d’affichage

                                         //noter que j’utilise "#" comme séparateur parce qu’il n’y a qu’un seul paramètre et que le kit CASI

                                         //utilise ; comme séparateur donc une valeur différente est requise. Si on utilisait ; CASI essaierait

                                         //d’en faire trois paramètres, alors qu’en réalité il n’y en a qu’un

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

 

                                         //créer la connexion à Azure et charger

                                         //créer une instance du contrôle

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

 

                                         //définir les propriétés pour la récupération des données ; il faut configurer les propriétés de cache puisque nous l’utilisons par programmation

                                         //mais le cache n’est en fait pas utilisé dans ce cas

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

 

                                         //exécuter la méthode

                                         bool success = cfgCtrl.ExecuteRequest();

 

                                         if (success)

                                         {

                                                //signaler à l’utilisateur si ça a fonctionné

                                                statusLbl.Text = "<p>Vos infos ont été ajoutées avec succès. Vous pouvez maintenant contacter un des " +

                                                       "autres membres partenaires ou notre personnel de support pour obtenir les droits d’accès au contenu du " +

                                                       "partenaire. Notez qu’il faut jusqu’à 15 minutes pour que votre demande " +

                                                       "soit traitée.</p>";

                                         }

                                         else

                                         {

                                                statusLbl.Text = "<p>Un problème est survenu lors de l’ajout de vos infos dans Azure ; réessayez plus tard ou " +

                                                       "contactez le Support si le problème persiste.</p>";

                                         }

                                  }

                           }

                     }

              }

              catch (Exception ex)

              {

                     statusLbl.Text = "Un problème est survenu lors de l’ajout de vos infos dans Azure ; réessayez plus tard ou " +

                           "contactez le Support si le problème persiste.";

                     Debug.WriteLine(ex.Message);

              }

       }

}

 

Un bref compte-rendu du code ressemble à ceci : je m’assure tout d’abord que la demande est authentifiée ; si c’est le cas, j’ai ajouté le bouton à la page et j’ajoute un gestionnaire d’événements pour l’événement clic du bouton. Dans le gestionnaire de l’événement clic du bouton, j’obtiens une référence IClaimsPrincipal à l’utilisateur actuel, puis je regarde la collection de revendications de l’utilisateur. Je lance une requête LINQ sur la collection de revendications pour rechercher la revendication de messagerie, qui est la revendication d’identité pour mon SPTrustedIdentityTokenIssuer. Si je trouve la revendication de messagerie, je crée une chaîne concaténée avec le type de revendication, la valeur de revendication et un nom convivial pour la revendication. Encore une fois, ce n’est pas strictement requis dans ce scénario, mais puisque je voulais que ce code soit utilisable dans un scénario plus générique, je l’ai écrit de cette façon. Cette chaîne concaténée est la valeur pour la méthode sur le serveur WCF, qui ajoute des données à la file d’attente Azure. Je crée ensuite une instance de mon composant personnalisé du kit CASI et le configure pour appeler la méthode WCF qui ajoute les données à la file d’attente, puis j’appelle la méthode ExecuteRequest qui soumet effectivement les données.

Si j’obtiens une réponse indiquant que l’ajout des données à la file d’attente a réussi, j’avertis l’utilisateur, sinon, je lui signale qu’il y a eu un problème et de vérifier à nouveau plus tard. Dans un scénario réel, bien sûr, j’aurais davantage de journalisation des erreurs et pourrais donc retracer exactement ce qui s’est passé et pourquoi. Même comme cela cependant, le kit CASI va écrire les informations d’erreur dans les journaux ULS dans une étendue SPMonitoredScope, donc tout ce qui est effectué pour la demande aura un ID de corrélation unique avec lequel nous pouvons voir toutes les activités associées à la demande. Je suis parvenu à un stade relativement satisfaisant maintenant.

Nous avons exploré tous les composants de l’infrastructure, et j’ai montré comment les données sont ajoutées à la file d’attente Azure, puis extraites de là par un processus de travail pour être ajoutées dans le stockage en table. Maintenant, nous pouvons examiner le fournisseur de revendications personnalisées. Il va utiliser le kit CASI pour appeler et interroger le stockage en table Azure que j’utilise. Regardons les aspects les plus intéressants du fournisseur de revendications personnalisées.

Examinons d’abord quelques attributs de niveau classe :

//le point de terminaison WCF que nous allons utiliser pour se connecter aux fonctions de carnet d’adresses

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

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

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

 

//la valeur du type de revendication d’identité

private const string IDENTITY_CLAIM =

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

 

//la collection du type de revendication que nous prenons en charge ; elle ne changera pas au cours de

//la durée de vie STS (w3wp.exe), nous la mettrons donc en cache dès que nous l’obtenons

AzureClaimProvider.AzureClaims.ClaimTypeCollection AzureClaimTypes =

       new AzureClaimProvider.AzureClaims.ClaimTypeCollection();

 

Tout d’abord, j’utilise une constante pour désigner le point de terminaison WCF auquel le kit CASI doit se connecter. Vous remarquerez que j’ai un point de terminaison de test et un point de terminaison de production. Lorsque vous utilisez le kit CASI en programmation, comme nous le ferons pour notre fournisseur de revendications personnalisées, vous devez toujours lui indiquer où se trouve le point de terminaison WCF avec lequel il doit communiquer.

Ensuite, comme je l’ai décrit précédemment, j’utilise la revendication de messagerie comme revendication d’identité. Comme je m’y référerai un certain nombre de fois par le biais de mon fournisseur, j’ai simplement inclus une constante au niveau de la classe.

Enfin, j’ai une collection de AzureClaimTypes. J’ai expliqué dans la partie 2 de cette série de billets pourquoi j’utilise une collection ; je me contente de la stocker ici au niveau de la classe afin de ne pas avoir à réextraire ces infos chaque fois que ma méthode FillHierarchy est appelée. Les appels à Azure sont coûteux, donc je les minimise autant que possible.

Voici le prochain segment de code :

internal static string ProviderDisplayName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

internal static string ProviderInternalName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

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

//UTILISER CETTE PROPRIÉTÉ MAINTENANT LORS DE LA CRÉATION DE LA REVENDICATION POUR PICKERENTITY

internal static string SPTrustedIdentityTokenIssuerName

{

       get

       {

              return "SPS ACS";

       }

}

 

 

public override string Name

{

       get

       {

              return ProviderInternalName;

       }

}

 

Je voulais dans ce code souligner le fait que puisque mon fournisseur émet des revendications d’identité, il DOIT être le fournisseur par défaut pour SPTrustedIdentityTokenIssuer. L’objet de ce billet n’est pas d’expliquer comment faire cela, mais je l’ai traité ailleurs dans mon blog. La principale chose à retenir à ce propos est qu’il doit y avoir une forte relation entre le nom que vous utilisez pour votre fournisseur et le nom utilisé pour le SPTrustedIdentityTokenIssuer. La valeur que j’ai utilisée pour le ProviderInternalName est le nom que je dois insérer dans la propriété ClaimProviderName pour le SPTrustedIdentityTokenIssuer. Aussi, je dois utiliser le nom du SPTrustedIdentityTokenIssuer lorsque je crée des revendications d’identité pour les utilisateurs. J’ai donc créé un SPTrustedIdentityTokenIssuer appelé « SPS ACS » et je l’ai ajouté à ma propriété SPTrustedIdentityTokenIssuerName. C’est pourquoi ces valeurs sont codées ici.

Étant donné que je ne veux aucune augmentation de revendications chez ce fournisseur, je n’ai pas écrit de code pour remplacer FillClaimTypes, FillClaimValueTypes ou FillEntityTypes. Le prochain segment de code est FillHierarchy, qui est l’endroit où j’indique à SharePoint quels types de revendications je prends en charge. Voici donc le code en question :

try

{

       if (

               (AzureClaimTypes.ClaimTypes == null) ||

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

       )

       {

              //créer une instance du contrôle

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

 

              //définir les propriétés pour la récupération des données ; il faut configurer les propriétés de cache puisque nous l’utilisons par programmation

              //mais le cache n’est en fait pas utilisé dans ce cas

              cfgCtrl.WcfUrl = SVC_URI;

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

              cfgCtrl.MethodName = "GetClaimTypes";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = "GetClaimTypes";

              cfgCtrl.SharePointClaimsSiteUrl = context.AbsoluteUri;

 

              //exécuter la méthode

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //si ça a fonctionné, obtenir la liste des types de revendication

                     AzureClaimTypes =

                                                (AzureClaimProvider.AzureClaims.ClaimTypeCollection)cfgCtrl.QueryResultsObject;

              }

       }

 

       //s’assurer que le sélecteur demande le type d’entité que nous retournons ; ce n’est pas le cas de l’admin de collection de sites par exemple

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

              return;

 

       //à ce stade, nous avons tous les types de revendications possibles, donc on les ajoute à la hiérarchie

       //vérifier si hierarchyNodeID est null ; ce sera le cas quand le contrôle

       //est chargé initialement, mais si un utilisateur clique sur l’un des nœuds, il retournera

       //la clé du nœud cliqué. Cela vous permet de construire une

       //hiérarchie au fur et à mesure qu’un utilisateur clique sur quelque chose, plutôt qu’en une seule fois

       if (

               (string.IsNullOrEmpty(hierarchyNodeID)) &&

               (AzureClaimTypes.ClaimTypes.Count() > 0)

              )

       {

              //énumérer sur chaque type de revendication

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

              {

                     //au premier chargement, ajouter tous nos nœuds

                     hierarchy.AddChild(new

                                                Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(

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

              }

       }

}

catch (Exception ex)

{

       Debug.WriteLine("Erreur lors du remplissage de la hiérarchie : " + ex.Message);

}

 

Donc ici je vérifie si j’ai récupéré la liste des types de revendications que je prends déjà en charge. Si je ne l’ai pas, je crée une instance de mon contrôle personnalisé du kit CASI et fais un appel à mon WCF pour récupérer les types de revendications en appelant la méthode GetClaimTypes sur ma classe WCF. Si j’obtiens les données, je les insère dans la variable au niveau de la classe que j’ai décrite précédemment, appelée AzureClaimTypes, puis je l’ajoute à la hiérarchie de types de revendications que je prends en charge.

Les méthodes suivantes que nous allons examiner sont les méthodes FillResolve. Ces méthodes ont deux signatures différentes car elles font deux choses différentes. Dans un scénario, nous avons une revendication particulière avec sa valeur et son type et SharePoint veut juste vérifier qu’elle est valide. Dans le second cas, un utilisateur a juste tapé une valeur dans le contrôle de saisie SharePoint et cela équivaut en fait à faire une recherche de revendications. Voila pourquoi je vais les examiner séparément.

Dans le cas où j’ai une revendication spécifique et que SharePoint veut vérifier les valeurs, j’appelle une méthode personnalisée que j’ai écrite, appelée GetResolveResults. Dans cette méthode, je passe l’Uri où la requête est effectuée ainsi que le type de revendication et la valeur de revendication que SharePoint cherche à valider. La méthode GetResolveResults ressemble à ceci :

//Noter que claimType est passé ici en vue d’une extensibilité future ; mais dans

//le cas actuel, nous utilisons seulement des revendications d’identité

private AzureClaimProvider.AzureClaims.UniqueClaimValue GetResolveResults(string siteUrl,

       string searchPattern, string claimType)

{

       AzureClaimProvider.AzureClaims.UniqueClaimValue result = null;

 

       try

       {

              //créer une instance du contrôle

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

 

              //définir les propriétés pour la récupération des données ; il faut configurer les propriétés de cache puisque nous l’utilisons par programmation

              //mais le cache n’est en fait pas utilisé dans ce cas

              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;

 

              //exécuter la méthode

              bool success = cfgCtrl.ExecuteRequest();

 

              //si la requête n’a rencontré aucune erreur alors capturer le résultat

              if (success)

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

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

 

       return result;

}

 

Donc ici je crée une instance du contrôle personnalisé du kit CASI, puis j’appelle la méthode ResolveClaim sur mon WCF. Cette méthode prend deux paramètres que je passe en tant que valeurs délimitées par un point-virgule (parce que c’est la façon dont le kit CASI distingue les valeurs des différents paramètres). J’exécute ensuite la requête et si elle trouve une correspondance, elle retourne une seule UniqueClaimValue, sinon la valeur de retour est null. Dans ma méthode FillResolve, voici à quoi ressemble le code :

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

{

       //s’assurer que le sélecteur demande le type d’entité que nous retournons ; ce n’est pas le cas de l’admin de collection de sites par exemple

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

              return;

 

       try

       {

              //rechercher les revendications correspondantes

              AzureClaimProvider.AzureClaims.UniqueClaimValue result =

                     GetResolveResults(context.AbsoluteUri, resolveInput.Value,

                     resolveInput.ClaimType);

 

              //si une correspondance a été trouvée, l’ajouter à la liste de résolution

              if (result != null)

              {

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

                     SPClaimEntityTypes.User, result.DisplayName);

                           resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

Donc je vérifie tout d’abord que la requête correspond à une revendication d’utilisateur, puisque c’est le seul type de revendication que mon fournisseur retourne. Si la requête ne correspond pas à une revendication d’utilisateur, je la laisse tomber. Ensuite, j’appelle ma méthode pour résoudre la revendication et si j’obtiens un résultat non null, je le traite. Pour cela, j’appelle une autre méthode personnalisée que j’ai écrite, appelée GetPickerEntity. Ici, je passe le type et la valeur de revendication pour créer une revendication d’identité, et ensuite je peux ajouter cette PickerEntity qu’elle renvoie à la liste des instances PickerEntity passée à ma méthode. Je ne vais pas entrer dans la méthode GetPickerEntity, parce que ce billet est déjà incroyablement long et que j’ai déjà abordé la façon de procéder dans d’autres billets sur mon blog.

Maintenant parlons un peu de l’autre méthode FillResolve. Comme je l’ai expliqué plus haut, elle agit essentiellement comme une recherche donc je vais la plupart du temps combiner les méthodes FillResolve et FillSearch ici. Ces deux méthodes vont appeler une méthode personnalisée que j’ai écrite, appelée SearchClaims, qui ressemble à ceci :

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

       string siteUrl)

{

                    

       AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

              new AzureClaimProvider.AzureClaims.UniqueClaimValueCollection();

 

       try

       {

              //créer une instance du contrôle

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

 

              //définir les propriétés pour la récupération des données ; il faut configurer les propriétés de cache puisque nous l’utilisons par programmation

              //mais le cache n’est en fait pas utilisé dans ce cas

              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;

 

              //exécuter la méthode

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //si ça a fonctionné, obtenir le tableau des résultats

                     results =

 (AzureClaimProvider.AzureClaims.UniqueClaimValueCollection)cfgCtrl.QueryResultsObject;

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine("Erreur lors de la recherche des revendications : " + ex.Message);

       }

 

       return results;

}

 

Dans cette méthode, comme vous l’avez vu ailleurs dans ce billet, je crée juste une instance de mon contrôle du kit CASI personnalisé. J’appelle la méthode SearchClaims sur mon WCF et je lui passe le type de revendication que je veux rechercher, la valeur de revendication que je veux trouver dans ce type de revendication et le nombre maximum d’enregistrements à retourner. Vous vous souvenez peut-être, comme abordé dans la partie 2 de cette série de billets, que SearchClaims fait juste un BeginsWith sur le modèle de recherche qui est passé, donc avec beaucoup d’utilisateurs il pourrait facilement y avoir plus de 200 résultats. Mais 200 est le nombre maximum de correspondances que montrera le sélecteur de personnes, c’est donc tout ce que je demande. Si vous croyez que les utilisateurs vont faire défiler plus de 200 résultats pour rechercher un résultat, je peux vous garantir que c’est peu probable.

Maintenant que nous avons notre collection de UniqueClaimValues, examinons la façon de l’utiliser dans nos deux méthodes de substitution dans le fournisseur de revendications personnalisées. Tout d’abord, voici à quoi ressemble la méthode FillResolve :

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

{

       //cette version de résolution est comme une recherche, donc nous allons la traiter comme ça

       //s’assurer que le sélecteur demande le type d’entité que nous retournons ; ce n’est pas le cas de l’admin de collection de sites par exemple

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

              return;

 

       try

       {

              //faire la recherche de correspondances

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, resolveInput, context.AbsoluteUri);

 

              //ajouter une entité du sélecteur pour chaque correspondance

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

       }

}

Elle appelle simplement la méthode SearchClaims, et pour chaque résultat obtenu en retour (le cas échéant), elle crée une nouvelle PickerEntity et l’ajoute à la liste des entités passée dans la substitution. Elles s’afficheront toutes ensuite dans le contrôle de saisie dans SharePoint. La méthode FillSearch l’utilise comme ceci :

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

{

       //s’assurer que le sélecteur demande le type d’entité que nous retournons ; ce n’est pas le cas de l’admin de collection de sites par exemple

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

              return;

 

       try

       {

              //faire la recherche de correspondances

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, searchPattern, context.AbsoluteUri);

 

              //s’il y avait plus de zéro résultat, les ajouter au sélecteur

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

              {

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

                     {

                           //nœud où nous insèrerons nos correspondances

                           Microsoft.SharePoint.WebControls.SPProviderHierarchyNode matchNode = null;

 

                           //obtenir une entité du sélecteur à ajouter à la boîte de dialogue

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

 

                           //ajouter le nœud à l’endroit où il doit être affiché

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

                           {

                                  //créer le nœud où afficher notre correspondance

                                  matchNode = new

                                         SPProviderHierarchyNode(ProviderInternalName,

                                         cv.DisplayName, cv.ClaimType, true);

 

                                  //l’ajouter à l’arborescence

                                  searchTree.AddChild(matchNode);

                           }

                           else

                                  //obtenir le nœud pour cette équipe

                                  matchNode = searchTree.Children.Where(theNode =>

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

 

                           //ajouter la correspondance à notre nœud

                           matchNode.AddEntity(pe);

                     }

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

Dans FillSearch, j’appelle à nouveau ma méthode SearchClaims. Pour chaque UniqueClaimValue retournée (le cas échéant), je vérifie si j’ai ajouté le type de revendication au nœud de la hiérarchie des résultats. Encore une fois, dans ce cas je vais toujours retourner un type de revendication (messagerie) seulement, mais j’ai écrit ceci pour être extensible afin vous puissiez utiliser plusieurs types de revendications ultérieurement. J’ajoute le nœud à la hiérarchie s’il n’existe pas, ou je le trouve s’il existe. Je prends l’entité PickerEntity que j’ai créée à partir de UniqueClaimValue et je l’ajoute au nœud de la hiérarchie. Et c’est tout.

Je ne vais pas aborder la méthode FillSchema ni les quatre substitutions de propriétés booléennes que chaque fournisseur de revendications personnalisées doit avoir, parce qu’elles ne contiennent rien de spécial pour ce scénario et que j’en ai décrit les principes de base dans d’autres billets sur ce blog. Je ne vais pas non plus parler du récepteur de fonctionnalité utilisé pour enregistrer ce fournisseur de revendications personnalisées parce que lui-aussi ne contient rien de spécial pour ce projet et que je l’ai décrit ailleurs. Après l’avoir compilé, il vous suffit de vérifier que votre assembly pour le fournisseur de revendications personnalisées ainsi que pour le composant du kit CASI personnalisé est enregistré dans le Global Assembly Cache sur chaque serveur de la batterie ; par ailleurs, vous devez configurer le SPTrustedIdentityTokenIssuer pour qu’il utilise votre fournisseur de revendications personnalisées comme fournisseur par défaut (également expliqué ailleurs dans ce blog).

Voici donc le scénario de base, de bout en bout. Lorsque vous êtes sur le site SharePoint et que vous ajoutez un nouvel utilisateur (revendication de messagerie en fait), le fournisseur de revendications personnalisées est appelé tout d’abord pour obtenir la liste des types de revendications pris en charge et puis, à nouveau, lorsque vous tapez une valeur dans le contrôle de saisie ou recherchez une valeur en utilisant le sélecteur de personnes. Dans chaque cas, le fournisseur de revendications personnalisées utilise le contrôle du kit CASI personnalisé pour présenter un appel authentifié à Windows Azure afin de communiquer avec notre WCF qui utilise nos classes de données personnalisées pour récupérer les données du stockage en table Azure. Nous déballons les résultats retournés et le présentons à l’utilisateur. Avec cela, vous avez votre solution extranet SharePoint et Azure clé en main, complète et prête à l’emploi, que vous pouvez utiliser telle quelle, ou modifier en fonction de vos besoins. Tout le code source du composant du kit CASI personnalisé, du composant WebPart qui enregistre l’utilisateur dans une file d’attente Azure et du fournisseur de revendications personnalisées est joint à ce billet. J’espère que vous en profiterez, que vous le trouverez utile et que vous commencerez à voir comment relier ces services distincts pour créer des solutions à vos problèmes. Voici quelques captures d’écran de la solution finale :

Site racine en tant qu’utilisateur anonyme :

Voici à quoi il ressemble après vous être authentifié ; notez que le composant WebPart affiche maintenant le bouton Devenir membre (Request Membership) :

Voici un exemple du sélecteur de personnes en action, après une recherche de valeurs de revendication qui commencent par « sp » :

 

Ce billet de blog a été traduit de l’anglais. Vous trouverez la version originale ici The Azure Custom Claim Provider for SharePoint Project Part 3