Valores únicos en una lista usando Callbacks en Sharepoint 2007 – UniqueColumn FieldType

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.
Entonces me acorde de un problema que habíamos tenido en un proyecto en el cual necesitábamos que una columna tuviera valores únicos y que los mismos no se pudieran repetir. Si bien las listas de Sharepoint tiene una semejanza con una tabla de base de datos, no lo son y hay muchas restricciones que  podemos establecer en una tabla que en una lista no, pero después el comportamiento es similar.  Haciendo un poco de investigación Santiago encontró una característica para Sharepoint 2007 basada en policitas que nos permite crear una  restricción sobre un campo de una lista y que chequea que los valores en los mismos sean únicos, acá les dejo el link a CodePlex donde encontraran esta solución y otras más.
Quise adentrarme un poco más en el tema y crear un tipo de columna para WSS 3.0 que me permitirá tener valores únicos en la misma, pero además quise que la validación fuera utilizándose la característica de Callbacks de Asp.Net 2.0 y que la validación se llevara en el servidor, pero sin tener que someter la página completa, como suele pasar en Sharepoint 2007 y WSS 3.0 cuando se validad cualquier restricción en campo de un elemento de la lista.
Lo que se debía hacer es fácil, tomar el valor que ingresa el usuario en el campo correspondiente y controlar que el mismo ya no estuviera almacenado en la lista, la idea estaba y la solución que debía desarrollar también y no era difícil de llevar a cabo, dato que para saber  si un valor para una columna determinada ya fue guardado en la lista o bien podemos recorrer todos sus elementos o bien podemos realizar una consulta con CAML, adivinen que, yo prefiero usar una consulta con CAML, es mas eficiente que recorrer todos los elementos ya que nunca vamos a saber cuántos elementos tendremos en la lista.
Comencé a crear mi tipo de columna (FieldType) personalizada, así que lo primero que hice fue abrir mi Visual Studio y generarme un proyecto nuevo utilizando las Plantillas de proyectos para Sharepoint. En este caso me genere un proyecto vacio y agregue una por una todas las referencias que necesitaba para llevar a cabo el desarrollo.
En la imagen 1 podrán apreciar como quedo el proyecto en Visual Studio una vez terminado el desarrollo.

[Imagen 1]
1_Proyecto_Visual_Studio_FieldType 

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.
Una vez que nuestro proyecto está configurado, lo primero que vamos hacer es crearnos un UserControl para darle la Interfaz de usuario que nuestro tipo de columna tendrá cuando se esté ingresando un nuevo elemento en la lista o para cuando estemos editando uno nuevo. Este control de usuario tiene la particularidad que todo nuestro código Asp.Net 2.0 y JavaScript deberá estar comprendido dentro del Tag “SharePoint:RenderingTemplate” para así crear un template determinado y que el mismo sea utilizado cuando se esta renderizando nuestro campo personalizado. En nuestro caso y como primera instancia hicimos un template basados en un TextBox para que el usuario ingrese los valores que desea guardar en la lista de Sharepoint. También colocamos un control RequiredFieldValidator para controlar que se ingrese un valor si el usuario selecciona que es requerida la columna cuando se está realizando la configuración de la lista. También hemos colocado varias funciones JavaScript para trabajar con la característica de CallBack de Asp.Net 2.0 y otras para realizar tareas adicionales de validar y notificar al usuario cuando el valor esta incorrecto. En la sección 1 podemos ver el control de usuario definido para nuestra columna personalizada.

[Sección 1]

<%@Control Language="C#" %>
<%@Assembly Name="Siderys.Blog.CustomField.UniqueColumnFieldType, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=711eed342842acee" %>
<%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>
<SharePoint:RenderingTemplate ID="txtUniqueColumn" runat="server"> <Template> <asp:TextBox runat="server" ID="txtValueField" CssClass="ms-long" MaxLength="255" /><br /> <asp:RequiredFieldValidator runat="server" ID="rfvTxtValueField"
ErrorMessage="Debe Ingresar un Valor" ControlToValidate="txtValueField" Enabled="false">
</
asp:RequiredFieldValidator> <script type="text/javascript"> var lBtnOks = new Array(); //funcion que recibe el resultado de la ejecución en el servidor. function OnCallbackComplete(arg, context) { if(arg != "") { alert(arg); SetColorBorderTextBox("#ff0000"); } else { SetColorBorderTextBox(""); EnabledSaveField(lBtnOks, false); } } //funcin que establece el color de forde del control function SetColorBorderTextBox(pColor) { lTextBox = GetTagFromIdentifierAndTitle("input","ValueField", ""); lTextBox.style.borderColor = pColor; } //funciona que habilita los botones de guardar function EnabledSaveField(pButtons, pState) { for(iterator = 0; iterator < pButtons.length; iterator++) { pButtons[iterator].disabled = pState; } } //funcion que llama al servidor para validar el texto ingresado por el usuario. function OnCallbackCheckField(param) { lBtnOks = GetTagFromIdentifierAndTitleArray("input","SaveItem","") EnabledSaveField(lBtnOks, true); CheckUniqeField(param, ''); } //funcion que devuelve un array de controles a partir del tag y el id function GetTagFromIdentifierAndTitleArray(tagName, identifier, title) { var len = identifier.length; var tags = document.getElementsByTagName(tagName); var j = 0; var lReturn = new Array(); for (var i=0; i < tags.length; i++) { var tempString = tags[i].id; if (tags[i].title == title && (identifier == "" ||
tempString.indexOf(identifier) == tempString.length - len)) { lReturn[j] = tags[i]; j++; } } return lReturn; } //funcion que devuelve un control a partir del tag y el id function GetTagFromIdentifierAndTitle(tagName, identifier, title) { var len = identifier.length; var tags = document.getElementsByTagName(tagName); for (var i=0; i < tags.length; i++) { var tempString = tags[i].id; if (tags[i].title == title && (identifier == "" ||
tempString.indexOf(identifier) == tempString.length - len)) { return tags[i]; } } return null; } </script> </Template> </SharePoint:RenderingTemplate>

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” .


[Sección 2]

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 = 
(RequiredFieldValidator)TemplateContainer.FindControl("rfvTxtValueField"); ClientScriptManager cm = Page.ClientScript; string cbReference = cm.GetCallbackEventReference(this, "arg", "OnCallbackComplete", ""); string callbackScript = "function CheckUniqeField(arg, context) {" + cbReference + "; }"; cm.RegisterClientScriptBlock(this.GetType(), "CheckUniqeField", callbackScript, true); if (txtValueField == null) { throw new Exception("El campo es nulo"); } if (ControlMode.Equals(SPControlMode.New)) { txtValueField.Text = string.Empty; rfvTxtValueField.Enabled = mRequired; } } public string GetCallbackResult() { return mReturnCheckFieldCallback; } public void RaiseCallbackEvent(string eventArgument) { CheckUniqueField(eventArgument); } private void CheckUniqueField(string pValueToCheck) { string lQueryString = "<Where><Eq><FieldRef Name='" + mInternalName + "' />
<Value Type='Text'>"
+ pValueToCheck + "</Value></Eq></Where>"; SPQuery lQuery = new SPQuery(); lQuery.Query = lQueryString; SPListItemCollection lResultQuery = mListField.GetItems(lQuery); if (lResultQuery.Count > 0) { mReturnCheckFieldCallback = "El valor: " + pValueToCheck +
" ya existe en la lista: " + mListField.Title; } } } }

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.


[Sección 3]

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)
:base(pFields,pFieldName, pDisplayName) { } public override string GetValidatedString(object value) { return value.ToString(); } public override Microsoft.SharePoint.WebControls.BaseFieldControl FieldRenderingControl { get { BaseFieldControl lUniqueColumn = new UniqueFieldColumn(this.Required,
this.InternalName, SPContext.Current.List); lUniqueColumn.FieldName = InternalName; return lUniqueColumn; } } } }

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.


[Sección 4]

<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, 
Siderys.Blog.CustomField.UniqueColumnFieldType, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=711eed342842acee</Field> <field name="UserCreatable">TRUE</field> <Field Name="Sortable">TRUE</Field> <Field Name="Filterable">TRUE</Field> </FieldType> </FieldTypes>

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.


[Sección 5]

iisreset /stop 

"%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe" -uf 
Siderys.Blog.CustomField.UniqueColumnFieldType "%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe" -
if binDebugSiderys.Blog.CustomField.UniqueColumnFieldType.dll copy /y FLDTYPES_UniqueColumn.xml
"%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEXML" xcopy /s /Y *.ascx
"
%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATECONTROLTEMPLATES" iisreset /start

 

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.


[Imagen 2]

2_Configuración_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.


[Imagen 3]

3_Validacion_Texto_Columna 

En la imagen 4 vemos el campo resaltado después de realizada la validación correspondiente del texto ingresado por el usuario.


[Imagen 4]

4_Campo_Resaltado_Columna_Texto 

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”.


[Imagen 5]

5_Lista_Valores_Ingresados 

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.


[Imagen 6]

6_Validacion_Campo_Obligatorio 

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.

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. 

Imagen 1
1_Add_Form_VS

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.
Vale la pena aclarar que nuestro formulario de inicialización solo será desplegado cuando iniciamos el WorkFlow de forma manual, en cambio si la se inicia el WorkFlow de forma automática el mismo no será desplegado.
En la imagen 2 vemos el WorkFlow que hemos creado, es un WorkFlow sencillo puesto que lo que queríamos compartir con Uds era la utilización de los formularios. 

Imagen 2
2_Workflow 
 

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.
Lo primero que vamos hacer es completar la directiva @Page del formulario Aspx para que el formulario quede correctamente asociado al código. Por defecto la directiva de pagina contiene el atributo “Inherits”, el cual indica cual es la clase que estará procesando toda la lógica de nuestro formulario, pero con esto solo no basta, dado que la clase que hace de Code Beheind se encuentra en el mismo Assembly generado para nuestro WorkFlow, así que lo que vamos hacer es agregarle a nuestro atributo “Inherits” cuál es el Assembly que contiene la clase que se tienen que invocar cuando el formulario sea cargado, para ello simplemente agregamos una “,” inmediatamente después del nombre de la clase y agregamos toda la información de nuestro Assembly, para lo cual tendrán que firmarlo, compilar y generarlo, si todavía no lo hicieron.  En la sección 1 vemos como quedaría el atributo “Inherits” en la directiva de pagina @Page.


Sección 1
Inherits="Siderys.Blog.Workflow.Forms.WFInstantiationForm, Siderys.Blog.Workflow.WorkflowInicializacionForm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=711eed342842acee"

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.

En la sección 3 vemos el código Asp.Net de nuestro formulario ASPX diseñado para el WorkFlow.
 

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. 

Imagen 3

3_Formulario_Sharepoint 
 

En la imagen 4 vemos como se ejecuta el control PeopleEditor de Sharepoint 2007 en nuestro formulario.

Imagen 4

4_People_Editor

Por último en la imagen 5 vemos la tarea asignada al usuario cargado en el control PeopleEditor. 

Imagen 5

5_Workflow_corriendo

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] 

 1_Acceso_Manejo_Errores 

 

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]

2_Manejo_Errores 

 

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]

 3_Excepciones 

 

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]

 4_Propiedades 

 

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]

5_WhileException 

 

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.