jueves, 16 de junio de 2016

Crear contextos ProjectContext o ClientContext para Project Online en una Provider-hosted app con Project o SharePoint Online con CSOM y TokenHelper


CSOM, nuestra api de desarrollo para conectar desde un cliente contra servicios web de SharePoint, tiene un comportamiento muy distinto a la antigua api de servidor de SharePoint o a la muy apreciada por mí, digo con mucho sarcasmo con conste, api PSI de Project.

En este caso nuestro paciente quería autenticarse remotamente contra los servicios de Project Server, hasta aquí bien, utilicemos un objeto ProjectContext o ClientContext como hemos hecho siempre y ya está, idea feliz.

Entonces nos damos cuenta de que la cosa no es tan sencilla. Como muchos ya sabréis, Microsoft nos provee en nuestros proyectos de una clase llamada TokenHelper para hacernos la vida más fácil, o eso nos dijeron al menos.

Además, nuestro paciente tuvo la increíble ocurrencia de que no le bastaba sólo con necesitar obtener un contexto de sí mismo (la persona que se conecta a SharePoint) si no que además también quiere obtener contextos de otros usuarios, como por ejemplo alguien con privilegios de administrador para poder obtener datos de toda la colección de sitios o de datos de Project Server de otros usuarios. Esto es, lo que antiguamente conocíamos como "pf, tío no te compliques!, haz un RunWithElevatedPrivileges y yatá!"

Tanta elucubración dio como resultado una clase que llamamos Connection.cs, con un método estático y público que inicializaremos al principio de la carga de la página o del servicio que estemos programando y que mágicamente nos generará dos contextos de ejecución, uno para el usuario que está conectándose y otro para la cuenta que nosotros queramos, todo ello sacado del HttpContext que realiza el ingreso en SharePoint, cosas del funcionamiento de TokenHelper y el querystring que no vamos a explicar aquí.

En primer lugar, en nuestra clase hemos de tener dos propiedades estáticas de sólo lectura de tipo ProjectContext, llamadas pwaContextServicio y pwaContextUsuario, y después utilizar este método.
public static void InitializeProjectServerContext(HttpRequest request, bool pageIsPostback)
        {
            if (!pageIsPostback)
            {
                pwaContextServicio = ProjectConnector.GetProjectOnlineContext(
                    request.QueryString["SPHostUrl"].ToString(),
                    "usuario",
                    "password",
                    "dominio");

                pwaContextUsuario = TokenHelper.GetProjectContextWithContextToken(
                 request.QueryString["SPHostUrl"].ToString(),
                 TokenHelper.GetContextTokenFromRequest(request),
                 request.Url.Authority);
             
            }
        }
pwaContextServicio se cargará con la cuenta elegida por nosotros y pwaContextUsuario ser cargará con la cuenta de las credenciales provistas al ingresar a SharePoint Online.

El asunto más gordo que requirió más horas de cirugía invasiva fue la clase ProjectConnector, que a su vez es otra clase creada por nosotros que utilizamos para este menester, su método estático GetProjectOnlineContext es el que realiza la magia.

Esta clase está creada a partir de encapsular en un proyecto la clase Office365ClaimsHelper, todo esto se puede encontrar en GitHub.

ProjectConnector, creada por nosotros, la gente de Doctor SharePoint:
using Microsoft.ProjectServer.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RC.Project
{
    public static class ProjectConnector
    {
        private static string connectUserName;
        private static string connectPwd;
        private static string pwaPath;
        private static string projDomain;
        public static ProjectContext GetProjectOnlineContext(string pPwaPath, string pUserName, string pPassword, string pDomain)
        {
            connectUserName = pUserName;
            connectPwd = pPassword;
            projDomain = pDomain;
            pwaPath = pPwaPath;
            return ProjectContext;
        }

        private static ProjectContext ProjectContext
        {
            get
            {
                var pc = new ProjectContext(pwaPath);
                var claimsHelper = ClaimsHelper;
                pc.ExecutingWebRequest += claimsHelper.clientContext_ExecutingWebRequest;
                return pc;
            }
        }
        private static Office365ClaimsHelper ClaimsHelper
        {
            get
            {
                return new Office365ClaimsHelper(new Uri(pwaPath),
                                                    string.Format("{0}@{1}", connectUserName, projDomain),
                                                    connectPwd);
            }
        }
    }
}
A su vez, Office365ClaimsHelper se apoya en otras clases, por tanto, nos encargaremos primero de descargar del GitHub las siguientes clases y encapsularlas en nuestra biblioteca de clases junto a ProjectContext:

IWSTrustFeb2005Contract
Office365ClaimsHelper
RequestBodyWriter
WsTrustFeb2005ContractClient

Una vez construído todo esto ya tendremos una forma de conectar con el usuario que queramos a Project Server (o a SharePoint) pasándole el dominio, usuario y password.

Recordemos siempre que un objeto de contexto de Project siempre está siendo heredado de uno de SharePoint, por lo que crear a partir de ProjectContext un ClientRuntimeContext es tan sencillo como añadir esto:
private static ClientRuntimeContext InitializeSharePointContext(ProjectContext pwaContextServicio)
        {
            Web web = pwaContextServicio.Web;
            pwaContextServicio.Load(web);
            pwaContextServicio.ExecuteQuery();
            return web.Context;
        }
Y nada más, le damos a nuestro paciente un par de Nolotiles  test de ejecución y para casa.

No hay comentarios: