Estábamos trabajando en un proyecto sobre Sharepoint 2007 para nuestra intranet y nos encontramos con el problema que necesitábamos en una lista hacer un campo lookup hacia otra lista que se encontraba en otro subSite del portal. Si bien Sharepoint 2007 nos permite realizar campos lookup en nuestra listas, los mismos son solo contra lista que se encuentran en el mismo sitio donde estamos creando dicho campo. Para solucionar el problema encontramos una solución publicada por “Tony Bierman” llamada Cross-Site. Bien, lo primero que vamos hacer es crearnos un archivo *.snk para firmar nuestro Assembly dado que el mismo deberá ser colocado en la GAC del servidor, el mismo lo generamos utilizando la herramienta sn.exe que vienen con el FrameWork, en mi caso yo tengo uno generado (Siderys.snk) que es el que utilizo para firmar todos los Assemblies desarrollados. A continuación debemos configurar nuestro proyecto con el espacio de nombre por defecto, el nombre que llevara nuestro Assembly y cargar el archivo snk. [Sección 1] <%@Control Language="C#" %> <%@Assembly Name="Siderys.Blog.CustomField.UniqueColumnFieldType, Version=1.0.0.0, Lo próximo que tenemos que hacer es implementar la clase que tendrá el codebeheind de nuestro control de usuario, como se puede ver en la directiva @Assembly colocada en el control de usuario, nuestro control de usuario contendrá una clase donde implementaremos todo el código necesario para realizar las validaciones del valor ingresado por el usuario. Ya comentamos que la validación la realizaremos usando un CAML contra la lista donde se creó la columna, para lo cual necesitamos saber cuál es la lista y además necesitamos saber cómo se llama la columna en Sharepoint (el nombre interno de la misma), para ello nuestro control de usuario contendrá una sobre cargar del constructor donde recibirá todos los valores necesarios para ejecutar la misma. Además esta clase es la que implementara la interface “ICallbackEventHandler” para realizar la llamada vía CallBack, vale la pena aclarar que cuando implementamos esta interface debemos implementar dos métodos “RaiseCallbackEvent” y “GetCallbackResult” que son los encargados de recibir la petición desde el cliente vía JavaScript y la encargada de enviar la notificación una vez se termine la ejecución. En la sección 2 vemos el código completo de la clase “UniqueFieldColumn” .
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using Microsoft.SharePoint.WebControls; using Microsoft.SharePoint; namespace Siderys.Blog.CustomField { public partial class UniqueFieldColumn : BaseFieldControl, ICallbackEventHandler { protected TextBox txtValueField; protected RequiredFieldValidator rfvTxtValueField; private bool mRequired = false; private SPList mListField = null; private string mInternalName = string.Empty; private string mReturnCheckFieldCallback = string.Empty; public UniqueFieldColumn(bool pRequired, string pInternalName, SPList pListField) { mRequired = pRequired; mInternalName = pInternalName; mListField = pListField; } protected override string DefaultTemplateName { get { return "txtUniqueColumn"; } } public override object Value { get { EnsureChildControls(); return txtValueField.Text; } set { EnsureChildControls(); txtValueField.Text = value.ToString(); } } public override void Focus() { EnsureChildControls(); txtValueField.Focus(); } protected override void CreateChildControls() { if (Field == null) { return; } base.CreateChildControls(); if (ControlMode.Equals(SPControlMode.Display)) { return; } txtValueField = (TextBox)TemplateContainer.FindControl("txtValueField"); txtValueField.Attributes.Add("onchange", "OnCallbackCheckField(this.value)"); rfvTxtValueField = Lo próximo que haremos es extender la clase SPFieldText que es la que utiliza Sharepoint y WSS para representar una columna de tipo texto en nuestras listas. En nuestro caso estamos haciendo un campo de texto y nos basamos en esta columna para realizar la nuestra, pero si estuviéramos en otro escenario, por ejemplo creando un tipo de columna más compleja podríamos utilizar cualquieras de las otras clases provistas por Sharepoint y WSS. Esta clase declara dos constructores personalizados y realiza la sobre carga del método “GetValidatedString” donde podríamos realiza la validación del texto introducido por el usuario si no estuviéramos utilizando CallBacks y la sobre carga de la property “FieldRenderingControl” que nos devuelve la instancia de nuestro control utilizando la clase base del mismo “BaseFieldControl”. En la sección 3 vemos la implementación de la clase “UniqueFieldType” que es la que utilizamos para la generación de nuestro columna personalizada.
using System; using System.Collections.Generic; using System.Text; using Microsoft.SharePoint; using Microsoft.SharePoint.WebControls; using Siderys.Blog.CustomField; namespace Siderys.Blog.CustomField { public class UniqueFieldType: Microsoft.SharePoint.SPFieldText { public UniqueFieldType(SPFieldCollection pFields, string pFieldName):base(pFields, pFieldName) { } public UniqueFieldType(SPFieldCollection pFields, string pFieldName, string pDisplayName) Por último debemos crear un archivo XML para definir nuestra columna en Sharepoint y WSS, este XML deberá comenzar con “fldtypes_” y después podemos llamarlo como nosotros necesitemos, en nuestro caso se llama “fldtypes_UniqueColumn.xml” y el mismo deberá ser colocado en una ruta especifica dentro del servidor, para que sea tenido en cuenta, la misma es “Microsoft Sharedweb server extensions12TEMPLATEXML”. Nosotros hemos configurado las propiedades obligatorias que debe tener el elemento “FieldTypes” dentro del archivo, pero en la siguiente página del MSDN podrán ver todas las propiedades que se pueden configurar para este elemento. En la sección 4 vemos el código completo para nuestro archivo XML.
<FieldTypes> <FieldType> <Field Name="TypeName">UniqueColumn</Field> <Field Name="TypeDisplayName">Unique Column</Field> <Field Name="TypeShortDescription">Unique Column</Field> <Field Name="ParentType">Text</Field> <Field Name="FieldTypeClass">Siderys.Blog.CustomField.UniqueFieldType, Una vez tenemos todo implementado, podemos proceder con la instalación de todo nuestro desarrollo, el problema acá es que todos los componente deben ser colocados en lugares diferentes dentro de nuestro servidor, así que me cree un archivo .bat para realizar la misma, en la sección 5 podrán ver el código de este instalador.
iisreset /stop "%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe" -uf
Ahora a probar que todo funciones correctamente, lo primero que tenemos que hacer es agregar la columna a una lista, en la imagen 2 vemos la pantalla de configuración para agregar una nueva columna.
Una vez creada la columna vamos a ingresar un elemento en la misma y después vamos a ingresar un segundo elemento con el mismo valor de la primera para ver como la validación del texto ingresado se realiza. Vale la pena destacar que los botones de Aceptar del formulario proporcionado por Sharepoint y WSS para ingresar un valor son deshabilitados si el valor esta repetido, esto es para que el usuario no pueda guardar el elemento antes de corregir el valor del texto, además se indica resaltando el campo en el formulario. En la imagen 3 vemos como se produce la validación del texto ingresado por el usuario.
En la imagen 4 vemos el campo resaltado después de realizada la validación correspondiente del texto ingresado por el usuario.
En la imagen 5 vemos los valores que ya esta cargados en la lista y confirmamos que el valor “Fabián” ya está en el campo “Unique Nombre” para la lista “Unique Column”.
Por último vemos en la imagen 6 como se realiza la validación del campo si el usuario definió la columna como requerida y tiene que ingresar la información.
En un próximo artículo vamos a estar confeccionando un tipo de columna mas compleja y nos permita tener tipos de columnas mas complejas. |
Mes: mayo 2008
WorkFlow Sharepoint 2007- Formulario de inicialización ASPX
Acá presentamos el artículo para la utilización de formularios ASPX en nuestros WorkFlow con la herramienta wss3workflow. Como comentamos esta herramienta, una vez instalada, nos brinda una serie de plantillas en el Visual Studio para agregar formularios. Para este proyecto, yo me cree una carpeta llamada “Forms” para colocar el formulario de inicialización que vamos a estar creando, parados en la carpeta presionamos el botón derecho, agregar, nuevo elemento y nos despliega una nueva ventana como se puede ver en la imagen 1, donde seleccionaremos “Initialization Form”, colocaremos un nombre y presionamos agregar. Vale la pena aclarar que en esta artículo no vamos hablar de cómo crear un WorkFlow desde un principio, dado que eso lo hemos explicado en nuestro anterior artículo WorkFlow de aprobación para Sharepoint 2007 paso a paso, lo que estaremos explicando en este artículo es como creamos un formulario ASPX de inicialización. El formulario de inicialización nos sirve para cargar información adicional a nuestro WorkFlow cuando el mismo está siendo inicializado. En este ejemplo en concreto lo que estamos haciendo es indicar que persona será la encargada de aprobar la tarea que se está creando, a lo cual nos permite indicar quienes son los aprobadores y tener un WorkFlow de aprobación genérico. Una vez que tenemos creado nuestro WorkFlow, lo que debemos hacer es programar nuestro formulario. Cuando agregamos el formulario a nuestro proyecto, en el mismo se agregaron dos archivos, el archivo ASPX y el archivo Aspx.cs el cual tendrá todo nuestro código servidor. Bien una vez que modificamos dicho atributo, lo próximo que haremos en nuestro formulario ASPX es agregar el atributo “MasterPageFile” a la directiva de pagina @Page para indicar cuál será la Pagina Maestra que utilizaremos en el mismo. Esto es importante, dado que nuestro formulario solo cuenta con “PlaceHolder” para agregar el contenido y si no queremos obtener un error en tiempo de ejecución indicando que no hay ninguna Pagina Maestra asociada debemos agregar este atributo como se muestra en la sección 2. Sección 2 MasterPageFile="~/_layouts/application.master" Para este ejemplo selecciona la Pagina Maestra llamada “application.master” debido a que quiero que aparezca todo el menú de navegación de Sharepoint en la parte superior, pero también podíamos haber elegido la Pagina Maestra “simple.master”. Una vez realizada las modificaciones sobre la directiva de página, lo próximo que vamos a realizar es la construcción de nuestro formulario. En nuestro formulario hemos colocado un control PeopleEditor de Sharepoint el cual utilizaremos para seleccionar al aprobador y dos Button. Sección 3 <asp:Content id="_mainContent" runat="server" ContentPlaceholderId="PlaceHolderMain"> <table> <tr> <td> <b>Aprobador</b> </td> <td> <spw:PeopleEditor ID="peAprobador" runat="server" AllowEmpty="false" ValidatorEnabled="true" MultiSelect="false" Title="Seleccionar Aprobador"/> </td> </tr> <tr> <td> <asp:Button ID="cmdAprobador" runat="server" Text="Aprobar" OnClick="cmdAprobar_Click" /> </td> <td> <asp:Button ID="cmdCancelar" runat="server" Text="Cancelar" OnClick="cmdCancelar_Click" /> </td> </tr> </table> <spw:FormDigest ID="_formDigest" runat="server" /> </asp:Content> Una vez creado nuestro formulario, lo próximo que haremos es agregar el código necesario en nuestra clase para programar los eventos de los dos botones y cargar la información introducida por el usuario para ser enviada al WorkFlow. Cuando creamos el formulario se creó esta clase asociada al mismo, la cual extiende de la clase “InstantiationForm” que se encuentra en el Assembly “CodeCounsel.SharePoint.Workflow.dll” instalado cuando instalamos el Add-In para el Visual Studio. Al abrir esta clase nos encontramos que la misma tiene un constructor por defecto y la sobrecarga del método GetInstantiationCustomData (). Este método es el utilizado por el formulario para enviar la información al WorkFlow una vez inicializado. Este método, solo nos permite enviar un String, en este ejemplo estaremos enviando el usuario cargado en el control PeopleEditor, al cual accederemos a través de la colección de cuentas que contiene este control. Nosotros vamos agregar los manejadores de los dos botones (Aceptar y Cancelar) que están declarados en el formulario ASPX, como son manejadores para el evento Click su firma es sencilla. En el método que maneja el evento Click del botón aceptar vamos a invocar al método de nuestra clase base llamado “CommitInstantiation ()” el cual confirma la inicialización de nuestro Workflow. Este método esta declarado en la clase “InstantiationForm” y en el método que maneja el evento del botón Cancelar invocaremos al método “CancelInstantiation ()” que redirecciona al usuario nuevamente a la página de propiedades de la lista. Bien, ya hemos implementado nuestro formulario, en la sección 4 podemos ver el código completo. Sección 4 using System; using CodeCounsel.SharePoint.Workflow; using Microsoft.SharePoint.WebControls; namespace Siderys.Blog.Workflow.Forms { public partial class WFInstantiationForm : InstantiationForm { string mUsuario = string.Empty; protected override string GetInstantiationCustomData() { return mUsuario; } protected void cmdAprobar_Click(object sender, EventArgs e) { mUsuario = Request.Form["ctl00$PlaceHolderMain$peAprobador$downlevelTextBox"].ToString(); CommitInstantiation(); } protected void cmdCancelar_Click(object sender, EventArgs e) { CancelInstantiation(); } } } Por último lo que debemos hacer es modificar el archivo WorkFlow.xml, para relacionar nuestro formulario con el WorkFlow, para ello utilizaremos el atributo “InstantiationUrl” de elemento “Workflow” dentro del archivo xml. El formulario en nuestro caso fue colocado bajo la ruta de “layout” de Sharepoint para el mismo sea tenido en cuenta desde cualquier sitio de nuestro portal Sharepoint. En la sección 5 vemos el código completo del archivo WorkFlow.xml y en la sección 6 vemos el código completo del archivo Feature.xml Sección 5 <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Workflow Name="Simple Approval WorkFlow Inicializacion Form Aspx" Description="Simple Approval WorkFlow Inicializacion Form Aspx" Id="9D44C6AA-128C-4256-B0E0-F30187CA5413" CodeBesideClass="Siderys.Blog.Workflow.WorkflowInicializacionForm" CodeBesideAssembly="Siderys.Blog.Workflow.WorkflowInicializacionForm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=711eed342842acee" StatusUrl="_layouts/WrkStat.aspx" InstantiationUrl="_layouts/SimpleApprovalFileForms/InicializacionForm/WFInstantiationForm.aspx" > <Categories/> <MetaData> <StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl> </MetaData> </Workflow> </Elements>
Sección 6 <Feature Id="9224D4F6-726A-4342-9751-B9A7EDA0CFB2" Title="Simple Approval WorkFlow Inicializacion Form Aspx" Description="Simple Approval WorkFlow Inicializacion Form Aspx" Version="12.0.0.0" Scope="Site" xmlns="http://schemas.microsoft.com/sharepoint/"> <ElementManifests> <ElementManifest Location="workflow.xml"/> </ElementManifests> <Properties> <Property Key="GloballyAvailable" Value="true" /> </Properties> </Feature>
En la imagen 3 podemos ver nuestro formulario en ejecución cuando estamos asociando un WorkFlow. En la imagen 4 vemos como se ejecuta el control PeopleEditor de Sharepoint 2007 en nuestro formulario. Por último en la imagen 5 vemos la tarea asignada al usuario cargado en el control PeopleEditor.
Este fue el último artículo sobre cómo podemos crear formularios ASPX y utilizarlos en nuestros Workflow en Sharepoint. Creo que es una muy buena opción a Infopath y más cuando estamos en el contexto de WorkFlow en WSS 3.0. |
Manejo de Errores en los WorkFlow en Sharepoint 2007
El último artículo que escribimos vimos como podemos colocar un formulario ASPX para el manejo de una tarea. Ahora vamos hablar como podemos manejar los errores que se produzcan en nuestro WorkFlow, atrapar los mismos y manejar el error. Para el manejo de errores contamos con una sección dentro de nuestros WorkFlow, donde podemos encontrar una actividad llamada “FaultHandlersActivity” la cual nos permitirá únicamente colocar actividades “FaultHandler”. Como podemos ver en la imagen 1 para acceder a la sección de manejo de errores podemos hacerlo a través de dos lugares, en la parte inferior contamos con tres iconos o presionando el botón derecho sobre el WorkFlow podemos acceder a la sección llamada “Workflow Exceptions”.
[Imagen 1]
Una vez que seleccionamos la opción “Ver manejo de Errores (View Fault Handlres)” accedemos a la sección donde podemos manejar los errores en nuestro WorkFlow como se puede ver en la imagen 2. [Imagen 2]
En esta sección lo que debemos hacer es colocar actividades “FaultHandler” tanta como errores queramos manejar. Funciona de la misma forma que la sentencia Catch del bloque Try donde podemos colocar tanos Catch como tipos de errores queremos manejar. Para ello lo que debemos hacer es arrastrar una actividad “FaulgHandler” desde la barra de herramientas y colocarla en la actividad “FaultHandlersActivity” correspondiente. Una vez colocada la actividad lo próximo que vamos hacer es configurar la propiedad “FaultType” donde vamos a cargar que tipo de error vamos a estar atrapando con la activad que acabamos de colocar. Acá podemos escribir la excepción que deseamos manejar con su namespace complete “System.Exception” o presionamos el botón que se encuentra en la propiedad para que se nos despliegue una nueva ventana como se puede ver en la imagen 3 para seleccionar el tipo de error que queremos manejar de todos nuestros posibles errores. [Imagen 3]
Una vez asociado el tipo de error que queremos manejar lo próximo que vamos hacer es colocar el código necesario para poder manejarlo. Para esto debemos seleccionar la actividad “” recién configurada, presionamos el botón derecho sobre la misma y seleccionamos la opción “Promover la propiedad (Promote Bindible Properties)” esto nos colocara una propiedad en nuestro WorkFlow donde podremos acceder a los valores de la misma tal cual lo hacemos en una excepción dentro del bloque Catch. Lo próximo que haremos es colocar una actividad de código “codeActivity” que es donde estaremos sacando todos los valores del error y cargándolos en una propiedad para mostrarla en la pantalla de resumen del WorkFlow dentro de Sharepoint. También vamos a colocar una actividad “LogToHistoryListActivity” para mostrar el error. Nuestro manejo de errores deberá quedar configurado como se puede ver en la imagen 4.
[Imagen 4]
Lo próximo que vamos hacer es configurar la actividad “LogToHistoryListActivity” para lo cual vamos a crear una propiedad pública del tipo String para cargarla en la misma. Para lo cual accedemos a la propiedad de dicha actividad y cargamos la propiedad “HistorryOutcome” con la propiedad recién cargada. Bien una vez configurada todas las actividades vamos a programar el evento de nuestra actividad de código, para lo cual accedemos a la actividad, vamos a la sección de evento y damos doble click sobre el evento “ExecuteCode”. En la sección 1 podemos ver el código correspondiente para esta actividad donde extraemos los valores del error y los cargamos en la propiedad pública que se estará mostrando en la actividad “LogToHistoryListActivity”
[Sección1] private void codeActivityHandleError_ExecuteCode(object sender, EventArgs e) { mError = faultHandlerException_Fault1.Message + " ----> " + faultHandlerException_Fault1.Source; } Bien vale la pena destacar también que en algunas actividades como por ejemplo “WhileActivity” podemos poner un manejo personalizado de error para la misma. Para ello, simplemente tenemos que seleccionar la actividad y acceder al menú contextual de propiedades presionando el botón derecho. Una vez en el menú seleccionamos la opción “View Fault Handlres” a lo cual se nos colocara una actividad “FaultHandlersActivity” donde podremos hacer el mismo manejo descripto anteriormente pero esta vez solo para esta actividad. En la imagen 5 vemos como queda esta opción activada dentro de la actividad WhileActivity.
[Imagen 5]
Una vez que se produzca un error en nuestro WorkFlow lo veremos en la pantalla del WorkFlow status correspondiente a la instancia que estamos corriendo. |