SharePoint 2010: ¿Se pueden activar features con el modelo de objetos en cliente? (II)

Esta pregunta me hacía hace unos meses, sobre todo tratando de ver como automatizar el despliegue y activación de soluciones Sandbox tanto para SharePoint 2010 On-Premise como para SharePoint Online a través del modelo de objetos en cliente. El caso es que esta semana, a partir de un requerimiento de un proyecto, le he tenido que meter “mano” en serio a este tema y he llegado a una solución más o menos satisfactoria en cuanto a que funciona, pero que no me parece nada óptima…pero salvo que me equivoque, de momento no hay mucho que rascar a falta de clases que permitan activar soluciones Sandbox en el modelo de objetos en cliente (CSOM). Bueno, vamos a meternos en harina:

  • Lo primero que he hecho es crear un proyecto de tipo Windows Forms (por ejemplo, lo que implica que vamos a trabajar con el CSOM .NET), con un formulario inicial en el que el usuario especifique la url de la colección de sitios con la que se va a trabajar. En este ejemplo particular, se va a tratar además de una colección de sitios de SharePoint Online por lo que el formulario, que consta de un simple botón y una caja de texto, tiene el siguiente código:
    • Cómo veis, desde el formulario inicial llamamos a otro, pero en otro hilo creado a tal efecto.
    • La razón de crear un nuevo hilo es debida al mecanismo de autenticación contra Office 365 que implica iniciar una sesión en Office 365 a través de un control WebBrowser…y el caso es que este control WebBrowser no se puede abrir en el mismo hilo si ya se ha abierto otro formulario.
   1: private void btnGo_Click(object sender, EventArgs e)

   2: {

   3:     sUrl = txtOffice365Site.Text;

   4:     this.Close();

   5:     this.Dispose();

   6:     //Application.Run(new frmActivarSBSPO(sUrl));

   7:     Thread thrThread = 

   8:         new Thread(new ThreadStart(OpenActivateSolutionForm));

   9:     thrThread.Name = "MainForm";

  10:     thrThread.SetApartmentState(ApartmentState.STA);

  11:     thrThread.Start();

  12: }

  13:  

  14: void OpenActivateSolutionForm()

  15: {

  16:     frmActivarSBSPO frmActivarSolution =

  17:         new frmActivarSBSPO(sUrl);

  18:     Application.Run(frmActivarSolution);            

  19: }

  • En un segundo formulario, lo qué haremos una vez iniciada sesión en Office 365 es activar una solución Sandbox en concreto. Añadimos en primer lugar una referencia al CSOM de .NET (Microsoft.SharePoint.Client y Microsoft.SharePoint.Client.Runtime). Añadimos las directivas necesarias en el segundo formulario:
   1: //Espacios de nombres necesarios

   2: using MO_NET = Microsoft.SharePoint.Client;

   3: using System.Net;

   4: using MSDN.Samples.ClaimsAuth;

   5: using System.IO;

  • Añadimos los miembros de la clase formulario y codificamos el constructor. Como veis, es aquí dónde iniciamos la autenticación usando para ello el ejemplo de Robert Bogue disponible aquí y que por supuesto tendremos que referenciar en nuestro proyecto. Como veis, generamos el contexto a partir de un ClaimClientContext,
   1: MO_NET.ClientContext ctx;

   2: DataTable dtDatos;

   3: WebBrowser wBrowser;

   4:  

   5: /// <summary>

   6: /// Form constructor

   7: /// </summary>

   8: /// <param name="sTargetSite"></param>

   9: public frmActivarSBSPO(string sTargetSite)

  10: {

  11:     InitializeComponent();           

  12:     

  13:     //Accesing to a Claim Client Context

  14:     using (ctx = ClaimClientContext.GetAuthenticatedContext(sTargetSite))

  15:     {

  16:         if (ctx != null)

  17:         {

  18:             MO_NET.Web wSite = ctx.Web;

  19:             ctx.Load(wSite);

  20:             ctx.ExecuteQuery();                    

  21:         }

  22:     }

  23: }

  • A continuación, en el código del botón que usaremos para activar la solución es dónde comienza “la chicha” de la activación de la solución. La única forma que he visto para activar una solución Sanbox pasa por simular una navegación a la solución en concreto y simular un clic de ratón de forma que la solución se active. Para simular la navegación, usamos un objeto de tipo WebBrowser y el método Navigate() en el que especificamos la Url de activación de la solución. A continuación, para dar tiempo a cargar esa página tenemos que indicar el manejador para DocumentCompleted que luego codificaremos. Como veis, además nos aseguramos de suprimir cualquier posible error de JavaScript que pudiese darse para evitar que se le muestre al usuario.
   1: private void btnInstalar_Click(object sender, EventArgs e)

   2: {

   3:     try

   4:     { 

   5:         string targetSolutionUrl = ctx.Url +

   6:             "/_catalogs/solutions/Forms/Activate.aspx?Op=ACT&ID=5&IsDlg=0";

   7:         //WebBrowser object allows to simulate a navigation

   8:         wBrowser = new WebBrowser();

   9:         wBrowser.Navigate(new Uri(targetSolutionUrl));

  10:         wBrowser.DocumentCompleted +=

  11:             new WebBrowserDocumentCompletedEventHandler(wBrowser_DocumentCompleted);

  12:         //Supressing any JavaScript error

  13:         wBrowser.ScriptErrorsSuppressed = true;

  14:         MessageBox.Show("¡Solución SandBox Activada!");       

  15:     }

  16:     catch (Exception ex)

  17:     {

  18:         MessageBox.Show(ex.Message);

  19:     }

  20: }

  • A continuación, codificamos el manejador para DocumentCompleted dónde nos aseguraremos por una parte de localizar el botón “Activate” relativo a una solución Sandbox y luego forzaremos el correspondiente clic. Cómo veis, todo pasa por definir un objeto Document a partir del objeto WebBrowser y luego un objeto ElementCollection que contendrá la colección de hipervínculos de la página. Localizamos el botón de activación y simulamos el clic en el mismo…lamentablemente, con hacer una simulación y debido al comportamiento del objeto WebBrowser no es suficiente por lo que tenemos que asegurarnos que la solución se ha activado y esto es lo que hacemos con la llamada a ActivateSolution. Una vez que la solución se ha activado, simplemente mostramos en un control de tipo DataGridView las soluciones de la galería de soluciones a través del método ReedSolutions().
   1: private void wBrowser_DocumentCompleted(object sender, 

   2:         WebBrowserDocumentCompletedEventArgs e)

   3: {

   4:     dtDatos = new DataTable();

   5:     dtDatos.Columns.Add("A Tag");

   6:     string sButton;

   7:     HtmlDocument hDoc = ((WebBrowser)sender).Document;

   8:     //Collection of hyperlinks elements in the page

   9:     HtmlElementCollection aTag = hDoc.Links;

  10:     foreach (HtmlElement he in aTag)

  11:     {

  12:         DataRow drow = dtDatos.NewRow();

  13:         drow["A Tag"] = he.Id;

  14:         dtDatos.Rows.Add(drow);

  15:     } 

  16:  

  17:     //This block allows to simulate a click in the Activate button for a Sandbox solution

  18:     if (dtDatos != null)

  19:     {

  20:         bool bActivarSolucion; ;

  21:         foreach (DataRow dtr in dtDatos.Rows)

  22:         {

  23:             bActivarSolucion = false;

  24:             sButton = dtr["A Tag"].ToString();

  25:             //Looking for the activate button

  26:             if (sButton.Contains("Activate"))

  27:             {

  28:                 //Simulating the click in the button

  29:                 wBrowser.Document.GetElementById(sButton).InvokeMember("click");

  30:                 iCounter = iCounter + 1;

  31:                 bActivarSolucion = ActivateSolution(ctx);

  32:             }

  33:             if (bActivarSolucion)

  34:             {

  35:                 dataGridView1.DataSource = 

  36:                     ReedSolutions(ctx);

  37:                 break;

  38:             }

  39:         }

  40:     }            

  41: }

  • El método ActivateSolution() simplemente comprueba que la solución en cuestión ha sido activada en cuyo caso devuelve True o no de manera que el clic de activación sea forzado de nuevo:
   1: public static bool ActivateSolution(MO_NET.ClientContext ctx)

   2:    {

   3:        bool bActivarSolucion = false;

   4:        try

   5:        {

   6:            using (MO_NET.ClientContext ctxAux = ctx)

   7:            {

   8:                if (ctx != null)

   9:                {

  10:                    //Accessing to the solutions gallery

  11:                    MO_NET.List solutionList = ctx.Site.GetCatalog(121);

  12:  

  13:                    //Accessing to the solutions collection

  14:                    MO_NET.ListItemCollection licCollection =

  15:                        solutionList.GetItems(MO_NET.CamlQuery.CreateAllItemsQuery());

  16:  

  17:                    //Defining the operation

  18:                    ctx.Load(licCollection);

  19:  

  20:                    //Performing the operation

  21:                    ctx.ExecuteQuery();

  22:  

  23:                    //Processing results

  24:                    foreach (MO_NET.ListItem li in licCollection)

  25:                    {

  26:                        MO_NET.FieldLookupValue fl = (MO_NET.FieldLookupValue)li.FieldValues["Status"];

  27:                        string sSolutionPath = li["FileRef"].ToString();

  28:                        //Checking if the solution is active

  29:                        if (fl != null && sSolutionPath.Contains("SPOWP"))

  30:                        {

  31:                            bActivarSolucion = true;

  32:                            break;                                   

  33:                            

  34:                        }

  35:                        else

  36:                        {

  37:                            if (sSolutionPath.Contains("SPOWP"))

  38:                            {                                    

  39:                                break;

  40:                            }

  41:                            

  42:                        }

  43:                    }

  44:                }

  45:            }

  46:        }

  47:        catch (Exception ex)

  48:        {

  49:            MessageBox.Show("Error: {0}", ex.Message);

  50:        }

  51:  

  52:        return bActivarSolucion;

  53:    }

  • Finalmente, el método ReedSolucions() simplemente me devuelve información relativa al listado de soluciones disponible en la galería de soluciones:
   1: public static DataTable ReedSolutions(MO_NET.ClientContext ctx)

   2: {

   3:     DataTable dtSoluciones = new DataTable();

   4:     dtSoluciones.Columns.Add("ID");

   5:     dtSoluciones.Columns.Add("Solución");

   6:     dtSoluciones.Columns.Add("Estado Activación");

   7:     DataRow dtrSoluciones;

   8:     

   9:     try

  10:     {

  11:         using (MO_NET.ClientContext ctxAux = ctx)

  12:         {

  13:             if (ctx != null)

  14:             {

  15:                 //Accessing to the solutions gallery

  16:                 MO_NET.List solutionList = ctx.Site.GetCatalog(121);

  17:  

  18:                 //Accessing to the solutions collection

  19:                 MO_NET.ListItemCollection licCollection =

  20:                     solutionList.GetItems(MO_NET.CamlQuery.CreateAllItemsQuery());

  21:  

  22:                 //Defining the operation

  23:                 ctx.Load(licCollection);

  24:  

  25:                 //Performing the operation

  26:                 ctx.ExecuteQuery();

  27:  

  28:                 //Processing the results

  29:                 foreach (MO_NET.ListItem li in licCollection)

  30:                 {

  31:                     MO_NET.FieldLookupValue fl = (MO_NET.FieldLookupValue)li.FieldValues["Status"];

  32:                     dtrSoluciones = dtSoluciones.NewRow();

  33:                     dtrSoluciones["ID"] = li["ID"];

  34:                     dtrSoluciones["Solución"] = li["FileRef"];

  35:                     //Solución activada o no

  36:                     if (fl != null)

  37:                     {

  38:                         dtrSoluciones["Estado Activación"] = "Sí";

  39:                     }

  40:                     else

  41:                     {

  42:                         dtrSoluciones["Estado Activación"] = "No";

  43:                     }

  44:                     dtSoluciones.Rows.Add(dtrSoluciones);

  45:                 }

  46:             }

  47:         }

  48:     }

  49:     catch (Exception ex)

  50:     {

  51:         MessageBox.Show(ex.Message);

  52:     }

  53:     return dtSoluciones;

  54: }

Y a partir de aquí a comprobar que todo funciona como se espera:

  • En primer lugar, verificamos que la solución sanbox en cuestión no está activa en la galería de soluciones.
  • Ejecutamos el proyecto de forma que en la primera ventana del mismo especificamos la Url de la colección de sitios de Office 365.
  • A continuación y desde el formulario anterior se muestra la ventana de petición de credenciales de Office 365.
image image image
  • Una vez especificadas las credenciales, se muestra el segundo formulario. Pulsamos el botón de activación de solución.
  • Una vez que el proceso de activación concluye, veremos como en el grid aparece la solución Sandbox con estado activado.
  • Lo mismo se puede comprobar al inspeccionar de nuevo la galería de soluciones.
image image image

Publicado por

Juan Carlos González

Juan Carlos es Ingeniero de Telecomunicaciones por la Universidad de Valladolid y Diplomado en Ciencias Empresariales por la Universidad Oberta de Catalunya (UOC). Cuenta con más de 12 años de experiencia en tecnologías y plataformas de Microsoft diversas (SQL Server, Visual Studio, .NET Framework, etc.), aunque su trabajo diario gira en torno a SharePoint & Office 365. Juan Carlos es MVP de Office Servers & Services desde 2015 (anteriormente fue reconocido por Microsoft como MVP de Office 365 y MVP de SharePoint Server desde 2008 hasta 2015), coordinador del grupo de usuarios .NET de Cantabria (Nuberos.Net, www.nuberos.es), co-fundador y coordinador del Grupo de Usuarios de SharePoint de España (SUGES, www.suges.es), así como co-director de la revista gratuita en castellano sobre SharePoint CompartiMOSS (www.compartimoss.com). Hasta la fecha, ha publicado 8 libros sobre SharePoint & Office 365 y varios artículos en castellano y en inglés sobre ambas plataformas.

6 comentarios en “SharePoint 2010: ¿Se pueden activar features con el modelo de objetos en cliente? (II)”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *