Proveedor de notificaciones personalizado Azure para el proyecto de SharePoint Project: parte 3

Artículo original publicado el martes, 21 de febrero de 2012

En la parte 1 de esta serie, describí brevemente los objetivos de este proyecto, que en un nivel elevado es usar el almacenamiento de tablas de Windows Azure como almacén de datos para un proveedor de notificaciones personalizado de SharePoint. El proveedor de notificaciones usará el CASI Kit para recuperar los datos que necesita de Windows Azure para proporcionar la funcionalidad de selector de personas (por ejemplo, libreta de direcciones) y resolución de nombres de tipo en control.

En la parte 2, describí todos los componentes que se ejecutan en la nube; las clases de datos que se usan para trabajar con almacenamiento de tablas y colas de Azure, un rol de trabajo para leer los elementos de las colas y poblar el almacenamiento de tablas y un front-end de WCF que permite a una aplicación cliente crear nuevos elementos en la cola, así como hacer todas las tareas de selección de usuarios estándar de SharePoint, como por ejemplo, proporcionar una lista de tipos de notificaciones admitidas, buscar de valores de notificación y resolver notificaciones.

En esta última parte de esta serie, miraremos los distintos componentes que se usan en el extremo de SharePoint. Incluye un componente personalizado construido mediante CASI Kit para agregar elementos a la cola, así como realizar nuestras llamadas al almacenamiento de tablas de Azure. También incluye nuestro proveedor de notificaciones personalizado, que usará el componente CASI Kit para conectar SharePoint con estas funciones de Azure.

Para comenzar, echemos un vistazo al componente CASI Kit personalizado. No dedicaré mucho tiempo en esto porque el CASI Kit se cubre extensivamente en este blog. Este componente en particular se describe en la parte 3 de la serie sobre CASI Kit. En pocas, he creado un nuevo proyecto de biblioteca de clases de Windows. Agregué referencias al ensamblado de clases base de CASI Kit y los otros ensamblados de .NET necesarios (que describo en la parte 3). He agregado una referencia de servicio en mi proyecto al extremo de WCF que creé en la parte 2 de este proyecto. Por último, agregué una nueva clase al proyecto e hice que heredara la clase base de CASI Kit. Además agregué el código para invalidar el método ExecuteRequest. Como seguramente ha observado en la serie sobre CASI Kit, el código para invalidar ExecuteRequest tiene el aspecto siguiente:

       public class DataSource : AzureConnect.WcfConfig

       {

              public override bool ExecuteRequest()

              {

                     try

                     {

                           //crear la instancia de proxy con enlaces y establezca el extremo de la clase base

                           //control de configuración que se ha creado para esto

                           AzureClaims.AzureClaimsClient cust =

                                  new AzureClaims.AzureClaimsClient(this.FedBinding,

                                   this.WcfEndpointAddress);

 

                           //configurar el canal para que se pueda llamar con

                           //FederatedClientCredentials.

                           SPChannelFactoryOperations.ConfigureCredentials<AzureClaims.IAzureClaims>

                                   (cust.ChannelFactory,

                                   Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

                           //crear un canal al extremo WCF mediante

                           //el token y las notificaciones del usuario actual

                           AzureClaims.IAzureClaims claimsWCF =

                                  SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

                                  <AzureClaims.IAzureClaims>(cust.ChannelFactory,

                                  this.WcfEndpointAddress,

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

 

                           //establecer la propiedad de cliente para la clase base

                           this.WcfClientProxy = claimsWCF;

                     }

                     catch (Exception ex)

                     {

                           Debug.WriteLine(ex.Message);

                     }

 

                     //ahora que la configuración se ha completado, llamar al método

                     return base.ExecuteRequest();

              }

       }

 

“AzureClaims” es el nombre de la referencia de servicio que creé y usa la interfaz IAzureClaims que definí en mi proyecto WCF en Azure. Como expliqué anteriormente en la serie sobre CASI Kit, este código sirve básicamente como base; solo agregué el nombre de mi interfaz y clase que están expuestas en la aplicación WCF. Otra cosa que he hecho, también explicado en la serie sobre CASI Kit, es crear una página ASPX llamada AzureClaimProvider.aspx. Simplemente copié y pegué el código que describo en la parte 3 de la serie sobre CASI Kit y sustituí el nombre de mi clase y el extremo en el que se puede alcanzar. La etiqueta de control de la página ASPX para mi componente de CASI Kit tiene el aspecto siguiente:

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

 

Lo importante que hay que observar aquí es que creé un registro CNAME para “spsazure.vbtoys.com” que apunta a mi aplicación Azure en cloudapp.net (esto también se describe en la parte 3 del CASI Kit). Establecí en GetClaimTypes el valor MethodName predeterminado que la página usará para invocar, que es un método que no lleva ningún parámetro y devuelve una lista de tipos de notificación compatible con mi proveedor de notificaciones de Azure. Esto lo convierte en una buena prueba para validar la conectividad entre mi aplicación de Azure y SharePoint. Simplemente puedo ir a https://cualquierSitioSharePoint/_layouts/AzureClaimProvider.aspx y si todo está configurado correctamente, veré datos en la página. Cuando haya implementado mi proyecto mediante la adición del ensamblado a la memoria caché global de ensamblados y la implementación de la página en el directorio SharePoint’s _layouts. Esto es exactamente lo que hice, coloqué la página en uno de mis sitios y verifiqué que devolvió datos, por lo que sabía que la conexión entre SharePoint y Azure funcionaba.

Ahora que he establecido los elementos base, finalmente llego a la parte “divertida” del proyecto, que es hacer dos cosas:

  1. 1.       Crear “algún componente” que enviará información acerca de los nuevos usuarios a mi cola de Azure.
  2. 2.       Crear un proveedor de notificaciones personalizado que usará mi componente de CASI Kit personalizado para proporcionar tipos de notificación, resolución de nombres y búsquedas de notificaciones.

Este es un buen momento para parar y apartarse un poco. En este caso concreto, simplemente quería implementar algo lo más rápido posible. Entonces, creé una nueva aplicación web y habilité el acceso anónimo. Como seguramente sabe, la simple habilitación en el nivel de la aplicación web NO lo habilita en el nivel de la colección de sitios. Entonces, para este escenario, también lo habilité solo en la colección de sitios raíz. Concedí acceso a todos los elementos del sitio. Todas las demás colecciones de sitios, que contendría información solo para miembros, NO tiene habilitado el acceso anónimo, por lo que los usuarios deben recibir derechos para unirse.

La siguiente cosa que hay que tener en cuenta es cómo administrar los identificadores que usarán el sitio. Evidentemente, no es algo que quiera hacer. Tendría que encontrar varios métodos diferentes para sincronizar las cuentas en Azure o algo ridículo parecido, pero como expliqué en la parte 1 de esta serie, existen varios proveedores que ya lo hace, por lo que dejaré que hagan su labor. Lo que quiero decir es que saqué partido de otro servicio en la nube de Microsoft, llamado ACS o Servicio de control de acceso. En pocas palabras, ACS actúa como un proveedor de identidades para SharePoint. Por tanto, simplemente creé una relación de confianza entre mi granja de servidores de SharePoint y la instancia de ACS que creé para este POC. En ACS, agregué SharePoint como entidad de confianza, por lo que ACS sabe a dónde enviar los usuarios cuando estos se hayan autenticado. También configuré ACS para permitir a los usuarios iniciar sesión mediante sus cuentas de Gmail, Yahoo o Facebook. Una vez que hayan iniciado sesión, ACS obtiene una única notificación que usaré, la dirección de correo electrónico, y la reenvía a SharePoint.

Bueno, eso es toda la información sobre los elementos base, Azure proporciona almacenamiento de tablas y colas para trabajar con los datos, ACS proporciona los servicios de autenticación y CASI Kit proporciona los elementos base para los datos.

Entonces, con todos elementos base descritos, ¿cómo se usarán? Aún quería que el proceso para convertirse en miembro fuera sencillo, por lo que escribí un elemento web para agregar usuarios a mi cola de Azure. Este comprueba para ver si la solicitud está autenticada (es decir, el usuario ha hecho clic en el vínculo Iniciar sesión para entrar en un sitio anónimo, ha iniciado sesión en uno de los proveedores que mencioné anteriormente y ACS me ha devuelto su información de notificación). Si la solicitud no está autenticada, el elemento web no hace nada. Sin embargo, si la solicitud sí está autenticada, crea un botón que, al hacer clic en él, tomará la notificación de correo electrónico del usuario y la agregará a la cola de Azure. Esta es la parte a la que me refería cuando dije que había que apartarse un poco y reflexionar. Para un POC, esto está bien, funciona. Sin embargo, puede pensar en otras maneras en las que podría procesar esta solicitud. Por ejemplo, quizás escribir la información en una lista de SharePoint. Podría escribir un trabajo de temporizador personalizado (con esto CASI Kit funciona muy bien) y procesar periódicamente las solicitudes nuevas a partir de esa lista. Podría usar SPWorkItem para poner en cola las solicitudes que se procesarán más adelante. Podría almacenarla en una lista y agregar un flujo de trabajo personalizado que quizás deba pasar por un proceso de aprobación y, una vez que la solicitud se haya aprobado, usa una acción de flujo de trabajo personalizado para invocar al CASI Kit e insertar los detalles en la cola de Azure. En pocas palabras, hay MUCHA potencia, flexibilidad y personalización posibles aquí, todo queda en manos de su imaginación. De hecho, en algún momento es posible que crea otra versión de esto que lo escribirá en una lista personalizada, la procesará de manera asíncrona en algún punto, agregará los datos a la cola de Azure y luego agregará automáticamente la cuenta al grupo Visitantes de uno de los subsitios, de modo que el usuario estaría inscrito y listo para comenzar inmediatamente. Pero esto sería tema de otro artículo en el caso que decida hacerlo.

Entonces, dicho esto, tal como describió anteriormente, simplemente dejo que el usuario haga clic en un botón si han iniciado sesión y luego usaré mi componente CASI Kit personalizado para llamar al extremo WCF y agregar la información a la cola de Azure. Este es el código del elemento web; es bastante sencillo y agradezco a CASI Kit:

public class AddToAzureWP : WebPart

{

 

       //botón cuyo evento de clic se debe controlar para poder

       //agregar el usuario a 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

              {

                     //buscar la identidad de notificación

                     IClaimsPrincipal cp = Page.User as IClaimsPrincipal;

 

                     if (cp != null)

                     {

                           //obtener la identidad de notificaciones para poder enumerar las notificaciones

                           IClaimsIdentity ci = (IClaimsIdentity)cp.Identity;

 

                           //buscar la notificación de correo electrónico

                           //comprobar si hay notificaciones presentes antes de ejecutar esto

                           if (ci.Claims.Count > 0)

                           {

                                  //buscar la notificación de dirección de correo electrónico

                                  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)

                                  {

                                         //crear la cadena que se enviará a la cola Azure:  notificación, valor y nombre para mostrar

                                         //observe que uso "#" como delimitadores porque hay un solo parámetro y CASI Kit

                                         //usa ; como delimitador, por lo que se necesita otro valor.  Si se usara ; CASI intentaría

                                         //convertirlo en tres parámetros, cuando en realidad solo es uno

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

 

                                         //crear la conexión a Azure y cargar

                                         //crear una instancia del control

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

 

                                         //establecer las propiedades para recuperar datos; se deben configurar las propiedades de caché porque se usará mediante programación

                                         //sin embargo, en este caso no se usa en realidad

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

 

                                         //ejecutar el método

                                         bool success = cfgCtrl.ExecuteRequest();

 

                                         if (success)

                                         {

                                                //si funcionó, avisar al usuario

                                                statusLbl.Text = "<p>Su información se agregó correctamente.  Puede ponerse en contacto con cualquiera de" +

                                                       "los demás miembros del partner o nuestro personal de soporte técnico para obtener derechos de acceso al contenido" +

                                                       "del Partner. Tenga en cuenta que procesamiento de su solicitud puede tardar hasta " +

                                                       "15 minutos.</p>";

                                         }

                                         else

                                         {

                                                statusLbl.Text = "<p>Se produjo un problema al agregar su información en Azure; vuelva a intentarlo más tarde o " +

                                                       "póngase en contacto con el soporte técnico si el problema persiste.</p>";

                                         }

                                  }

                           }

                     }

              }

              catch (Exception ex)

              {

                     statusLbl.Text = "Se produjo un problema al agregar su información en Azure; vuelva a intentarlo más tarde o " +

                           "póngase en contacto con el soporte técnico si el problema persiste.";

                     Debug.WriteLine(ex.Message);

              }

       }

}

 

Un resumen breve del código tiene el siguiente aspecto: en primer lugar, me aseguro de que la solicitud esté autenticada, si lo está, agrego el botón a la página y un controlador de eventos para el evento clic del botón. En el controlador de eventos clic del botón, obtengo una referencia IClaimsPrincipal al usuario actual y consulto la colección de notificaciones del usuario. Ejecuto una consulta LINQ en la colección de notificaciones para buscar la notificación de correo electrónico, que es la notificación de identidad para el parámetro SPTrustedIdentityTokenIssuer. Si encuentro la notificación de correo electrónico, creo una cadena concatenada con el tipo de notificación, el valor de la notificación y el nombre descriptivo de la misma. Nuevamente, esto no es estrictamente necesario en este escenario, pero dado que quería poder usarlo en un escenario más genérico, usé código de esta forma. Esta cadena concatenada es el valor del método en WCF que agrega datos a la cola de Azure. Luego, creo una instancia de mi componente CASI Kit personalizado y lo configuro para llamar al método WCF que agrega datos a la cola. A continuación, llamo al método ExecuteRequest para insertar los datos.

Si obtengo una respuesta que indica que estaba agregando datos correctamente a la cola, se lo notifico al usuario. De lo contrario, le indico que se produjo un problema y necesitará volver a comprobarlo más adelante. Por supuesto, en un escenario real, tendría aún más registros de errores para poder realizar un seguimiento de lo que sucedió exactamente y por qué. Sin embargo, tal como está el CASI Kit grabará toda información de error en los registros ULS en un parámetro SPMonitoredScope, de modo que todo lo que haga para la solicitud tendrá un identificador de correlación único con el que se podrá ver toda la actividad asociada con la solicitud. Por tanto, ahora mismo estoy en muy buen estado.

Bueno, hemos hablado de los elementos base y he mostrado cómo los datos se agregan a la cola de Azure y de dónde el proceso de trabajo los obtiene para agregarlos al almacenamiento de tablas. Ese es el objetivo final porque ahora pasaremos al proveedor de notificaciones personalizado. Este usará el CASI Kit para llamar y consultar el almacenamiento de tablas de Azure que estoy usando. Echemos un vistazo a los aspectos más interesantes del proveedor de notificaciones personalizado.

Miremos primero a un par de atributos de nivel de clase:

//el extremo WCF que usaremos para conectarse para las funciones de libreta de direcciones

//dirección URL de prueba:  https://az1.vbtoys.com/AzureClaimsWCF/AzureClaims.svc

//dirección URL de producción:  https://spsazure.vbtoys.com/AzureClaims.svc

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

 

//el valor de tipo de notificación de identidades

private const string IDENTITY_CLAIM =

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

 

//la colección de tipos de notificaciones que admitimos; no cambiará a lo largo de la vida de

//STS (w3wp.exe), por lo que se almacenará en caché una vez que se obtiene

AzureClaimProvider.AzureClaims.ClaimTypeCollection AzureClaimTypes =

       new AzureClaimProvider.AzureClaims.ClaimTypeCollection();

 

En primer lugar, uso una constante para hacer referencia al extremo WCF al que debe conectarse el CASI Kit. Observe que tengo un extremo de prueba y un extremo de producción. Cuando se usa el CASI Kit mediante programación, como lo haremos en nuestro proveedor de notificaciones personalizado, siempre le indica dónde está el extremo WCF con el que debe comunicarse.

A continuación, tal como describió anteriormente, uso la notificación de correo electrónico como notificación de identidades. Dado que haré referencia a ella varias veces en el proveedor, simplemente inserté una constante en el nivel de la clase.

Por último, tengo una colección de AzureClaimTypes. Expliqué en la parte 2 de esta serie por qué uso una colección y simplemente la estoy almacenando aquí en el nivel de la clase para no tener que volver a recuperar esa información cada vez que se invoque el método FillHierarchy. Las llamadas a Azure tienen su precio, por lo que las minimizo lo más posible cuando puedo.

Este es el siguiente fragmento de código:

internal static string ProviderDisplayName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

internal static string ProviderInternalName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

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

//USAR ESTA PROPIEDAD AHORA AL CREAR LA NOTIFICACIÓN PARA EL VALOR PICKERENTITY

internal static string SPTrustedIdentityTokenIssuerName

{

       get

       {

              return "SPS ACS";

       }

}

 

 

public override string Name

{

       get

       {

              return ProviderInternalName;

       }

}

 

La razón por la cual quería señalar este código es porque dado que mi proveedor emite notificaciones de identidades, DEBE ser el proveedor predeterminado para el parámetro SPTrustedIdentityTokenIssuer. Explicar cómo hacer eso está fuera del ámbito de este artículo, pero lo he cubierto en otras ocasiones en mi blog. Lo que hay que recordar cuando se hace esto es que debe tener una relación sólida entre el nombre que usa para el proveedor y el nombre que usa para el parámetro SPTrustedIdentityTokenIssuer. El valor que usé para el parámetro ProviderInternalName es el nombre que tengo que insertar en la propiedad ClaimProviderName del parámetro SPTrustedIdentityTokenIssuer. Además, necesito usar el nombre del parámetro SPTrustedIdentityTokenIssuer al crear las notificaciones de identidad para los usuarios. Por tanto, he creado un parámetro SPTrustedIdentityTokenIssuer denominado “SPS ACS” y lo he agregado a la propiedad SPTrustedIdentityTokenIssuerName. Por eso tengo estos valores de código aquí.

Dado que no haré ningún aumento de notificaciones en este proveedor, no escribí ningún código para invalidar FillClaimTypes, FillClaimValueTypes o FillEntityTypes. El siguiente fragmento de código es FillHierarchy, que es donde le indico a SharePoint qué tipos de notificaciones admito. El código para eso es el siguiente:

try

{

       if (

               (AzureClaimTypes.ClaimTypes == null) ||

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

       )

       {

              //crear una instancia del control

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

 

              //establecer las propiedades para recuperar datos; se deben configurar las propiedades de caché porque se usará mediante programación

              //sin embargo, la memoria caché no se usa realmente en este caso

              cfgCtrl.WcfUrl = SVC_URI;

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

              cfgCtrl.MethodName = "GetClaimTypes";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = "GetClaimTypes";

              cfgCtrl.SharePointClaimsSiteUrl = context.AbsoluteUri;

 

              //ejecutar el método

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //si funcionó, obtener la lista de tipos de notificaciones

                     AzureClaimTypes =

                                                (AzureClaimProvider.AzureClaims.ClaimTypeCollection)cfgCtrl.QueryResultsObject;

              }

       }

 

       //asegurarse de que el selector solicita el tipo de entidad que devolvemos; por ejemplo, el administrador de la colección de sitios no lo hará

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

              return;

 

       //llegado a este punto, tendremos todos los tipos de notificación posibles, por lo que deben agregarse a la jerarquía

       //comprobar si el parámetro hierarchyNodeID es nulo; lo será cuando el control

       //se cargue originalmente, pero si un usuario hace clic en uno de los nodos, devolverá

       //la clave del nodo en el que se hizo clic. Esto le permite crear una

       //jerarquía a medida que un usuario hace clic en algo, en lugar de hacerlo todo de una vez

       if (

               (string.IsNullOrEmpty(hierarchyNodeID)) &&

               (AzureClaimTypes.ClaimTypes.Count() > 0)

              )

       {

              //enumerar por cada tipo de notificación

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

              {

                     //cuando se carga por primera vez, agregar todos los nodos

                     hierarchy.AddChild(new

                                                Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(

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

              }

       }

}

catch (Exception ex)

{

       Debug.WriteLine("Error filling hierarchy: " + ex.Message);

}

 

Aquí compruebo para ver si ha obtenido la lista de tipos de notificaciones que admito. En caso negativo, creo una instancia de mi control personalizado de CASI Kit y creo una llamada a WCF para recuperar los tipos de notificación. Para ello, llamo al método GetClaimTypes en mi clase WCF. Si objeto datos, los inserto en la variable de nivel de clase que describí anteriormente llamada AzureClaimTypes y luego los agrego a la jerarquía de tipos de notificaciones que admito.

Los siguientes métodos que vamos a ver son los métodos FillResolve. Los métodos FillResolve tienen dos firmas diferentes porque hacen dos cosas diferentes. En un escenario, tenemos una notificación específica con un valor y tipo, y SharePoint simplemente debe comprobar que es válida. En el segundo caso, simplemente inserté un valor en el tipo en control de SharePoint, que es básicamente lo mismo que hacer una búsqueda de notificaciones. Por ello, los miraremos por separado.

En el caso donde especifiqué una notificación y SharePoint desea comprobar los valores, llamo a un método personalizado que creé llamado GetResolveResults. En él, paso el URI en el que se realiza la solicitud, así como el tipo y el valor de notificación que SharePoint desea validar. El parámetro GetResolveResults entonces tiene el aspecto siguiente:

//Tener en cuenta que el parámetro claimType que se pasa aquí es para una ampliación futura; sin embargo,

//en el caso actual, solo se usan notificaciones de identidad

private AzureClaimProvider.AzureClaims.UniqueClaimValue GetResolveResults(string siteUrl,

       string searchPattern, string claimType)

{

       AzureClaimProvider.AzureClaims.UniqueClaimValue result = null;

 

       try

       {

              //crear una instancia del control

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

 

              //establecer las propiedades para recuperar datos; se deben configurar las propiedades de caché porque se usará mediante programación

              //sin embargo, la memoria caché no se usa realmente en este caso

              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;

 

              //ejecutar el método

              bool success = cfgCtrl.ExecuteRequest();

 

              //si la consulta no encontró errores, capturar el resultado

              if (success)

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

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

 

       return result;

}

 

Aquí estoy creando una instancia del control CASI Kit personalizado y llamando al método ResolveClaim en mi WCF. Este método acepta dos parámetros, por lo que lo paso en valores delimitados por punto y comas (porque es el método que CASI Kit usa para distinguir entre valores de parámetro diferentes). A continuación, simplemente ejecuto la solicitud y, si encuentra una coincidencia, devolverá un único valor UniqueClaimValue. De lo contrario, el valor devuelto será nulo. En el método FillResolve, el código tiene el aspecto siguiente:

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

{

       //asegurarse de que el selector solicita el tipo de entidad que devolvemos; por ejemplo, el administrador de la colección de sitios no lo hará

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

              return;

 

       try

       {

              //buscar notificaciones coincidentes

              AzureClaimProvider.AzureClaims.UniqueClaimValue result =

                     GetResolveResults(context.AbsoluteUri, resolveInput.Value,

                     resolveInput.ClaimType);

 

              //si se encuentra una coincidencia, agregarla a la lista resuelta

              if (result != null)

              {

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

                     SPClaimEntityTypes.User, result.DisplayName);

                           resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

Primero compruebo que la solicitud es para una notificación de usuario, ya que esta es el único tipo de notificación que devuelve el proveedor. Si la solicitud no es para una notificación de usuario, abandono. A continuación, llamo al método para resolver la notificación y, si obtengo un resultado no nulo, lo proceso. Para ello, llamo a otro método personalizado que creé denominado GetPickerEntity. Aquí, paso el tipo y el valor de notificación para crear una notificación de identidad y luego lo agrego al parámetro PickerEntity que devuelve a las instancias de lista de PickerEntity pasado en el método. No trataré el método GetPickerEntity aquí porque este artículo ya es bastante largo y lo he cubierto en otros artículos de mi blog.

Veremos ahora el otro método FillResolve. Tal como describí anteriormente, este método básicamente actúa como una búsqueda, por lo que combinaré los métodos FillResolve y FillSearch aquí. Ambos métodos llamarán a un método personalizado que escribí denominado SearchClaims y que tiene el aspecto siguiente:

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

       string siteUrl)

{

                    

       AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

              new AzureClaimProvider.AzureClaims.UniqueClaimValueCollection();

 

       try

       {

              //crear una instancia del control

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

 

              //establecer las propiedades para recuperar datos; se deben configurar las propiedades de caché porque se usará mediante programación

              //sin embargo, la memoria caché no se usa realmente en este caso

              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;

 

              //ejecutar el método

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //si funcionó, obtener la matriz de resultados

                     results =

 (AzureClaimProvider.AzureClaims.UniqueClaimValueCollection)cfgCtrl.QueryResultsObject;

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine("Error searching claims: " + ex.Message);

       }

 

       return results;

}

 

En este método, tal como ha visto en este artículo, simplemente creo una instancia de mi control CASI Kit personalizado. Llamo al método SearchClaims en mi WCF y paso el tipo de notificación en la que quiero realizar la búsqueda, el valor de notificación que quiero encontrar en ese tipo de notificación y el número máximo de registros que se deben devolver. Quizás recuerdo de la parte 2 de esta serie que SearchClaims simplemente hace una operación BeginsWith en el patrón de búsqueda en el que se pasa, por lo que con muchos usuarios, podría haber fácilmente más de 200 resultados. No obstante, 200 es el número máximo de coincidencias que mostrará el selector de personas, por lo que ese es el número que solicitaré. Si realmente cree que los usuarios se desplazarán por más de 200 resultados, estoy aquí para decirle que es muy poco probable.

Bueno, ahora tenemos nuestra colección de UniqueClaimValues; miremos cómo usar los dos métodos de invalidación en el proveedor de notificaciones personalizado. En primer lugar, el método FillResolve tiene el aspecto siguiente:

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

{

       //esta versión de la resolución es igual que una búsqueda, por lo que la trataremos como tal

       //asegurarse de que el selector solicita el tipo de entidad que devolvemos; por ejemplo, el administrador de la colección de sitios no lo hará

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

              return;

 

       try

       {

              //realizar la búsqueda para obtener coincidencias

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, resolveInput, context.AbsoluteUri);

 

              //consultar cada coincidencia y agregar una entidad de selector para cada una de ellas

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

       }

}

Simplemente llama al método SearchClaims y para cada resultado que obtiene (si los hay), creará un nuevo valor PickerEntity que agregará a la lista de valores pasados en la invalidación. Todos ellos aparecerán en el tipo en control de SharePoint. El método FillSearch tiene el aspecto siguiente:

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

{

       //asegurarse de que el selector solicita el tipo de entidad que devolvemos; por ejemplo, el administrador de la colección de sitios no lo hará

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

              return;

 

       try

       {

              //realizar la búsqueda para obtener coincidencias

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, searchPattern, context.AbsoluteUri);

 

              //si hay más de cero resultados, agregarlos al selector

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

              {

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

                     {

                           //nodo en el que se colocarán las coincidencias

                           Microsoft.SharePoint.WebControls.SPProviderHierarchyNode matchNode = null;

 

                           //obtener una entidad selectora para agregar al cuadro de diálogo

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

 

                           //agregar el nodo en el que también se debe mostrar

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

                           {

                                  //crear el nodo para poder mostrar la coincidencia también allí

                                  matchNode = new

                                         SPProviderHierarchyNode(ProviderInternalName,

                                         cv.DisplayName, cv.ClaimType, true);

 

                                  //agregarlo al árbol

                                  searchTree.AddChild(matchNode);

                           }

                           else

                                  //obtener el nodo para este equipo

                                  matchNode = searchTree.Children.Where(theNode =>

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

 

                           //agregar la coincidencia al nodo

                           matchNode.AddEntity(pe);

                     }

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

En el método FillSearch, llamo al método SearchClaims nuevamente. Para cada valor UniqueClaimValue que obtengo (si los hay), consulto para ver si he agregado el tipo de notificación al nodo de jerarquía de resultados. Nuevamente, en este caso, siempre devolveré solo un tipo de notificación (correo electrónico), pero lo escribí para que sea ampliable. De este modo, podrá usar más tipos de notificación más adelante. Entonces, agrego el nodo de jerarquía si este no existe o lo busco si sí existe. Uso el parámetro PickerEntity que creé a partir del valor UniqueClaimValue y lo agrego al nodo de jerarquía. Y eso es todo.

No trataré el método FillSchema ni ninguna de las cuatro invalidaciones de la propiedad Boolean que debe tener todo proveedor de notificaciones personalizado, ya que no tienen nada de especial para este escenario y he cubierto los elementos básicos relacionados en otros artículos de este blog. Tampoco trataré el receptor de características que se usa para registrar este proveedor de notificaciones personalizado porque, nuevamente, no tiene nada de especial para este proyecto y lo he tratado en otro artículo, Después de realizar la compilación, simplemente debe asegurarse de que el ensamblado del proveedor de notificaciones personalizado, así como el componente CASI Kit personalizado, estén registrados en la memoria caché global de ensamblados de cada granja de servidores y deberá configurar el parámetro SPTrustedIdentityTokenIssuer para que use el proveedor de notificaciones personalizado como el proveedor predeterminado (que también se explica en otro artículo de este blog).

Este es el escenario básico de principio a fin. Cuando esté en el sitio de SharePoint e intente agregar un usuario nuevo (en realidad una notificación de correo electrónico), el proveedor de notificaciones personalizado se invoca primero para obtener una lista de tipos de notificaciones admitidos y luego otra vez al especificar un valor en el tipo en control o al buscar un valor mediante el selector de personas. Si el proveedor de notificaciones personalizado usa el control CASI Kit personalizado para realizar una llamada autenticada a Windows Azure para comunicarse con el WCF, que usa nuestras clases de datos personalizadas para recuperar datos del almacenamiento de tablas de Azure. Devuelve los resultados y los presentamos al usuario. Con eso, cuenta con su solución completa SharePoint y Azure “extranet predefinida” que puede usar tal como está o modificar para adaptarla a sus necesidades. El código de origen para el componente CASI Kit personalizado, el elemento web que registra el usuario en una cola Azure y el proveedor de notificaciones personalizado están adjuntos a este artículo. Espero que disfrute de ellos y que les sea de utilidad. También espero que pueda comenzar a ver cómo puede relacionar todos estos servicios separados para crear soluciones que se adapten a sus problemas. A continuación incluyo unas capturas de pantalla de la solución final:

Sitio raíz como usuario anónimo:

Este es su aspecto después de la autenticación; observe que el elemento web ahora muestra el botón Request Membership:

Este es un ejemplo del selector de personas en acción, después de buscar valores de notificación que comienzan con “sp”:

 

Esta entrada de blog es una traducción. Puede consultar el artículo original en The Azure Custom Claim Provider for SharePoint Project Part 3