miércoles, 28 de enero de 2009

OwnerEventHandler 1.0.0

Recientemente he publicado en codeplex el proyecto “OwnerEventHandler”. Consiste en una solución sharepoint que despliega un eventhandler para controlar las modifiocaciones de los elementos o documentos.

OwnerEventHandler permite que los usuarios tenga permisos de colaborador en las listas o librerías pero solo puedan modificar o editar los documentos de los que son propietarios.

La ventaja de este proyecto es que tiene múltiples utilidades con ciertos cambios en el código. Por ejemplo: podríamos utilizarlo para cambiar los permisos de los elementos, de manera que cada usuario pudiera ver los elementos que haya subido, o incluso podrías hacer que solo los pudieran editar un grupo de usuarios encargados de la supervisión.

El proyecto está compuesto de una solución WSP “OwnerEventReceiver”, que contiene una feature a nivel de sitio “OwnerItemEventReceiver”. El eventHandler está alojado en la librería OwnerEventHandler.dll.

Para registrarlo en una librería o biblioteca podemos utilizar "Event handler explorer”.

El código es bastante sencillo, consiste en un EventHandler que captura el evento ItemAdded para modificar los permisos de manera que todos los miembros excepto el propietario tengan permisos de lectura.

 

public class OwnerEventItemEventReceiver : SPItemEventReceiver
    { 
         public override void ItemAdded(SPItemEventProperties properties)
        {
            try
            {
                SPUser currentUser = properties.ListItem.ParentList.ParentWeb.CurrentUser;

                using (SPWeb webOrigUser = properties.OpenWeb())
                {
                    SPUserToken token = webOrigUser.AllUsers["SHAREPOINT\\system"].UserToken;
                    using (SPSite site = new SPSite(properties.SiteId, token))
                    {
                        using (SPWeb currentWeb = site.OpenWeb(properties.RelativeWebUrl))
                        {

                            try
                            {
                                DisableEventFiring();

                                SPList sourceList = currentWeb.Lists[properties.ListId];
                                SPListItem itemAdded = sourceList.GetItemById(properties.ListItem.ID);
                                if (!itemAdded.HasUniqueRoleAssignments)
                                {
                                    itemAdded.BreakRoleInheritance(true);
                                    itemAdded.ParentList.ParentWeb.Dispose();
                                }

                                SPRoleAssignmentCollection roleAssignmentCollection = itemAdded.RoleAssignments;
                                foreach (SPRoleAssignment roleAssignment in roleAssignmentCollection)
                                {
                                    roleAssignment.RoleDefinitionBindings.RemoveAll();
                                    roleAssignment.RoleDefinitionBindings.Add(currentWeb.RoleDefinitions.GetByType(SPRoleType.Reader));
                                    roleAssignment.Update();
                                }

                                SPRoleDefinition reviserRoleDefinition = currentWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);
                                SPRoleAssignment reviserRolePropietario = new SPRoleAssignment(currentUser);
                                reviserRolePropietario.RoleDefinitionBindings.Add(reviserRoleDefinition);
                                itemAdded.RoleAssignments.Add(reviserRolePropietario);

                                itemAdded.Update();
                            }
                            catch (Exception ex1)
                            {
                                properties.ErrorMessage = ex1.Message;
                                properties.Status = SPEventReceiverStatus.CancelWithError;
                                System.Diagnostics.Trace.Write("Exception OwnerEventHandler.OwnerEventItemEventReceiver.ItemAdded.1: {0}", ex1.Message);
                            }
                            finally
                            {
                                EnableEventFiring();
                            }

                        }
                    }
                }
            }
            catch (Exception ex)
            {
                properties.ErrorMessage = ex.Message;
                properties.Status = SPEventReceiverStatus.CancelWithError;
                System.Diagnostics.Trace.Write("Exception OwnerEventHandler.OwnerEventItemEventReceiver.ItemAdded: {0}", ex.Message);
            }
        }

    }

 

Lo primero que hacemos es obtener el token del usuario system, esto es parecido a ejecutar “SPSecurity.RunWithElevatedPrivileges”.

