miércoles, 26 de enero de 2011

SPContext, using, RunWithElevatedPrivileges y demás…

Lo que os voy a explicar hoy no es nada nuevo. Mucho se ha escrito al respecto e incluso yo, seguramente, he escrito algún artículo sobre ello, pero el que se hable mucho de una cosa no implica que no sea importante o que no se tenga que ir recordando. El caso es que ayer, a partir de una consulta en un foro, recordé el que es, bajo mi punto de vista, el error que más comunmente cometemos los que desarrollamos sobre SharePoint, y pensé que sería un buen ejercicio escribir este artículo para ver si para todos aquellos que empiezan en esta tecnología (y todos aquellos que llevamos años pero que sistemáticamente cometemos una y otra vez el mismo error…) podría resultar de utilidad.

Comenzaremos por una cosa muy simple: estoy desarrollando un elemento web que tiene que acceder a información que se encuentra alojada en el sitio web donde reside dicho webpart. Lo primero que se plantea uno, es algo como lo del siguiente listado.

SPSite site = new SPSite(url);
SPWeb web = site.OpenWeb();

La variable url la habremos inicializado previamente con alguna información del contexto y lo único que tenemos que hacer es instanciar un objeto SPSite y un objeto SPWeb. Esto no es del todo incorrecto siempre que cuando acabemos de utilizar dichos objetos los eliminemos. Al final, lo que acabaríamos haciendo para trabajar de una manera apropiada sería algo similar a lo siguiente.

using (SPSite site = new SPSite(url))
{
    using (SPWeb web = site.OpenWeb())
    {
    }
}

Si conocéis el objeto SPContext o si lo habéis visto en el código de alguien, os puede pasar por la cabeza la idea de utilizarlo para hacer el código de arriba más ‘correcto’ y cambiarlo por:

using (SPSite site = SPContext.Current.Site)
{
    using (SPWeb web = site.OpenWeb())
    {
    }
}

Y ahí es donde lo romperíamos todo. Si os fijáis, ahora no estamos creando ninguna instancia de SPSite, sino que estamos haciendo una referencia al objeto de nuestro contexto, de manera que en cuanto salgamos del using y se ejecute el dispose estaremos eliminando un objeto que no deberíamos eliminar nunca. ¿Cómo arreglamos el código de arriba?

using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
    using (SPWeb web = site.OpenWeb())
    {
    }
}

¡Atención! fijáos que uso SPContext.Current.Site.ID y no SPContext.Current.Site.Url como parámetro de la constructora. Aparentemente el resultado es el mismo, pero cuando trabajamos en sitios extendidos la cosa varía un poquito. De todas maneras, si lo que tenéis que hacer es lo de arriba, podéis simplificarlo mucho y dejarlo tal que así:

SPWeb web = SPContext.Current.Web;

Pero aquí es cuando entra en juego nuestro amigo RunWithElevatedPrivileges. ¿Qué pasa cuando desde ese código necesitamos acceder a información con permisos elevados? Nada, que rodeamos el código de esta manera:

SPSecurity.RunWithElevatedPrivileges(delegate
{
    SPWeb web = SPContext.Current.Web;
});

Aquí lo volveríamos a romper todo, ya que el objeto SPContext es, como su nombre indica, una referencia al contexto de SharePoint, que está asociado al usuario autenticado independientemente de que el código se ejecute de manera elevada. La recomendación siempre es hacer uso del RunWithElevatedPrivileges sólo cuando sea estrictamente necesario y en el mínimo ámbito posible y, por lo tanto, todo nos empujaría a hacerlo de la siguiente manera.

using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
    using (SPWeb web = site.OpenWeb())
    {
        SPSecurity.RunWithElevatedPrivileges(delegate
        {
        });
    }
}
 

Aquí estaríamos cometiendo el mismo error. En el momento en que hemos creado la instancia del objeto SPSite estábamos trabajando en un contexto no elevado y, por lo tanto, así seguiremos siempre que hagamos referencia a éste. Si queremos tratar información de ese objeto SPWeb que viene de la instancia del objeto SPSite creada al inicio del código lo tenemos que hacer de la siguiente manera:

SPSecurity.RunWithElevatedPrivileges(delegate
{
    using (SPSite site = new SPSite(SPContext.Current.Site.ID))
    {
        using (SPWeb web = site.OpenWeb())
        {
        }
    }
});

Para finalizar, deciros que esto es sólo un ejemplo para un caso concreto. En general, trabajar con SharePoint, con su contexto y con la creación de objetos es algo que se tiene que hacer siguiendo ciertas normas. Os dejo un enlace que os puede resultar de interés: http://code.msdn.microsoft.com/SPDisposeCheck

Con esta utilidad podréis comprobar si estáis siguiendo correctamente las normas o no.

0 comentarios: