Partager via


Provider di attestazioni personalizzate Azure per un progetto di SharePoint - Parte 3

Articolo originale pubblicato martedì 21 febbraio 2012

Nella Parte 1 di questa serie sono stati descritti brevemente gli obiettivi di questo progetto, che in linea generale consiste nell'utilizzare Windows Azure Table Storage come archivio dati per un provider di attestazioni personalizzate di SharePoint. Il provider di attestazioni utilizzerà il kit CASI per recuperare i dati necessari da Windows Azure e fornire le funzionalità di selezione utenti, ad esempio la Rubrica, e di risoluzione dei nomi nel controllo di digitazione. 

Nella Parte 2 sono stati descritti tutti i componenti che vengono eseguiti nel cloud, ovvero le classi dati per l'utilizzo di Azure Table Storage e delle code, un ruolo di lavoro per la lettura di elementi dalle code e per l'inserimento di dati in Table Storage, nonché un server front-end WCF che consente a un'applicazione client di creare nuovi elementi nella coda e di eseguire tutte le operazioni correlate alla funzionalità di selezione utenti di SharePoint standard, ovvero fornire un elenco di tipi di attestazioni supportate, cercare valori di attestazioni e risolvere le attestazioni.

In questa parte finale della serie verranno esaminati i diversi componenti utilizzati sul lato SharePoint, incluso un componente personalizzato creato utilizzando il kit CASI per aggiungere elementi nella coda ed effettuare chiamate ad Azure Table Storage. È incluso inoltre un provider di attestazioni personalizzate, che utilizzerà il componente del kit CASI per la connessione di SharePoint a tali funzioni di Azure.

Per iniziare, esaminiamo rapidamente il componente del kit CASI personalizzato. Questo componente non verrà esaminato in modo approfondito, perché il kit CASI è già descritto nei dettagli in questo stesso blog. Questo componente specifico è descritto nella Parte 3 della serie del kit CASI. In poche parole, è stato creato un nuovo progetto libreria di classi di Windows. Sono stati aggiunti riferimenti all'assembly di classi di base del kit CASI e agli altri assembly .NET necessari (descritti nella Parte 3). È stato aggiunto inoltre nell'endpoint WCF creato nella Parte 2 di questo progetto un riferimento al servizio contenuto nel progetto. Infine, è stata aggiunta una nuova classe al progetto, facendo in modo che ereditasse la classe di base del kit CASI ed è stato aggiunto il codice per l'override del metodo ExecuteRequest. Come è possibile osservare nella serie del kit CASI, il codice per l'override del metodo ExecuteRequest è il seguente:

       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" è il nome del riferimento al servizio che è stato creato. Utilizza l'interfaccia IAzureClaims definita nel progetto WCF in Azure. Come già illustrato precedentemente nella serie del kit CASI, si tratta sostanzialmente di codice boilerplate. Sono stati semplicemente inseriti il nome dell'interfaccia e la classe esposta nell'applicazione WCF. È stata creata inoltre una pagina ASPX denominata AzureClaimProvider.aspx, come descritto nella serie del kit CASI. È stato semplicemente copiato e incollato il codice descritto nella Parte 3 della serie del kit CASI e sono stati sostituiti il nome della classe e l'endpoint in cui può essere raggiunta. Il tag di controllo nella pagina ASPX del componente del kit CASI personalizzato è il seguente:

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

 

È importante sottolineare che è stato creato un record CNAME per "spsazure.vbtoys.com" che punta all'applicazione Azure in cloudapp.net (anche questo descritto nella Parte 3 del kit CASI). È stato impostato il MethodName predefinito che verrà richiamato dalla pagina come GetClaimTypes, un metodo che non accetta parametri e restituisce un elenco di tipi di attestazioni supportati dal provider di attestazioni Azure. Questo è un ottimo test per verificare la connettività tra l'applicazione Azure e SharePoint. È sufficiente passare a https://anySharePointsite/_layouts/AzureClaimProvider.aspx e, se tutto è configurato correttamente, saranno visibili alcuni dati nella pagina. Una volta distribuito il progetto aggiungendo l'assembly alla Global Assembly Cache e distribuendo la pagina nella directory di SharePoint _layouts, ho aperto la pagina in uno dei miei siti e ho verificato che restituisse dati, a conferma del funzionamento della connessione tra SharePoint e Azure.

Dopo aver creato la struttura di base, è finalmente possibile passare alla parte "divertente" del progetto, che si esplica in due operazioni:

  1. 1.       Creare "alcuni componenti" per l'invio di informazioni su nuovi utenti alla coda di Azure
  2. 2.       Creare un provider di attestazioni personalizzate che utilizzerà il componente del kit CASI personalizzato per fornire tipi di attestazioni e le funzionalità di risoluzione dei nomi e di ricerca delle attestazioni

Questo è il momento ideale per fermarsi e fare un passo indietro. In questo caso specifico, desideravo solo ottenere un risultato il più rapidamente possibile. In pratica è stata creata una nuova applicazione Web ed è stato abilitato l'accesso anonimo. Come è noto, l'abilitazione solo a livello di applicazione Web NON comporta l'abilitazione a livello di raccolta siti. Per questo scenario pertanto l'abilitazione è stata eseguita solo nella raccolta siti radice, consentendo l'accesso a tutti gli elementi del sito. In tutte le altre raccolte siti, contenenti informazioni solo per i membri, NON è stato abilitato l'accesso anonimo e pertanto gli utenti devono disporre dei diritti appropriati per accedervi.

È ora il momento di pensare a come gestire le identità che utilizzeranno il sito. Ovviamente non è un aspetto di cui desidero occuparmi. Sarebbe stato possibile presentare numerosi metodi diversi per la sincronizzazione degli account in Azure o altre soluzioni analoghe, ma come è stato spiegato nella Parte 1 di questa serie, esistono già molti provider che svolgono questa attività e che possono rivelarsi utili per questo scopo. È stato utilizzato quindi un altro servizio cloud di Microsoft denominato Servizio di controllo di accesso, che funziona come un provider di identità per SharePoint. È stata creata pertanto solo una relazione di trust tra la farm di SharePoint e l'istanza del Servizio di controllo di accesso creata per questo modello di prova. Nel Servizio di controllo di accesso è stato aggiunto SharePoint come relying party, per indicare al servizio dove inviare gli utenti dopo l'autenticazione. Il Servizio di controllo di accesso è stato configurato inoltre in modo da consentire agli utenti di accedere utilizzando i propri account Gmail, Yahoo o Facebook. Dopo che gli utenti hanno eseguito l'accesso, il Servizio di controllo di accesso ottiene una singola attestazione, ovvero l'indirizzo di posta elettronica, e la invia a SharePoint.

Questo è tutto per quanto riguarda la struttura di base, ovvero Azure Table Storage e le code per l'utilizzo dei dati, i servizi di autenticazione del Servizio di controllo di accesso e la struttura di base fornita dal kit CASI per i dati.

In che modo è possibile utilizzare questa struttura di base? Poiché desideravo semplificare ulteriormente il processo, ho scritto una web part per l'aggiunta di utenti alla coda di Azure. Questa web part controlla che la richiesta sia autenticata, ovvero che l'utente abbia fatto clic sul collegamento di accesso visualizzato in un sito anonimo, che abbia eseguito l'accesso a uno dei provider sopra citati e che il Servizio di controllo di accesso mi abbia inviato le informazioni di attestazione. Se la richiesta non è autenticata, la web part non effettua alcuna operazione. Se invece la richiesta è autenticata, nella web part viene visualizzato un pulsante che, se selezionato, consente di acquisire l'attestazione basata su posta elettronica dell'utente e di aggiungerla nella coda di Azure. Questo è il punto in cui ho proposto di fare un passo indietro e di esaminare bene la questione. Per un modello di prova, questo processo funziona perfettamente. È tuttavia possibile escogitare altri modi per elaborare la richiesta. Si supponga ad esempio che si scrivano le informazioni in un elenco SharePoint. In questo caso è possibile scrivere un processo timer personalizzato, con cui il kit CASI interagisce perfettamente, ed elaborare periodicamente le nuove richieste dall'elenco. È possibile utilizzare SPWorkItem per accodare le richieste da elaborare in un secondo momento, archiviarlo in un elenco e aggiungere un flusso di lavoro personalizzato per l'esecuzione di un processo di approvazione. Una volta che è stata approvata, la richiesta utilizza un'azione del flusso di lavoro personalizzato per richiamare il kit CASI e inviare i dettagli alla coda di Azure. In poche parole, questa soluzione ha MOLTE potenzialità, è flessibile e supporta le personalizzazioni. È sufficiente usare l'immaginazione. È possibile che a un certo punto mi dedichi alla creazione di un'altra versione di questa soluzione che scriva in un elenco personalizzato, esegua un'elaborazione asincrona, aggiunga i dati nella coda di Azure e quindi aggiunga automaticamente l'account al gruppo dei visitatori in uno dei siti secondari. In questo modo verrebbe elaborato l'accesso dell'utente, che così potrebbe procedere direttamente. Ma questo sarà eventualmente materiale per un altro post.

Detto questo, come già affermato precedentemente, farò in modo che l'utente che ha eseguito l'accesso faccia clic su un pulsante e quindi utilizzerò il componente del kit CASI personalizzato per chiamare l'endpoint WCF e aggiungere le informazioni nella coda di Azure. Il codice per la web part è il seguente ed è piuttosto semplice, grazie al kit CASI:

public class AddToAzureWP : WebPart

{

 

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

       //add the user to Azure

       Button addBtn = null;

       Label statusLbl = null;

 

       protected override void CreateChildControls()

       {

              if (this.Page.Request.IsAuthenticated)

              {

                     addBtn = new Button();

                     addBtn.Text = "Request Membership";

                     addBtn.Click += new EventHandler(addBtn_Click);

                     this.Controls.Add(addBtn);

 

                     statusLbl = new Label();

                     this.Controls.Add(statusLbl);

              }

       }

 

       void addBtn_Click(object sender, EventArgs e)

       {

              try

              {

                     //look for the claims identity

                     IClaimsPrincipal cp = Page.User as IClaimsPrincipal;

 

                     if (cp != null)

                     {

                           //get the claims identity so we can enum claims

                           IClaimsIdentity ci = (IClaimsIdentity)cp.Identity;

 

                           //look for the email claim

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

                           if (ci.Claims.Count > 0)

                           {

                                  //look for the email address claim

                                  var eClaim = from Claim c in ci.Claims

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

                                         select c;

 

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

 

                                  if (ret != null)

                                  {

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

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

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

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

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

 

                                         //create the connection to Azure and upload

                                         //create an instance of the control

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

 

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

                                         //cache is not actually used in this case though

                                         cfgCtrl.WcfUrl = AzureCCP.SVC_URI;

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

                                         cfgCtrl.MethodName = "AddClaimsToQueue";

                                         cfgCtrl.MethodParams = qValue;

                                         cfgCtrl.ServerCacheTime = 10;

                                         cfgCtrl.ServerCacheName = ret.Value;

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

 

                                         //execute the method

                                         bool success = cfgCtrl.ExecuteRequest();

 

                                         if (success)

                                         {

                                                //if it worked tell the user

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

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

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

                                                       "processed.</p>";

                                         }

                                         else

                                         {

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

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

                                         }

                                  }

                           }

                     }

              }

              catch (Exception ex)

              {

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

                           "contact Support if the problem persists.";

                     Debug.WriteLine(ex.Message);

              }

       }

}

 

Segue una breve spiegazione del codice: viene verificato innanzitutto che la richiesta sia autenticata. In caso affermativo, viene aggiunto il pulsante alla pagina, insieme a un gestore eventi per l'evento clic del pulsante. Nel gestore eventi del clic del pulsante viene ottenuto un riferimento IClaimsPrincipal all'utente corrente e quindi viene esaminato l'insieme di attestazioni dell'utente. Viene eseguita una query LINQ sull'insieme di attestazioni per cercare l'attestazione basata su posta elettronica, ovvero l'attestazione d'identità per SPTrustedIdentityTokenIssuer. Dopo aver trovato l'attestazione basata su posta elettronica, viene creata una stringa concatenata con il tipo, il valore e il nome descrittivo dell'attestazione. Come già affermato, non si tratta di un'operazione obbligatoria per questo scenario, ma la scelta di questo tipo di codifica è stata dettata dall'esigenza di renderla utilizzabile per uno scenario più generico. La stringa concatenata è il valore per il metodo disponibile in WCF per l'aggiunta di dati nella coda di Azure. Viene quindi creata un'istanza del componente del kit CASI personalizzato, che viene configurata per chiamare il metodo WCF per l'aggiunta di dati nella coda. Viene infine chiamato il metodo ExecuteRequest per l'effettiva attivazione dei dati. 

Se si ottiene una risposta in cui viene indicato che l'aggiunta di dati nella coda ha avuto esito positivo, l'utente viene informato, altrimenti riceve una notifica in cui viene indicato che si è verificato un problema e che potrebbe essere necessario effettuare una verifica successivamente. In uno scenario reale naturalmente sarebbe attiva una registrazione degli errori più completa e sarebbe quindi possibile scoprire immediatamente cosa è successo e perché. Anche con la configurazione attuale tuttavia il kit CASI scriverà eventuali informazioni di errore nei registri ULS in SPMonitoredScope e pertanto tutte le operazioni effettuate per la richiesta saranno associate a un ID di correlazione univoco in base al quale è possibile visualizzare tutte le attività associate alla richiesta. Lo stato attuale è quindi piuttosto buono.

Sono stati esaminati tutti i componenti della struttura di base ed è stato illustrato come vengono aggiunti i dati nella coda di Azure e come vengono estratti dalla coda da un processo di lavoro e aggiunti in Table Storage. Questo è effettivamente l'obiettivo finale, perché ora è finalmente possibile esaminare il provider di attestazioni personalizzate. Verrà utilizzato il kit CASI per chiamare e interrogare l'istanza di Azure Table Storage in uso. Verranno esaminati ora gli aspetti più importanti del provider di attestazioni personalizzate.

Vengono analizzati innanzitutto alcuni attributi a livello di classe:

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

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

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

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

 

//the identity claimtype value

private const string IDENTITY_CLAIM =

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

 

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

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

AzureClaimProvider.AzureClaims.ClaimTypeCollection AzureClaimTypes =

       new AzureClaimProvider.AzureClaims.ClaimTypeCollection();

 

Innanzitutto, viene utilizzata una costante per fare riferimento all'endpoint WCF a cui deve connettersi il kit CASI. Come è possibile osservare, sono disponibili un endpoint di testing e un endpoint di produzione. Quando si utilizza il kit CASI a livello di programmazione, come nel provider di attestazioni personalizzate, è sempre necessario indicargli dove si trova l'endpoint WCF con cui deve interagire.

Successivamente, come già descritto in precedenza, viene utilizzata l'attestazione basata su posta elettronica come attestazione d'identità. Poiché verrà utilizzata molte volte come riferimento nel provider, è stata inserita una costante a livello di classe.

È presente infine un insieme di AzureClaimTypes. Nella Parte 2 di questa serie è stato già spiegato perché utilizzare un insieme. Verrà archiviato a livello di classe in modo da non dover ricercare le informazioni corrispondenti ogni volta che viene richiamato il metodo FillHierarchy. Poiché infatti le chiamate ad Azure sono onerose, è opportuno ridurle al minimo.

Viene riportato di seguito il successivo blocco di codice:

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;

       }

}

 

È stato messo in evidenza questo codice perché dal momento che il provider emette attestazioni d'identità, DEVE essere il provider predefinito per SPTrustedIdentityTokenIssuer. La spiegazione di questo processo non rientra negli obiettivi di questo post, ma viene comunque illustrato in altri punti del blog. L'elemento più importante di cui tenere conto è la necessità di una forte relazione tra il nome utilizzato per il provider e il nome utilizzato per SPTrustedIdentityTokenIssuer. Il valore utilizzato per ProviderInternalName è il nome che deve essere specificato nella proprietàClaimProviderName per SPTrustedIdentityTokenIssuer. È inoltre necessario utilizzare il nome di SPTrustedIdentityTokenIssuer quando vengono create attestazioni d'identità per gli utenti. È stato creato pertanto un SPTrustedIdentityTokenIssuer denominato "SPS ACS", che è stato aggiunto alla proprietà SPTrustedIdentityTokenIssuerName. È per questo motivo che sono stati codificati questi valori.

Dal momento che non verranno aumentate le attestazioni in questo provider, non è stato scritto alcun elemento di codice per l'override di FillClaimTypes, FillClaimValueTypes o FillEntityTypes. Il blocco di codice successivo è FillHierarchy, dove vengono indicati a SharePoint i tipi di attestazioni supportati. Viene riportato di seguito il relativo codice:

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

}

 

È ora necessario controllare se si dispone già dell'elenco dei tipi di attestazioni supportati. In caso contrario, viene creata un'istanza del controllo del kit CASI personalizzato e viene effettuata una chiamata a WCF per recuperare i tipi di attestazioni. A tale scopo, viene chiamato il metodo GetClaimTypes nella classe WCF. Gli eventuali dati ottenuti vengono inseriti nella variabile a livello di classe denominata AzureClaimTypes descritta precedentemente e quindi aggiunti alla gerarchia di tipi di attestazioni supportati.

I metodi successivi che verranno esaminati sono i metodi FillResolve. Questi metodi dispongono di due firme diverse perché eseguono due operazioni diverse. In uno scenario è presente un'attestazione specifica con valore e tipo e SharePoint ne verifica solo la validità. Nel secondo caso invece un utente si limita a digitare alcuni valori nel controllo di digitazione di SharePoint, operazione che in pratica equivale a ricercare le attestazioni. Per questo motivo, questi casi verranno esaminati separatamente.

Se è presente un'attestazione specifica di cui SharePoint deve verificare i valori, viene chiamato un metodo già scritto, denominato GetResolveResults. In questo metodo vengono passati l'URI in cui viene effettuata la richiesta e il tipo e il valore dell'attestazione che SharePoint tenta di convalidare. GetResolveResults è simile al seguente:

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

}

 

Verrà creata ora un'istanza del controllo del kit CASI personalizzato per poi chiamare il metodo ResolveClaim in WCF. Poiché tale metodo accetta due parametri, vengono utilizzati valori delimitati da punti e virgola (è in questo modo che il kit CASI distingue valori di parametri diversi). Si procede quindi all'esecuzione della richiesta. Se viene trovata una corrispondenza, verrà restituito un singolo valore UniqueClaimValue, altrimenti verrà restituito un valore nullo. Tornando al metodo FillResolve, il codice è il seguente:

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

       }

}

 

Viene verificato innanzitutto che la richiesta sia per un'attestazione utente, poiché è l'unico tipo di attestazione restituito dal provider. Se la richiesta non è relativa a un'attestazione utente, viene ignorata, quindi viene chiamato il metodo per risolvere l'attestazione e, se si ottiene un risultato non nullo, si procede all'elaborazione. Per l'elaborazione, viene chiamato un altro metodo personalizzato già scritto denominato GetPickerEntity. Vengono quindi passati il tipo e il valore dell'attestazione per creare un'attestazione d'identità. A questo punto è possibile aggiungere all'elenco di istanze di PickerEntity passate nel metodo l'elemento PickerEntity restituito. Il metodo GetPickerEntity non verrà esaminato perché è stato già illustrato in altri post del blog e questo post è già incredibilmente lungo.

Verrà esaminato ora l'altro metodo FillResolve. Come già spiegato precedentemente, funziona in pratica come una ricerca e pertanto verranno combinati i metodi FillResolve e FillSearch. Entrambi i metodi chiameranno un metodo personalizzato già scritto, denominato SearchClaims, il cui codice è il seguente:

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 questo metodo, come è stato già possibile osservare nel post, verrà creata un'istanza del controllo del kit CASI personalizzato. Verrà chiamato il metodo SearchClaims in WCF e verranno passati il tipo di attestazione in cui si desidera effettuare la ricerca, il valore dell'attestazione da trovare nel tipo di attestazione e il numero massimo di record da restituire. Come indicato nella Parte 2 di questa serie, SearchClaims esegue semplicemente un'operazione BeginsWith nel criterio di ricerca in cui viene passato. In presenza di molti utenti, è possibile pertanto che vengano restituiti più di 200 risultati. Tuttavia 200 è il numero massimo di corrispondenze che verranno mostrate dalla selezione utenti ed è questo il numero massimo di record che viene richiesto. È un'illusione infatti pensare che gli utenti scorrano più di 200 risultati per individuare quello desiderato.

Ora che è stato ottenuto un insieme di UniqueClaimValues, verrà illustrato come utilizzare i due metodi di override nel provider di attestazioni personalizzate. Innanzitutto viene riportato di seguito il metodo FillResolve:

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

{

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

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

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

              return;

 

       try

       {

              //do the search for matches

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, resolveInput, context.AbsoluteUri);

 

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

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

              {

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

                     resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

Viene chiamato il metodo SearchClaims e, per ogni eventuale risultato ottenuto, viene creato un nuovo elemento PickerEntity, che viene aggiunto al relativo elenco passato nell'override. Tutti questi elementi verranno visualizzati nel controllo di digitazione in SharePoint. Viene utilizzato dal metodo FillSearch come indicato di seguito:

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 verrà chiamato di nuovo il metodo SearchClaims. Per ogni eventuale elemento UniqueClaimValue restituito, viene controllato che il tipo di attestazione sia stato aggiunto nei nodi della gerarchia dei risultati. Di nuovo, in questo caso verrà restituito sempre un solo tipo di attestazione (basata su posta elettronica), ma il codice è comunque stato scritto in modo che sia estendibile per poter eventualmente utilizzare ulteriori tipi di attestazioni in un secondo momento. Viene aggiunto quindi il nodo della gerarchia, qualora non esista, oppure ne viene verificata l'esistenza. L'elemento PickerEntity creato dal valore UniqueClaimValue viene aggiunto al nodo della gerarchia. Questo è tutto.

Non verrà descritto il metodo FillSchema o gli altri quattro override di proprietà booleane di cui deve disporre ogni provider di attestazioni personalizzate, poiché non rivestono alcuna importanza speciale per questo scenario e i relativi concetti di base sono stati già illustrati in altri post del blog. Non verrà trattato neanche il ricevitore di funzionalità utilizzato per registrare questo provider di attestazioni personalizzate poiché, di nuovo, non riveste alcuna importanza per il progetto ed è stato già descritto altrove. Dopo la compilazione, è sufficiente verificare che l'assembly del provider di attestazioni personalizzate e del componente del kit CASI personalizzato sia registrato nella Global Assembly Cache in ogni server della farm. È inoltre necessario configurare SPTrustedIdentityTokenIssuer in modo che utilizzi il provider di attestazioni personalizzate come provider predefinito (come illustrato altrove in questo blog). 

Questo è lo scenario di base dall'inizio alla fine. Quando nel sito di SharePoint si tenta di aggiungere un nuovo utente (attestazione basata su posta elettronica), il provider di attestazioni personalizzate viene richiamato una prima volta per visualizzare un elenco di tipi di attestazioni supportati e quindi di nuovo quando viene digitato un valore nel controllo di digitazione o se si cerca un valore utilizzando la selezione utenti. In ogni caso il provider di attestazioni personalizzate utilizza il controllo del kit CASI personalizzato per effettuare una chiamata autenticata a Windows Azure per contattare WCF, che utilizza le classi di dati personalizzate per recuperare dati da Azure Table Storage. Vengono restituiti quindi risultati, che vengono infine presentati all'utente. In questo modo si dispone di una soluzione "Extranet pronta" completa di SharePoint e Azure chiavi in mano che può essere utilizzata così com'è o modificata in base alle esigenze. Il codice sorgente per il componente del kit CASI personalizzato, la web part che registra l'utente in una coda di Azure e il provider di attestazioni personalizzate sono allegati a questo post. Mi auguro che lo apprezziate, lo troviate utile e possiate iniziare a capire come associare questi servizi separati per creare le soluzioni ai vostri problemi. Di seguito sono disponibili alcune catture di schermata della soluzione finale:

Sito radice come utente anonimo:

Questo è l'aspetto dopo l'autenticazione. Nella web part è ora visibile il pulsante di richiesta di appartenenza Request Membership:

Questo è un esempio della selezione utenti in azione, dopo la ricerca dei valori di attestazioni che iniziano con "sp":

 

Questo è un post di blog localizzato. L'articolo originale è disponibile in The Azure Custom Claim Provider for SharePoint Project Part 3.