A continuación abrimos una referencia a la colección y al sitio, hacemos esto en lugar de utilizar el del SPItemEventProperties debido a que las referencias que mantiene el elemento añadido referencian al usuario que añadió el elemento, por lo que a la hora de modificar los permisos puede que no tenga permisos de Adminsitrador.

Después recorremos todas las configuraciones de permisos del elemento añadido y las editamos de manera que los miembros solo puedan leer.

Por último añadimos al usuario propietario el control total sobre el elemento.

lunes, 26 de enero de 2009

Implementación de Contentypes

Los contentypes serán uno de nuestros elementos en nuestros desarrollos sobre Sharepoint.

Para ver una introducción acerca de los contentypes y cómo manejarlos desde la interfaz gráfica podéis leer el post “Tipo de Contenido en SharePoint” de Jorge Diéguez.

En este post nos centraremos en la definición de contentypes mediante features creadas con “Herramientas para el desarrollo en Sharepoint”.

Empezaremos por definir nuestro contentype desde la interfaz gráfica. Con esto conseguiremos que Sharepoint cree la definición del contentype por nosotros. Para extraer esa definición a xml utilizaremos alguna de las herramientas disponibles como: Sharepoint Manager, FieldsExplorer o Feature Generator.

Para el ejemplo crearemos en Sharepoint un contenttype para almacenar los datos de los empleados. Definimos entonces el contentype Empleados con las columnas: Nombre, apellidos, número empleado.

Una vez creado el tipo de contenido, abriremos el Visual Studio y crearemos una solución con las extensiones de VSeWSS. Crearemos un proyecto del tipo “Empty” y añadiremos un elemento del tipo “ContentType”. Al añadir el elemento Visual studio nos preguntará sobre que tipo primario queremos que herede nuestro contentype. En este caso elegiremos “Item” o “elemento”, aunque podremos elegir cualquiera de los primarios que nos aparezca en la lista.

Sobre la vista VSeWSS veremos una nueva Feature, en mi caso con el nombre “EmpleadosContentType”.

Abriremos a continuación el fichero Xml que nos a generado las extensiones. En mi caso “EmpleadosContentype.xml”. Veremos que tenemos una definición de un contentype que hereda del tipo primario “Item”.

Este xml será el que defina nuestro contentype. En la sección fieldrefs añadiremos todas las referencias a los tipos de columnas o “Site columns” definidas previamente. En caso de tener que utilizar nuevos tipos de columnas las podremos definir mediante el tag “Field” bien en este fichero o en otro de esta u otra feature. Cada site column tiene un GUID único, por lo que para referenciarla utilizaremos este valor especificado en el atributo “ID”. En caso de utilizar columnas estándar definidas por Sharepoint, tendremos que buscar sus GUID’s y no será necesario tener que definirlas de nuevo.

Para obtener toda esta información utilizaremos Sharepoint Manager 2007. Navegaremos a nuestra colección y desplegaremos el elemento “Content types” alojado en la raíz de la colección. A continuación seleccionaremos la pestaña “Schema Xml”.

Esto nos mostrará la definición de nuestro content type en xml. Copiaremos el Xml generado y lo pegaremos en nuestro fichero XML dentro del tag “Elements”. Sharepoint Designer nos mostrará el xml de la instancia, por lo que tendremos que realizar algunos ajustes sobre el xml.

Para asegurarnos que las modificaciones tienen la estructura correcta, activaremos la validación del intellisense.

A continuación moveremos todos los elementos Field fuera del tag contentype y quitaremos todos los atributos subrayados como no reconocidos por el intellisense.

A continuación crearemos un elemento “FieldRef” por cada campo del contentype con los GUID de los campos que desplazamos previamente.

En mi ejemplo queda un xml como el siguiente:

<?xml version="1.0" encoding="utf-8"?>

<Elements Id="ad412d2a-bf6b-4ebe-bcd8-fb32fd01347f" xmlns="http://schemas.microsoft.com/sharepoint/">

<ContentType ID="0x0100D4030561D9561C4BB0330408060E5817" Name="Empleado" Group="Tipos de contenido personalizados">

<FieldRefs>

<FieldRef ID="{4a722dd4-d406-4356-93f9-2550b8f50dd0}" Name="FirstName" />

<FieldRef ID="{06b22ccb-9e3d-41d4-884f-4faf85d8c934}" Name="Apellidos" />

<FieldRef ID="{b0f31712-0348-4248-9fde-1cb33839da68}" Name="NumeroEmpleado" />

</FieldRefs>

</ContentType>

<Field ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" Name="ContentType" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="ContentType" Group="_Hidden" RowOrdinal="0" Type="Text" DisplayName="Tipo de contenido" ReadOnly="TRUE" Sealed="TRUE" ColName="tp_ContentType" PITarget="MicrosoftWindowsSharePointServices" PIAttribute="ContentTypeID" />

<Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="Title" Group="_Hidden" Type="Text" DisplayName="Título" Required="TRUE" FromBaseType="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />

<Field ID="{4a722dd4-d406-4356-93f9-2550b8f50dd0}" Name="FirstName" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="FirstName" Group="Columnas de calendario y contacto principal" DisplayName="Nombre" Type="Text" />

<Field Type="Text" DisplayName="Apellidos" Required="FALSE" MaxLength="255" Group="Columnas personalizadas" ID="{06b22ccb-9e3d-41d4-884f-4faf85d8c934}" SourceID="{d0b52269-2cb7-4c94-83c9-d40cf4207783}" StaticName="Apellidos" Name="Apellidos" />

<Field Type="Text" DisplayName="Numero Empleado" Required="FALSE" MaxLength="255" Group="Columnas personalizadas" ID="{b0f31712-0348-4248-9fde-1cb33839da68}" SourceID="{d0b52269-2cb7-4c94-83c9-d40cf4207783}" StaticName="Numero_x0020_Empleado" Name="Numero_x0020_Empleado" />

</Elements>

 

Ahora solo nos queda desplegar nuestra solución y activar la feature. Pero antes de hacer esto, como lo que estamos haciendo con nuestra feature es volver a definir un contentype que ya tenemos instanciado con el mismo GUID, nos aseguraremos de eliminarlo y las site columns definidas. Si no queremos hacer esto, podremos cambiar el nombre y los GUID del contenttype y de las sitecolumns

Para generear el XMl también podría haber utilizado “Fields Explorer” que nos permite exportar rápidamente los contentypes y site columns ya definidos.

viernes, 23 de enero de 2009

¿Cumpliste los objetivos?

Es lo que toca este mes, revisar objetivos. Este año parece que mucha gente no los está cumpliendo o si los cumple da igual, porque no hay dinero. ¿Pero no se supone que los objetivos son acerca de como lo hayas hecho el 2008?. ¿Para que sirven los objetivos o pagas de beneficios y demás?.

A mi entender los objetivos deben plantearse como un aliciente para que los empleados se involucren más en la compañía y para que tanto empresa como empleado evolucionen en el mismo camino, “Yo te ayudo, pero tú me ayudas a mí”. Evidentemente si lo haces bien deberías merecerte una subida de sueldo o un incentivo, pero es que el trabajo deberíamos hacerlo siempre bien, el cliente siempre debería estar contento con nosotros. Por lo tanto los objetivos deben ir más allá, deben de promover actitudes y actividades que hagan que el empleado y la empresa sean diferenciales del resto.

En mi caso no he cumplido objetivos porque me lo propuse así desde el principio, en mi caso estaban mal planteados. Es decir se daba importancia a aspectos que deberíamos tener y se premiaban actividades que realmente no aportaban mucho a la empresa y al resto de compañeros.

Al menos este año en nuestra empresa parece que se han dado cuenta y lo han intentado solucionar para 2009. Aparte de las certificaciones, se valoran aspectos como dar charlas o gravarlas (antes no valía con grabarlas!!), dar cursos, workshops o similares, mantener un blog (esta la tengo asegurada). En resumen, este 2009 habrá que arrimar el hombro para ayudar a nuestra empresa a ser más atractivos.

¿y tú qué tal este año?.

jueves, 22 de enero de 2009

Documentación Microsoft Dynamics NAV 2009

Microsoft ha publicado varias guías para el desarrollo, instalación y administración de Nav2009. podéis descargarlo en “Microsoft Dynamics NAV 2009 Developer and IT Pro Help”.

Os recomiendo algunos post como: “Microsoft Dynamics NAV Team Blog”, “Clausl's Dynamics NAV Blog”, “Freddys Blog”, “Waldo’s blog”, “Kine’s blog

martes, 20 de enero de 2009

MCTS en MOSS2007 Application Development

Un poco tarde, pero al fin me saqué la certificación 70-542 “MCTS Microsoft Office SharePoint Server 2007 ― Application Development”.

Volviendo al tema de las certificaciones, en este caso aunque no terminas siendo un experto al menos tienes una visión general de las funcionalidades de MOSS. Recomiendo haber realizado antes la certificación 70-541 que te permite entender como funcionan las tripas de sharepoint.

viernes, 16 de enero de 2009

Introducción a las Visual extension for Sharepoint

Las VSeWSS (Visual extension for Sharepoint) consisten en unas extensiones para Visual Studio para el desarrollo de soluciones y elementos de Sharepoint. Las VSeWSS nos ayudarán a construir los esqueletos de nuestras soluciones, features, webparts y sobre todo nos ahorrará trabajo a la hora de crear el fastidioso manifest de las soluciones.

Aunque existen otras soluciones similares como WspBuilder, VseWSS será una solución muy válida para nuestros proyectos.

Para utiliza VSeWSS crearemos un proyecto del tipo “Sharepoint”. Al seleccionar esta categoría veremos que podremos crear varios tipos de proyecto como: Definiciones de listas y sitios, webparts o proyectos vacíos para agregar lo que querramos.

Todos los proyectos que realicemos con VseWSS estarán compuesto por los ficheros propios que agreguemos y los que definan una solución de Sharepoint. Dentro de la solución se dividirá en varias features. Una solución de Sharepoint consiste en un fichero con extensión WSP que contiene todos los ficheros que creemos, sus definiciones y donde se alojarán al desplegarse la solución. Al agregar una solución a la granja estaremos almacenando el fichero en la bbdd de configuración. A continuación podremos desplegarla en los frontales que deseemos. Al desplegarla, Sharepoint copiará por nosotros los ficheros en todos los frontales y copiará las definiciones que correspondan. Si disponemos de una Web Application extendida solo tendremos que desplegarla en una de ellas ya que Sharepoint se encargará de replicarla.

Para este ejemplo crearemos un nuevo WebPart. Al seleccionarlo nos creará los ficheros para definir el webpart y los necesarios para crear la solución de sharepoint. Los ficheros de la definición de la solución y features están alojados en la carpeta “pkg” que no está agregada al proyecto. Para verla seleccionaremos la vista “Mostrar todos los ficheros”. Al hacerlo veremos la estructura del paquete una vez lo hayamos generado o previsualizado. Estos ficheros además los mantendrá el propio Vsewss automáticmanete, por lo que no tendremos que tocar nada manualmente a no ser que querramos hacer cosas avanzadas.

Solution.xml

Es un fichero propio de VSEWSS y contiene la definición de la solución describiendo que features se van a utilizar y que elementos la componen. Para construir el fichero de solución utiliza el guid indicado en el nodo xml “Element”, por lo que todos nuestros ficheros deben contener un nodo “Element” con un guid. Si no lo indicamos Vsewss construirá una feature por cada uno de los ficheros, y tendremos que modificar los ficheros a mano para volver al esado anterior.

Manifest.xml

Contiene la descripción que utilizará Sharepoint a la hora de desplegar la solución para saber dónde irá alojado cada fichero que componga la solución.

Feature.xml

Contiene la definición de las feature.

Al construir la solución Visual studio compilará los ficheros agregados en la solución. Para crear la solución de Sharepoint tendremos que seleccionar las opciones de las extensiones “Package” o “Deploy”. Esto nos creará por un lado todos los ficheros de definición de la solución y features y por otro lado los ficheros “*.wsp” que corresponde a la solución. El fichero wsp lo creará en el directorio bin.

Para crear los ficheros solution.xml, manifest.xml y las features podremos realizarlo construyendo el paquete o desde la vista “WSSP View” accesible desde el menú “View->Other Windows->WSSP View”. Esta ventana mostrará la estructura de nuestra solución.

Además de crear WebParts podremos añadir a nuestra solución otro tipo de elementos como: Definiciones de listas, contentypes, even handlers, field controls, …

Uno de los elementos más interesantes que podemos añadir es el “Template”. Esto nos creará una carpeta Template, de manera que vsewss nos copiará automáticamente al directorio “12\Template” de Sharepoint todos los ficheros que agreguemos en este directorio. Básicamente lo que hace con el elemento “Template” es mantener la sección “TemplateFiles” del fichero manifest de la solución.

Para crear una nueva feature podremos hacerlo manualmente o desde la vista “WSS View” en el botón superior “Create new feature”.

Una vez hayamos creado y compilado nuestra solución podremos desplegarla seleciconando la opción “Deploy” accesible al seleccionar con el botón derecho el fichero de proyecto de Visual Studio. Para desplegar una solución antes tendremos que indicar a Visual Studio sobre que web application lo haremos. Para hacerlo seleccionaremos la propiedades del proyecto y en la pestaña “Debug” marcaremos la opción “Start browser at URL”.

Al desplegar la solución veremos en la barra inferior de Visual Studio el progreso del despliegue. Además de la acción de despliegue podremos retraer la solución o copiar los ficheros de forma rápida mediante la opción “Quick Deploy”.

Nota: Para que podamos desplegar la solución debemos tener permisos de administración de la granja con el usuario logado.

Al trabajar con vsewss puede que nos den algunos errores al desplegar la solución muy distintos a los que nos daría si lo hiciéramos a mano.

caracteres no válidos en la ruta

Esto se produce al hacer instalaciones con idiomas distintos. Una solución es cambiar en las variables de entorno del servidor las rutas “Temps” y “Temp” por un directorio que no dependa del idioma, por ejemplo “C:\Temp”.

Could not load file or assembly

Esto se produce cuando hacemos referencia a un assembly que no está registrado en el GAC. Solo tenemos que agregarlo al GAC.

This solution contains two assemblies with the same name

Se produce cuando hemos registrado la dll manualmente en el gac. Solo hayque desinstalarla y deplegar de nuevo.

Más información acerca de errores comunes con vsewss: http://blog.csdn.net/timewolf/archive/2008/07/23/2694607.aspx

miércoles, 14 de enero de 2009

This Page has been modified since you opened it

El webpart que estaba construyendo estaba extendiendo las funcionalidades de búsqueda de MOSS y al hacer un submit a la página de resultado me daba el error “This Page has been modified since you opened it. You must open the page again.” o “Se ha modificado esta página desde que la abrió. Debe volver a abrirla.”. Lo curioso es que encima no dejaba ninguna marca en el log de Sharepoint ni de windows, por lo que no sabes exactamente que está pasando.

Al final después de mucho batallar, me fijé que el control estándar que estaba extendiendo llamaba a dos funciones javascript al hacer el submit

… onclick="ResetPageHashCode();WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions( …

Al principio no le dí mucha importancia, pero justo cuando estaba probando con Fidler para comparar lo que enviaba el control estándar con el mío, me di cuenta que borrando el valor del parámetro post “MSO_PageHashCode” dejaba de dar el error. Qué curioso!!!, resulta que mirando el código de la función “ResetPageHashCode” borra este elemento de la página antes de hacer un submit.

Efectivamente añadiendo esta llamada antes de hacer un submit conseguí arreglar mi control, aunque en mi caso tuve que tocarla ya que no me llegaba a funcionar del todo:

function ResetPageHashCode()
{
var f = document.forms[0];
if (null != f && null != f.elements['MSO_PageHashCode'])
f.elements['MSO_PageHashCode'].value = "";

_spFormOnSubmitCalled = false;
}

<asp:Button ID="ASB_BS_SRCH_1" runat="server" Text="Buscar" CssClass="boton" OnClientClick="ResetPageHashCode();Buscar();" />

No puedo asegurar que sea la solución para todos los casos en los que ocurre este error ya que se proponen muchas soluciones, pero en mi caso ha funcionado.

Otras soluciones a este error:

Resolution: MOSS: This Page has been modified since you opened it. You must open the page again.

SharePoint Errors, Warnings and Problems Collection

This Page has been modified since you opened it. You must open the page again.

Finally: A Federation Search results page that doesn't give me the need to "Refresh Page" before I can see the results