Small Bdc for Wss – Campos de Lista desde Base Datos

Hace tiempo que me estaba dando vuelta esta idea en la cabeza, la había empezado a codificar, pero por razones de tiempo la tuve que abandonar. En el día de ayer la retome y termine de implementar. La idea había surgido para un proyecto, que después no se llevo a cabo por diferentes motivos y a mí la idea me había gustado, dado que tener la posibilidad de cargar una columna de una lista con datos de una base de datos en Windows Sharepoint Services 3.0 me seducía mucho ya que esta funcionalidad (mucho más robusta y escalable) viene con la versión de MOSS 2007.

La vez pasada habíamos publicado un artículo sobre tener Campos Únicos en una lista de Sharepoint, nos permitía controlar que los datos que cargaba el usuario fueran diferentes entre sí. Basándonos en ese mismo artículo “Valores únicos en una lista” lo que hicimos acá fue llenar campos de una lista desde una base de datos. Para darle un poco más de aplicabilidad, no nos concentramos en solo un motor de Base de Datos, como podría haber sido SQL Server, si no que usamos OLEDB para realizar la conexión con la base y ejecución de la consulta. Como se trata de un campo de una lista, y lo que se busca es cargar el valor de la lista con registros almacenados en una base de datos, lo que hicimos fue simplemente contemplar la primera columna que se devuelve en el origen de datos, es decir que si se carga una consulta compleja como ser “Select * from tabla o Select Campo1, Campo2 from tabla” solo se tendrá en cuenta la primera columna del schema que devuelva la ejecución de la consulta.

Vale la pena realizar una aclaración antes de meternos de lleno al código, cuando estaba realizando la codificación del tipo de campo, me tope con un problema en la secuencia de cómo WSS y Sharepoint manejan el guardado de los valores del campo, la primera vez (cuando e crea) y cuando se modifica el mismo. Por eso, realizando un searching en google me tope con este artículo “Custom Field Type Properties” que explica que es lo que sucede y nos da una solución alternativa para salir del paso, la cual aplique con éxito. Cuando se dispara el método OnAdded del campo que estamos creando la instancia del campo que se nos pasa es diferente a la que teníamos en determinado momento (cuando lo estábamos configurando) y los valores de las custom properties utilizadas para almacenar la cadena de conexión y consulta select se pierden, es por ello que tuvimos que utilizar un Diccionario (Dictionary) estático y almacenar los valores de forma provisoría, para que cuando se dispara el método OnAdded tuviéramos los valores para guardarlos en nuestras custom properties correspondientes.

Hecha esta aclaración, lo que vamos hacer en primer lugar es crearnos un proyecto de Sharepoint vacio, podríamos utilizar un proyecto de biblioteca de clases, de no tener instalado la extensiones de WSS para visual studio 2005, sigo trabajando con visual studio 2005, pero próximamente me estaré migrando a visual studio 2008 dado que me estoy armando una maquina virtual con todo el ambiente, Windows 2008, Sql Server, Sharepoint con SP1 y visual studio 2008.

Una vez tenemos el proyecto creado vamos a configurar el mismo, lo que vamos hacer es editar las propiedades y colar un nombre para el Assembly que se va a generar, un espacio de nombre por defecto para todas las clases que se creen y vamos a firmar el mismo utilizando un arhivo .snk, en mi caso este archivo se llama “Siderys.snk”

Una vez configurado el proyecto, lo primero que vamos agregar al mismo es el archivo Xml que define a nuestro tipo de campo. En este archivo que lo llamaremos “fldtypes_BdcWss.xml” indicaremos cual es el nombre, descripción, la clase que lo implemente, el Assembly que contiene esa clase, el tipo de campo base que vamos a estar utilizando y algunas propiedades más que se pueden ver en la sección 1. También crearemos dos propiedades personalizadas, ocultas, que serán las que van almacenar la cadena de conexión con la base de datos y la consulta que vamos a estar ejecutando.

[Sección 1]

<FieldTypes>
    <FieldType>
        <Field Name="TypeName">BdcWss</Field>
        <Field Name="TypeDisplayName">Bdc Wss</Field>
        <Field Name="TypeShortDescription">Bdc Wss</Field>
        <Field Name="ParentType">Choice</Field>
        <Field Name="FieldTypeClass">Siderys.Blog.CustomField.BdcWss, Siderys.Blog.CustomField.BdcWss, 
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> <Field Name="FieldEditorUserControl">/_controltemplates/BdcWssEditFieldType.ascx</Field> <PropertySchema> <Fields> <Field ID="StringConnection" Hidden="TRUE" Name="StringConnection"
DisplayName="StringConnection" Type="Text" ></Field> <Field ID="StringSelect" Hidden="TRUE" Name="StringSelect"
DisplayName="StringSelect" Type="Text" ></Field> </Fields> </PropertySchema> </FieldType> </FieldTypes>

Una vez el archivo Xml está creado, lo próximo que vamos hacer es crear el primer control de usuario. Este control de usuario es el que se utilizara cuando estemos creando un nuevo elemento en la lista y nos desplegara un DrodDownList con los datos cargados para que el usuario seleccione uno de los valores recogidos de la base de datos. En la sección 2 vemos el código de nuestro ASCX llamado “BdcWssFieldType.ascx” y en la sección 3 vemos el código fuente de la clase que implementa la lógica del control.

[Sección 2]

<%@ Control Language="C#"%>
<%@Assembly Name="Siderys.Blog.CustomField.BdcWss, 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="BdcWss" runat="server"> <Template> <asp:DropDownList ID="ddlResultSelect" runat="server"> </asp:DropDownList> </Template> </SharePoint:RenderingTemplate>

[Sección 3]

using System;
using System.Data;
using System.Configuration;
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;
using Microsoft.SharePoint.WebControls;
using System.Data.OleDb;

/// <summary>
/// Summary description for BdcWssFieldType
/// </summary>
namespace Siderys.Blog.CustomField
{
    public class BdcWssFieldType : BaseFieldControl
    {
        protected DropDownList ddlResultSelect;
        private OleDbConnection mConnection = null;
        private OleDbCommand mCommand = null;
        private OleDbDataReader mReader = null;

        public BdcWssFieldType()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        protected override string DefaultTemplateName
        {
            get
            {
                return "BdcWss";
            }
        }

        public override object Value
        {
            get
            {
                return ddlResultSelect.SelectedItem.Text;
            }

            set
            {
                EnsureChildControls();
                ddlResultSelect.Items.FindByText(value.ToString()).Selected = true;
            }
        }

        public override void Focus()
        {
            EnsureChildControls();
            ddlResultSelect.Focus();
        }

        private void FillDropDownListFromDataBase()
        {
            try
            {
                //obtengo los valores para la conexion y la consulta almacenados en las propiedades del campo.
                string lConnectionString = this.Field.GetCustomProperty("StringConnection").ToString();
                string lSelectString = this.Field.GetCustomProperty("StringSelect").ToString();

                mConnection = new OleDbConnection(lConnectionString);
                mConnection.Open();
                mCommand = new OleDbCommand(lSelectString, mConnection);
                mReader = mCommand.ExecuteReader();
                ddlResultSelect.Items.Clear();
                while (mReader.Read())
                {
                    ddlResultSelect.Items.Add(new ListItem(mReader.GetString(0)));
                }               
            }
            catch (OleDbException ex)
            {
                throw ex;
            }
            finally
            {
                mReader.Close();
                mConnection.Close();
            }
        }

        protected override void CreateChildControls()
        {
            if (this.Field == null) return;
            base.CreateChildControls();
            if (this.ControlMode == Microsoft.SharePoint.WebControls.SPControlMode.Display)
                return;
            ddlResultSelect = (DropDownList)(TemplateContainer.FindControl("ddlResultSelect"));
            if (ddlResultSelect == null)
            {
                throw new ArgumentException("DropDowList es nulo.......");
            }

            if (!Page.IsPostBack)
            {
                try
                {
                    FillDropDownListFromDataBase();
                }
                catch (Exception ex)
                {
                    ddlResultSelect.Items.Add(ex.Message);
                }
            }
        }
    }
}

Lo próximo que vamos a realizar es un control de usuario que será utilizado cuando estemos creando el campo en la lista y nos permitirá ingresar la cadena de conexión a la base de datos y probar la misma. También nos permitirá ingresar la consulta que queremos ejecutar y probarla, donde se cargara un DropDownList de ejemplo con los datos sacados de la base de datos. En la sección 4 vemos el código del control ASCX llamado “BdcWssEditFieldType.ascx” y en la sección 5 vemos el código fuente de la clase que implementa la lógica del control de usuario.

[Sección 4]

<%@ Control Language="C#" AutoEventWireup="false" Inherits="Siderys.Blog.CustomField.BdcWssEditFieldType, 
Siderys.Blog.CustomField.BdcWss, Version=1.0.0.0, Culture=neutral, PublicKeyToken=711eed342842acee" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="~/_controltemplates/InputFormControl.ascx" %> <%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="~/_controltemplates/InputFormSection.ascx" %> <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint,
Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %> <wssuc:InputFormSection runat="server" id="BdcSection" Title="Bdc - Configuracion Base de Datos"> <template_inputformcontrols> <wssuc:InputFormControl runat="server" LabelText="Ingrese una cadena de conexion y una consulta select."> <Template_Control> <table> <tr class="ms-authoringcontrols"> <td> <asp:Label ID="lblConnectionString" runat="server" Text="Connection String">
</
asp:Label> </td> </tr> <tr> <td> <asp:TextBox ID="txtConnectionString" runat="server" TextMode="MultiLine"
Rows="5" Columns="40"></asp:TextBox> </td> </tr> <tr> <td> <asp:Button ID="cmdTestConnection" runat="Server" Text="Test Connection" /> </td> </tr> <tr> <td> <asp:Label ID="lblTestConnection" runat="server" ForeColor="Red"></asp:Label> </td> </tr> <tr class="ms-authoringcontrols"> <td> <asp:Label ID="lblSelect" runat="server" Text="Select Command"></asp:Label> </td> </tr> <tr> <td> <asp:TextBox ID="txtSelect" runat="server" TextMode="MultiLine" Rows="5"
Columns="40"></asp:TextBox> </td> </tr> <tr> <td> <asp:Button ID="cmdTestSelect" runat="Server" Text="Test Select" /> </td> </tr> <tr> <td> <asp:Label ID="lblTestSelect" runat="server" ForeColor="Red"></asp:Label> </td> </tr> <tr> <td> <asp:DropDownList ID="ddlSelectResult" runat="server" Visible=false> </asp:DropDownList> </td> </tr> </table> </Template_Control> </wssuc:InputFormControl> </template_inputformcontrols> </wssuc:InputFormSection>

[Sección 5]

using System;
using System.Data;
using System.Configuration;
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;
using Microsoft.SharePoint.WebControls;
using System.Data.OleDb;

/// <summary>
/// Summary description for BdcWssEditFieldType
/// </summary>
namespace Siderys.Blog.CustomField
{
    public class BdcWssEditFieldType : UserControl, IFieldEditor
    {
        protected TextBox txtConnectionString = null;
        protected Button cmdTestConnection = null;
        protected Label lblTestConnection = null;
        protected TextBox txtSelect = null;
        protected Button cmdTestSelect = null;
        protected Label lblTestSelect = null;
        protected DropDownList ddlSelectResult = null;
        private OleDbConnection mConnection = null;
        private OleDbCommand mCommand = null;
        private OleDbDataReader mReader = null;

        public BdcWssEditFieldType()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            //agrego el metodo para manejar el evento click
            this.cmdTestConnection.Click += new EventHandler(cmdTestConnection_Click);
            //agrego el metodo para manejar el evento click
            this.cmdTestSelect.Click += new EventHandler(cmdTestSelect_Click);
        }

        #region IFieldEditor Members

        public bool DisplayAsNewSection
        {
            get
            {
                return true;
            }
        }

        public void InitializeWithField(SPField field)
        {
            BdcWss lBdc = (BdcWss)field;
            if (lBdc != null)
            {
                EnsureChildControls();
                string lStringConnection = lBdc.GetCustomProperty("StringConnection").ToString();
                string lStringSelect = lBdc.GetCustomProperty("StringSelect").ToString();

                txtConnectionString.Text = lStringConnection;
                txtSelect.Text = lStringSelect;
            }
        }

        public void OnSaveChange(SPField field, bool isNewField)
        {
            BdcWss lBdcWss = (BdcWss)field;
            if (isNewField)
            {
                lBdcWss.UpdateConnectionStringProperty(this.txtConnectionString.Text);
                lBdcWss.UpdateSelectStringProperty(this.txtSelect.Text);
            }
            else
            {
                //Almaceno la cadena de conexion.
                lBdcWss.ConeectionString = this.txtConnectionString.Text;
                //Almaceno la consulta Select.
                lBdcWss.SelectString = this.txtSelect.Text;
            }
        }

        #endregion

        protected void cmdTestConnection_Click(object sender, EventArgs e)
        {
            try
            {
                mConnection = new OleDbConnection(this.txtConnectionString.Text);
                mConnection.Open();
                lblTestConnection.Text = "Se conecto correctamente";
            }
            catch (OleDbException ex)
            {
                lblTestConnection.Text = ex.Message;
            }
            finally
            {
                mConnection.Close();
            }
        }

        protected void cmdTestSelect_Click(object sender, EventArgs e)
        {
            try
            {
                mConnection = new OleDbConnection(this.txtConnectionString.Text);
                mConnection.Open();
                mCommand = new OleDbCommand(this.txtSelect.Text, mConnection);
                mReader = mCommand.ExecuteReader();
                ddlSelectResult.Visible = true;
                ddlSelectResult.Items.Clear();
                while (mReader.Read())
                {
                    ddlSelectResult.Items.Add(new ListItem(mReader.GetString(0)));
                }
                lblTestSelect.Text = "Se ejecuto correctamente";
            }
            catch (OleDbException ex)
            {
                lblTestSelect.Text = ex.Message;
            }
            finally
            {
                mReader.Close();
                mConnection.Close();
            }
        }
    }
}

Por último nos queda implementar la clase que representara a nuestro tipo de campo, esta clase extiende de “SPFieldText” como ya comentamos en el artículo anterior y dado que lo que vamos a guardar es un dato simple, seleccionado por el usuario no necesitamos implementar ningún otro tipo de campo más complejo. En la sección 6 vemos el código de la clase llamada “BdcWss”.

[Sección 6]

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace Siderys.Blog.CustomField
{
    public class BdcWss : SPFieldText
    {
        private string mConnectonString = string.Empty;
        private string mSelectString = string.Empty;

        private static Dictionary<int, string> mDicConnectionStriong = new Dictionary<int, string>();
        private static Dictionary<int, string> mDicSelectString = new Dictionary<int, string>();

        public BdcWss(SPFieldCollection pFields, string pFieldName)
            : base(pFields, pFieldName)
        {
            
        }

        public BdcWss(SPFieldCollection pFields, string pTypeName, string pDisplayName)
            : base(pFields, pTypeName, pDisplayName)
        {
            
        }

        public void Init()
        {
            mConnectonString = this.GetCustomProperty("StringConnection").ToString();
            mSelectString = this.GetCustomProperty("StringSelect").ToString();
        }

        private int GetContextId
        {
            get
            {
                return SPContext.Current.GetHashCode();
            }
        }
        public string ConeectionString
        {
            get
            {
                return mDicConnectionStriong.ContainsKey(GetContextId) ? mDicConnectionStriong[GetContextId] : mConnectonString;
            }
            set
            {
                mConnectonString = value;
            }
        }

        public string SelectString
        {
            get
            {
                return mDicSelectString.ContainsKey(GetContextId) ? mDicSelectString[GetContextId] : mSelectString;
            }
            set
            {
                mSelectString = value;
            }
        }

        public void UpdateConnectionStringProperty(string pValue)
        {
            mDicConnectionStriong[GetContextId] = pValue;
        }

        public void UpdateSelectStringProperty(string pValue)
        {
            mDicSelectString[GetContextId] = pValue;
        }

        public override void OnAdded(SPAddFieldOptions op)
        {
            base.OnAdded(op);
            Update();
        }

        public override void Update()
        {
            this.SetCustomProperty("StringConnection", ConeectionString);
            this.SetCustomProperty("StringSelect", SelectString);
            base.Update();
            if (mDicConnectionStriong.ContainsKey(GetContextId))
            {
                mDicConnectionStriong.Remove(GetContextId);
            }
            if (mDicSelectString.ContainsKey(GetContextId))
            {
                mDicSelectString.Remove(GetContextId);
            }
        }

        public override Microsoft.SharePoint.WebControls.BaseFieldControl FieldRenderingControl
        {
            get
            {
                Microsoft.SharePoint.WebControls.BaseFieldControl BdcWssControl = new BdcWssFieldType();
                BdcWssControl.FieldName = InternalName;
                return BdcWssControl;
            }
        }
    
    }
}

Una vez implementado, lo que nos resta es compilarlo e instalar todo en nuestro servidor de Sharepoint 2007, para lo cual en mi caso me cree un archivo .Bat para instalar todo. El Assembly lo debemos colocar en la GAC y los demás archivos debemos colocarlo en la carpeta “CONTROLTEMPLATES” donde está instalado Sharepoint. En la sección 7 podemos ver el código del archivo .bat creado para ejecutar la instalación.

[Sección 7]

iisreset /stop
"%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe" -uf Siderys.Blog.CustomField.BdcWss
"%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe" 
-if binDebugSiderys.Blog.CustomField.BdcWss.dll copy /y fldtypes_BdcWss.xml "%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEXML" xcopy /s /Y BdcWssFieldType.ascx "%CommonProgramFiles%Microsoft Sharedweb server
extensions12TEMPLATECONTROLTEMPLATES" xcopy /s /Y BdcWssEditFieldType.ascx "%CommonProgramFiles%Microsoft Sharedweb server
extensions12TEMPLATECONTROLTEMPLATES" iisreset /start pause

Bien una vez instalado todo, lo que nos resta es probar nuestro nuevo tipo de columna. Para testearla me voy a conectar a una base de datos creada en SQL Server 2005, con una tabla llamada productos, en la imagen 1 podemos ver dicha tabla cargada con datos de pruebas.

[Imagen 1]

1_TablaProductos

Lo que debemos hacer ahora es ir a una lista de Sharepoint y acceder a la configuración de la misma. Una vez en la sección de configuración lo que vamos hacer es crear una nueva columna en la misma, para ello seleccionamos el link “Crear Columna” . Una vez en dicha página lo que vamos hacer es colocar un nombre a esta nueva columna, en mi caso la llame “Productos”, en la imagen 2 vemos la pantalla para crear una nueva columna en la lista y en la lista de campos disponibles vemos nuestra nueva columna, cargamos el nombre y seleccionamos dicho tipo de campo.

[Imagen 2]

2_Configuracion_Columna_1

Una vez que seleccionamos el tipo de campo que queremos, la página se vuelve a cargar y nos muestra la sección codificada por nosotros en el control de usuario “BdcWssEditFieldType.ascx” donde vamos a carga la cadena de conexión y la consulta que queremos ejecutar. La imagen 3 nos muestra estos campos cargados y probados y lo próximo que debemos realizar es guardar nuestra nueva columna, para lo cual vamos a presionar el botón “Aceptar” de la página.

[Imagen 3]

3_Configuracion_Columna_2

Una vez guarda la nueva columna en la lista, lo próximo que debemos de realizar es crear un nuevo elemento en la misma y seleccionar un valor del DropDownList correspondiente. En la imagen 4 vemos el formulario para dar de altas nuevos elementos en la lista, con la nueva columna cargada.

[Imagen 4]

4_Nuevo_Elemnto_Lista

Una vez cargados los valores del nuevo elemento, presionamos el botón “Aceptar” para guardar los mismos en la lista. En la imagen 5 vemos la página de todos los elementos de la lista y en nuevo valor ingresado en la misma.

[Imagen 5]

5_Elemento_Vista_All

También quiero destacar que el comportamiento del nuevo campo es similar a todos los campos en una lista de Sharepoint, es decir que podemos editar la configuración y modificar en cualquier momento los valores de conexión y la consulta que estamos ejecutando. Como también podemos editar cualquier elemento creado y cambiar el valor almacenado en la lista por cualquier otro valor de la base de datos. En la imagen 6 vemos la edición del elemento recientemente creado y el nuevo valor seleccionado.

[Imagen 6]

6_Valor_Modificado

Espero que este artículo les sea de utilidad, en lo que respecta al uso que le podemos dar, creo que podemos tener valores en nuestras listas desde un origen de datos y sobre WSS 3.0 principalmente y también lo podemos utilizar en Sharepoint 2007 si no queremos configurar la característica BDC que este nos brinda. De todas formas vale la pena aclarar nuevamente que esto no intenta ser un sustituto de esa gran característica que nos provee MOSS, si no una versión de un tipo de campo que nos permite mostrar datos desde una base de datos.

Les pido disculpas por lo extenso de este artículo, pero hacerlo de una forma más corta hubiera implicado tener que dejar cosas afuera.

Aquí puede descargar el código fuente de este articulo.

Controles de Usuario (ASCX) en Sharepoint 2007 – SmartPart

Se que hace muy difícil a veces el desarrollo de formularios o WebParts para representar lo que tenemos que hacer. Por eso, en nuestros desarrollos usamos muchas veces controles de usuario en Sharepoint y los cargamos con una WebPart hecha por nosotros. Pero hace tiempo que venimos usando SmartPart para Sharepoint, que nos permite cargar cualquier control de usuario realizado en Sharepoint, sin tener la necesidad de tener que crear una WebPart, simplemente instalamos esta característica y desarrollamos los controles de usuarios y los instalamos en el servidor. La instalación se puede llevar a cabo con código fuente, colocando la clase en la misma carpeta o mediante un Assembly que puede estar en el directorio de ejecución o en la GAC.

Es un proyecto de CodePlex que me pareció muy bueno y simplifica mucho el desarrollo sobre Sharepoint. Los aliento a utilizarlo y verán como es de mucha utilidad.

Muchas gracias a todos los que participan en el desarrollo de este tipo de herramientas ya que su colaboración es enorme.

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.

 

WorkFlow Sharepoint 2007- Formulario Aprobación ASPX

El último artículo que escribimos hablamos de cómo crear un formulario de asociación utilizando wss3workflow, herramienta que se puede descargar de CodePlex.

Cuando trabajamos con WorkFlow sobre Sharepoint tenemos que interactuar con el usuario para que el mismo tome una decisión en nuestro flujo de trabajo, casi siempre estas interacciones las codificamos en formularios Infopath y asociamos los mismo a nuestros flujos de trabajo. Bien, sabemos que desarrollar los formularios en Infopath es una tarea medianamente sencilla, dependerá de lo  complejo del mismo. Estos formularios nos permiten tomar las decisiones de los usuarios e informarle a nuestro flujo de trabajo la acción realizada por el usuario.

Pero estos formularios dependen de una característica de Moss, Forms Services, esta característica permite convertir el formulario Infopath en una simple pagina Web, con lo cual el usuario que esta interactuando con nuestro WorkFlow no necesita tener instalado Infopath en su máquina y así podemos tener nuestros preciados formularios cargados en el Internet Explorer.

Pero qué pasaría si nos encontramos en un ambiente diferente, supongamos que estamos desarrollando un componente de negocio para nuestro cliente, pero el mismo será montado sobre Windows Sharepoint Services y no sobre Moss, acá nuestro preciado formulario en Infopath no será del todo útil. Para este ambiente (WSS) deberemos codificar nuestros formularios en ASPX y asociarlos a nuestros flujos de trabajo, tarea que no es tan sencilla de realizar ni tan intuitiva como uno desearía, dado que deberemos codificar todo nosotros.

Pues bien acá es donde entra la herramienta wss3workflow , la cual nos brinda un template  para Visual Studio para crear formularios ASPX para nuestros flujos de trabajos.  En la imagen 1 se ve como se puede agregar un nuevo formulario de tarea a nuestro flujo de trabajo e insertarlo en nuestro proyecto.

 [Imagen 1]

1_VisualStudioTaskForm

 

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

En la imagen 2 vemos el WorkFlow creado para este ejemplo, el mismo es sencillo y cuenta con las actividades necesarias para la creación de una tarea dentro de Sharepoint 2007.

 

 [Imagen 2]

2_WorkFlow_Creado

 

Una vez que tenemos creado nuestro WorkFlow, lo próximo que vamos hacer es crear nuestro formulario ASPX, nos posicionamos arriba de nuestro proyecto (en mi caso coloque una carpeta en el mismo para centralizar los formularios) presionamos el botón derecho, agregar, nuevo elemento y nos tendría que aparecer una ventana similar a la mostrada en la imagen 1. Seleccionamos la plantilla TaskForm, le colocamos un nombre y presionamos el botón agregar.

Una vez completado el proceso se tuvieron que haber agregados dos archivos, un con extensión ASPX y otro con extensión ASPX.cs. Todos los que hemos trabajados con Asp.Net en algún momento conocemos bien estos dos archivos, uno contiene la declaración de nuestros controles y el otro el código asociado a los mismos.

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, 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.ApprovalTaskFormWss, 
Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskForm, 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 la sección 3 vemos el código Asp.Net de nuestro formulario ASPX diseñado para el WorkFlow, el solo contiene dos botones, una para aprobar y el otro para rechazar la tarea asignada al usuario.

 

[Sección 3]

 

 

<asp:Content ID=“_mainContentrunat=“serverContentPlaceHolderID=“PlaceHolderMain>

<spw:FormDigest ID=“_formDigestrunat=“server/>

<asp:Button ID=“btnApprovalTaskrunat=“serverText=“Aprobar Tarea
OnClick=“btnApprovalTask_Click/>

<asp:Button ID=“btnRechazarTarearunat=“serverText=“Rechazar Tarea
OnClick=“btnRechazarTarea_Click/>

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

Nosotros vamos agregar los manejadores de los dos botones (Aprobar y Rechazar) 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 “CommitTask()” y en el método que maneja el evento del botón Cancelar invocaremos al método “CancelTask()”.

Por último vamos a invocar la sobre cargar (override) del método OnPreRender implementado en nuestra clase base. Este método lo que realiza en la inserción de una serie de elementos ocultos en nuestro formulario donde se cargaran todos los datos asociados al WorkFlow.

Bien, ya hemos implementado nuestro formulario, en la sección 5 podemos ver el código completo.

 

[Sección 5]

using System;

using System.Collections;

using CodeCounsel.SharePoint.Workflow;

using Microsoft.SharePoint;

namespace Siderys.Blog.Workflow.Forms

{

    public partial class ApprovalTaskFormWss : TaskForm

    {

        protected override void OnPreRender(EventArgs e)

        {

            base.OnPreRender(e);

        }

        protected override void GetTaskCustomData(Hashtable container)

        {

            

        }

        protected void btnApprovalTask_Click(object sender, EventArgs e)

        {

            CommitTask();

        }

        protected void btnRechazarTarea_Click(object sender, EventArgs e)

        {

            CancelTask();

        }

    }

}

 

 

Para asociar nuestro formulario a la tarea lo que vamos a tener que hacer a través de un Content Type. Este Content Type estará relacionado con nuestro WorkFlow por intermedio de la propiedad “TaskListContentTypeId” del archivo WorkFlow.xml y que ahora estaremos viendo.

La única característica que tendrá este Content Type es la ruta a nuestro formulario ASPX, pero si tuviéramos que agregarles nuevas columnas o columnas del sitio lo podríamos hacer utilizando el elemento “<FieldRefs>” del Schema, donde podremos agregar todas las columnas que necesitemos.

Una vez insertado el nuevo archivo XML lo próximo que vamos hacer es colocar el elemento “Elements” y dentro de este elemento vamos a declarar el Content Type utilizando el elemento “ContentType”.  En la sección 6 vemos el código completo para la definición del Content Type y al archivo lo hemos llamado “TaskFormContentType.xml”, ahora veremos las propiedades que debemos configurar.

 

[Sección 6]

 

 

<Elements xmlns=”http://schemas.microsoft.com/sharepoint/ " >

 <ContentType ID=“0x01080100C5B9C785D13F43f38645772B1C5A152AName=“WorkFlowTaskAspxFormGroup=“Siderys.Blog.WorkflowDescription=“Task Form in Aspx FileVersion=“0Hidden=“FALSE>

<XmlDocuments>

<XmlDocument NamespaceURI=“ http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”

&lt;FormUrls xmlns=“ http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”

&lt;New>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</New>

<Display>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</Display>

 <Edit>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</Edit>

</FormUrls>

</XmlDocument>

</XmlDocuments>

</ContentType>

</Elements>

 

Lo primero que tenemos que hacer es establecerle un ID, este Id debe comenzar con el siguiente código 0x01080100 y después continuar con un Guid sin los “-“. Esto es un requisito muy importante dado que esta serie de números identifica al Content Type dentro de la herencia de Content Type de Sharepoint 2007 y lo marca como un Content Type para tareas de WorkFlow, en la siguiente pagina del MSDN podemos ver la jerarquía completa de los Content Type.

Lo cargamos un nombre para identificarlo,  indicamos el grupo donde queremos que se cargue dentro de los Content Types del sitio, una descripción, una versión e indicamos si el mismo estará visible.

Por último debemos indicar la ruta al formulario que creamos, para lo cual introducimos un elemento “XmlDocuments” y ahí establecemos la ruta para las opciones “New”, “Display” y “Edit”.

Lo próximo que vamos hacer es crear nuestro formulario WorkFlow.xml y lo vamos asociar al Content Type creado recientemente utilizando la propiedad “TaskListContentTypeId” como mencionamos.  No vamos a explicar este archivo en profundidad puesto que en artículos anteriores ya hemos profundizado en el mismo.  Entonces lo que debemos hacer una vez que el archivo fue creado, vamos a establecer el valor para la propiedad “TaskListContentTypeId” cargándole el ID del Content Type previamente creado. En la sección 7 vemos el código completo para nuestra definición del WorkFlow.xml.

 

[Sección 7]

 

<Elements xmlns=“ http://schemas.microsoft.com/sharepoint/”>

 <Workflow

Name=“Simple Approval WorkFlow Task Form AspxDescription=“Simple Approval WorkFlow Task Form AspxId=“4937ACF5-37DC-41b8-95AB-F2340DF6C2D1CodeBesideClass=“Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskFormCodeBesideAssembly=“Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskForm, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=711eed342842aceeStatusUrl=“_layouts/WrkStat.aspxTaskListContentTypeId=“0x01080100C5B9C785D13F43f38645772B1C5A152A> <Categories/> <MetaData> <StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl> </MetaData> </Workflow> </Elements>

 

 Por último lo vamos a crear nuestro archivo Feature.xml donde estaremos declarando nuestro nuevo Feature para Sharepoint 2007 y estaremos agregando los dos archivos anteriormente construidos. En la sección 8 pueden ver cómo queda este archivo.

 

[Sección 8]

 <Feature  Id=“9DF221FB-20D8-4691-AAFD-DA6BF446FE2A

Title=“Simple Approval WorkFlow Task Form Aspx

Description=“Simple Approval WorkFlow Task Form Aspx

Version=“12.0.0.0

Scope=“Site

xmlns=“ http://schemas.microsoft.com/sharepoint/”>

<ElementManifests>

<ElementManifest Location=“TaskFormContentType.xml/>

<ElementManifest Location=“workflow.xml/>

</ElementManifests>

<Properties>

<Property Key=“GloballyAvailableValue=“true/>

WorkFlow Sharepoint 2007- Formulario Aprobación ASP X

El último artículo que escribimos hablamos de cómo crear un formulario de asociación utilizando wss3workflow, herramienta que se puede descargar de CodePlex.

Cuando trabajamos con WorkFlow sobre Sharepoint tenemos que interactuar con el usuario para que el mismo tome una decisión en nuestro flujo de trabajo, casi siempre estas interacciones las codificamos en formularios Infopath y asociamos los mismo a nuestros flujos de trabajo. Bien, sabemos que desarrollar los formularios en Infopath es una tarea medianamente sencilla, dependerá de lo  complejo del mismo. Estos formularios nos permiten tomar las decisiones de los usuarios e informarle a nuestro flujo de trabajo la acción realizada por el usuario. Pero estos formularios dependen de una característica de Moss, Forms Services, esta característica permite convertir el formulario Infopath en una simple pagina Web, con lo cual el usuario que esta interactuando con nuestro WorkFlow no necesita tener instalado Infopath en su máquina y así podemos tener nuestros preciados formularios cargados en el Internet Explorer. Pero qué pasaría si nos encontramos en un ambiente diferente, supongamos que estamos desarrollando un componente de negocio para nuestro cliente, pero el mismo será montado sobre Windows Sharepoint Services y no sobre Moss, acá nuestro preciado formulario en Infopath no será del todo útil. Para este ambiente (WSS) deberemos codificar nuestros formularios en ASPX y asociarlos a nuestros flujos de trabajo, tarea que no es tan sencilla de realizar ni tan intuitiva como uno desearía, dado que deberemos codificar todo nosotros. Pues bien acá es donde entra la herramienta wss3workflow , la cual nos brinda un template  para Visual Studio para crear formularios ASPX para nuestros flujos de trabajos.  En la imagen 1 se ve como se puede agregar un nuevo formulario de tarea a nuestro flujo de trabajo e insertarlo en nuestro proyecto.

 [Imagen 1]

1_VisualStudioTaskForm

 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 tarea. En la imagen 2 vemos el WorkFlow creado para este ejemplo, el mismo es sencillo y cuenta con las actividades necesarias para la creación de una tarea dentro de Sharepoint 2007.  

 [Imagen 2]

2_WorkFlow_Creado 

Una vez que tenemos creado nuestro WorkFlow, lo próximo que vamos hacer es crear nuestro formulario ASPX, nos posicionamos arriba de nuestro proyecto (en mi caso coloque una carpeta en el mismo para centralizar los formularios) presionamos el botón derecho, agregar, nuevo elemento y nos tendría que aparecer una ventana similar a la mostrada en la imagen 1. Seleccionamos la plantilla TaskForm, le colocamos un nombre y presionamos el botón agregar. Una vez completado el proceso se tuvieron que haber agregados dos archivos, un con extensión ASPX y otro con extensión ASPX.cs. Todos los que hemos trabajados con Asp.Net en algún momento conocemos bien estos dos archivos, uno contiene la declaración de nuestros controles y el otro el código asociado a los mismos. 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, 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.ApprovalTaskFormWss,Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskForm, 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 la sección 3 vemos el código Asp.Net de nuestro formulario ASPX diseñado para el WorkFlow, el solo contiene dos botones, una para aprobar y el otro para rechazar la tarea asignada al usuario.   

[Sección 3]

<asp:Content ID=“_mainContentrunat=“serverContentPlaceHolderID=“PlaceHolderMain>
<spw:FormDigest ID=“_formDigestrunat=“server/>
<asp:Button ID=“btnApprovalTaskrunat=“serverText=“Aprobar TareaOnClick=“btnApprovalTask_Click/>
<asp:Button ID=“btnRechazarTarearunat=“serverText=“Rechazar TareaOnClick=“btnRechazarTarea_Click/>
</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.

Nosotros vamos agregar los manejadores de los dos botones (Aprobar y Rechazar) 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 “CommitTask()” y en el método que maneja el evento del botón Cancelar invocaremos al método “CancelTask()”. Por último vamos a invocar la sobre cargar (override) del método OnPreRender implementado en nuestra clase base. Este método lo que realiza en la inserción de una serie de elementos ocultos en nuestro formulario donde se cargaran todos los datos asociados al WorkFlow. Bien, ya hemos implementado nuestro formulario, en la sección 4 podemos ver el código completo.  

[Sección 4]

using System;

using System.Collections;

using CodeCounsel.SharePoint.Workflow;

using Microsoft.SharePoint;

namespace Siderys.Blog.Workflow.Forms

{

    public partial class ApprovalTaskFormWss : TaskForm

    {

        protected override void OnPreRender(EventArgs e)

        {

            base.OnPreRender(e);

        }

        protected override void GetTaskCustomData(Hashtable container)

        {

            

        }

        protected void btnApprovalTask_Click(object sender, EventArgs e)

        {

            CommitTask();

        }

        protected void btnRechazarTarea_Click(object sender, EventArgs e)

        {

            CancelTask();

        }

    }

}

Para asociar nuestro formulario a la tarea lo que vamos a tener que hacer a través de un Content Type. Este Content Type estará relacionado con nuestro WorkFlow por intermedio de la propiedad “TaskListContentTypeId” del archivo WorkFlow.xml y que ahora estaremos viendo. La única característica que tendrá este Content Type es la ruta a nuestro formulario ASPX, pero si tuviéramos que agregarles nuevas columnas o columnas del sitio lo podríamos hacer utilizando el elemento “<FieldRefs>” del Schema, donde podremos agregar todas las columnas que necesitemos. Una vez insertado el nuevo archivo XML lo próximo que vamos hacer es colocar el elemento “Elements” y dentro de este elemento vamos a declarar el Content Type utilizando el elemento “ContentType”.  En la sección 5 vemos el código completo para la definición del Content Type y al archivo lo hemos llamado “TaskFormContentType.xml”, ahora veremos las propiedades que debemos configurar.  

[Sección 5] 

<Elements xmlns=”http://schemas.microsoft.com/sharepoint/ " >
 <ContentType ID=“0x01080100C5B9C785D13F43f38645772B1C5A152AName=“WorkFlowTaskAspxFormGroup=“Siderys.Blog.WorkflowDescription=“Task Form in Aspx FileVersion=“0Hidden=“FALSE>

<XmlDocuments>

<XmlDocument NamespaceURI=“ http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”

&lt;FormUrls xmlns=“ http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url”

&lt;New>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</New>

<Display>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</Display>

 <Edit>_layoutsSimpleApprovalFileFormsApprovalFormApprovalTaskFormWss.aspx</Edit>

</FormUrls>

</XmlDocument>

</XmlDocuments>

</ContentType>

</Elements> 

Lo primero que tenemos que hacer es establecerle un ID, este Id debe comenzar con el siguiente código 0x01080100 y después continuar con un Guid sin los “-“. Esto es un requisito muy importante dado que esta serie de números identifica al Content Type dentro de la herencia de Content Type de Sharepoint 2007 y lo marca como un Content Type para tareas de WorkFlow, en la siguiente pagina del MSDN podemos ver la jerarquía completa de los Content Type. Lo cargamos un nombre para identificarlo,  indicamos el grupo donde queremos que se cargue dentro de los Content Types del sitio, una descripción, una versión e indicamos si el mismo estará visible. Por último debemos indicar la ruta al formulario que creamos, para lo cual introducimos un elemento “XmlDocuments” y ahí establecemos la ruta para las opciones “New”, “Display” y “Edit”. Lo próximo que vamos hacer es crear nuestro formulario WorkFlow.xml y lo vamos asociar al Content Type creado recientemente utilizando la propiedad “TaskListContentTypeId” como mencionamos.  No vamos a explicar este archivo en profundidad puesto que en artículos anteriores ya hemos profundizado en el mismo.  Entonces lo que debemos hacer una vez que el archivo fue creado, vamos a establecer el valor para la propiedad “TaskListContentTypeId” cargándole el ID del Content Type previamente creado. En la sección 6 vemos el código completo para nuestra definición del WorkFlow.xml.  

[Sección 6] 

<Elements xmlns=“ http://schemas.microsoft.com/sharepoint/”>
 <Workflow
Name=“Simple Approval WorkFlow Task Form AspxDescription=“Simple Approval WorkFlow Task Form AspxId=“4937ACF5-37DC-41b8-95AB-F2340DF6C2D1CodeBesideClass=“Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskFormCodeBesideAssembly=“Siderys.Blog.Workflow.SimpleApprovalWorkFlowTaskForm,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=711eed342842aceeStatusUrl=“_layouts/WrkStat.aspxTaskListContentTypeId=“0x01080100C5B9C785D13F43f38645772B1C5A152A>

<Categories/>

<MetaData>

<StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>

</MetaData>

</Workflow>

  </Elements> 

 Por último lo vamos a crear nuestro archivo Feature.xml donde estaremos declarando nuestro nuevo Feature para Sharepoint 2007 y estaremos agregando los dos archivos anteriormente construidos. En la sección 7 pueden ver cómo queda este archivo.  

[Sección 7]

 <Feature  Id=“9DF221FB-20D8-4691-AAFD-DA6BF446FE2ATitle=“Simple Approval WorkFlow Task Form AspxDescription=“Simple Approval WorkFlow Task Form AspxVersion=“12.0.0.0Scope=“Sitexmlns=“ http://schemas.microsoft.com/sharepoint/”>

<ElementManifests>

<ElementManifest Location=“TaskFormContentType.xml/>

<ElementManifest Location=“workflow.xml/>

 </ElementManifests>

<Properties>

<Property Key=“GloballyAvailableValue=“true/>

</Properties>

</Feature> 

Una vez creado todos los archivos, lo que vamos hacer es instalar todo en Sharepoint 2007, para lo cual pueden utilizar el archivo .bat que se agrego a nuestro proyecto cuando creamos el mismo o bien pueden hacerlo a mano. En este caso nosotros vamos a utilizar el archivo .bat y el formulario lo vamos colocar en la carpeta _layouts de Sharepoint, para lo cual podemos modificar este archivo para que nos cree la carpeta necesaria y después nos copie los mismo. En la sección 8 pueden ver parte del archivo Install.bat y las modificaciones realizadas para que nos copie el archivo ASPX.

[Sección 8] 

echo Copying the feature... 
 rd /s /q "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATEFEATURESSimpleApprovalWorkFlowTaskForm" 
 mkdir "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATEFEATURESSimpleApprovalWorkFlowTaskForm" 
 mkdir "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATELAYOUTSSimpleApprovalFileFormsApprovalForm" 
 copy /Y feature.xml  "%programfiles%Common FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESSimpleApprovalWorkFlowTaskForm" 
 copy /Y workflow.xml  "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATEFEATURESSimpleApprovalWorkFlowTaskForm" 
 copy /Y TaskFormContentType.xml  "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATEFEATURESSimpleApprovalWorkFlowTaskForm" 
 copy /Y Forms*.aspx "%programfiles%Common FilesMicrosoft Sharedweb server extensions12 TEMPLATELAYOUTSSimpleApprovalFileFormsApprovalForm" 

  Una vez instalado, lo que vamos hacer es activar nuestro Feature en Sharepoint, si el mismo esta instaldo, desactívenlo y actívenlo de nuevo, para asegurarse que el Content Type se cree correctamente. En la imagen 3 vemos la lista de Feature y nuestro Feature activado nuevamente.

[Imagen 3]

  3_Features_Sharepoint

Si accedemos a los Content Type el sitio podremos ver nuestro Content Type creado, en la imagen 4 vemos la lista de Content Type del sitio.  

[Imagen 4]

   4_Content_Types

En la imagen 5 podremos ver la tarea asignada al usario.  

[Imagen 5]

   5_Tarea_Asignada

En la imagen 6 podemos ver nuestro formulario cargado una vez que el usuario selecciono la tarea asignada.  

[Imagen 6]

   6_Formulario_Task_Aspx

Por último en la imagen 7 vemos la tarea completada por el usuario.  

[Imagen 7]

   7_Tarea_Completada

En el próximo artículo esteremos hablando de cómo se manejan las excepciones en nuestros WorkFlow y nos esteramos adentrando en algunas actividades específicas.

WorkFlow Sharepoint 2007 – Formulario de asociación ASPX

Se pensaron que nos habíamos ido, no estamos acá nuevamente, después de pasar un mes de Marzo bastante movido en lo referente a trabajo y en lo personal con algunas complicaciones que por suerte superamos, nos hicimos hueco para retomar nuestro blog y escribir este artículo.

El último artículo que escribimos hablamos de cómo hacer un “WorkFlow de aprobación para Sharepoint 2007 paso a paso” donde hablamos del ambiente que necesitamos y describimos paso a paso la creación de un WorkFlow para Sharepoint utilizando Visual Studio. Este WorkFlow utilizaba un formulario creado con Infopath 2007 para la aprobación de tareas, el cual nos simplifica mucho utilizar Infopath para crear formularios para interactuar con los usuarios.

Después de publicar este artículo y someterlo a la crítica de mi buen amigo Haaron Gonzalez (gracias Haaron por tus palabras de aliento para que siga escribiendo, como ves sigoJ) el cual paso con buenas calificaciones, me puse a trabajar en la creación de un WorkFlow también para Sharepoint 2007, pero esta vez utilizando formularios ASPX de  Asp.Net y no formularios construidos con Infopath 2007. Hablando con Haaron, me comento que existía una herramienta, wss3workflow, publicada en CodePlex, que permitía crear formularios ASPX para nuestros WorkFlow sin la necesidad de hacerlo todo a pulmón. Esta herramienta, una vez instalada, nos brinda una serie de plantillas en el Visual Studio para agregar formularios,  cuando seleccionamos agregar un nuevo elemento en nuestro proyecto. Para este proyecto, yo me cree una carpeta llamada “AssociationForm” para colocar el formulario de asociació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 “Association Form”, colocaremos un nombre y presionamos agregar.

Imagen 1

 1_AsosiationForm_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 asociación y en el próximo artículo vernos como crear un formulario de tarea.

El formulario de asociación nos sirve para cargar información adicional a nuestro WorkFlow cuando el mismo está siendo asociado a la lista. 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.

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_Creado

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, si estuviéramos bajo el contexto de una aplicación normal de Asp.Net donde nuestro archivo cs queda asociado al formulario no tendríamos problemas, pero acá el contexto es un poco diferente, 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.WorkShop.AssociationForm.AssociationFormSimpleApprovalWF, Siderys.Blog.Workflow.WorkShop.SimpleApprovalWorkflowAssosiationForm, 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. El formulario que vamos a crear es sencillo, contendrá un Asp.Net TextBox , dos Button y un RequiredFieldValidator. La caja de texto se utilizara para cargar el usuario a quien se le asignara la tarea de aprobación, en este ejemplo utilizamos una simple caja de texto, pero podríamos haber utilizado el control de Sharepoint PeopleEditor, para seleccionar usuarios. En el artículo “Como usar el  control PeopleEditor de Sharepoint 2007” encontraran como utilizar fácilmente.
En la sección 3 vemos el código Asp.Net de nuestro formulario ASPX diseñado para el WorkFlow.

 

Sección 3

<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" 
Inherits="Siderys.Blog.Workflow.WorkShop.AssociationForm.AssociationFormSimpleApprovalWF, 
Siderys.Blog.Workflow.WorkShop.SimpleApprovalWorkflowAssosiationForm, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=711eed342842acee" %>
                                                                                                                 
<%@ Register TagPrefix="spw" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, 
PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %>
<%@ Register TagPrefix="spu" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Utilities" %>
<asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server"> <span>Simple Approval Workflow Association Forms</span> </asp:Content> <asp:Content ID="Content2" contentplaceholderid="PlaceHolderPageTitleInTitleArea" runat="server"> <asp:Label runat="server" ID="Label1" Text="Simple Approval Workflow Association Forms" /> </asp:Content> <asp:Content id="_mainContent" runat="server" ContentPlaceHolderId="PlaceHolderMain" > <table> <tr> <td> <asp:Label id="lblApprovador" runat="server" /> </td> <td> <asp:TextBox id="txtApprovador" runat="server" /> <asp:RequiredFieldValidator ID="rfvApprovador" runat="server"
ControlToValidate="txtApprovador">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td> <asp:Button id="btnAceptar" runat="server" text="Aceptar" OnClick="btnAceptar_Click" /> </td> <td> <asp:Button id="btnCancel" runat="server" text="Cancel" OnClick="btnCancel_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 “AssociationForm” que se encuentra en el Assembly  “CodeCounsel.SharePoint.Workflow.dll” instalado cuando instalamos el Add-In para el Visual Studio y que se encuentra en la GAC.  

Al abrir esta clase nos encontramos que la misma tiene un constructor por defecto y la sobrecarga del método GetAssocationCustomData(). Este método es el utilizado por el formulario para enviar la información al WorkFlow una vez asociado. Este método, solo nos permite enviar un String, pero podríamos crear un objeto complejo, serializarlo a XML y envíalo como un String al WorkFlow, a lo cual lo único que deberíamos hacer en el WorkFlow es tomar dicha información y nuevamente convertirla en nuestro Objeto complejo para tomar sus valores, de misma forma que  Infopath interactúa con el WorkFlow.

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 “CommitAssocation()” el cual terminara de asociar toda la información al WorkFlow. Este método esta declarado en la clase “AssociationForm” y lo que hace es realizar la asociación del WorkFlow a la lista correspondiente y en el método que maneja el evento del botón Cancelar invocaremos al método “CancelAssociation()” que redirecciona al usuario nuevamente a la página de propiedades de la lista. En la imagen 3 vemos el código del método “”, para lo cual inspeccionamos el Assembly utilizando la herramienta Reflector.

Imagen 3

3_Reflector 

Por último vamos a invocar la sobre cargar (override) del método OnPreRender implementado en nuestra clase base. Este método lo que realiza en la inserción de una serie de elementos ocultos en nuestro formulario donde se cargaran todos los datos asociados al WorkFlow, guid de la lista, nombre del WorkFlow,  Id de la lista de tareas, etc. Esta información nos podría ser de utilidad por si queremos realizar alguna tarea previa a la asociación del WorkFlow. En la imagen 4 vemos el código de este método implementado en nuestra clase base.

Imagen 4

4_OnPreRender 

Bien, ya hemos implementado nuestro formulario, en la sección 5 podemos ver el código completo.

Sección 5

using System;

using CodeCounsel.SharePoint.Workflow;

using System.Web.UI.WebControls;

 

namespace Siderys.Blog.Workflow.WorkShop.AssociationForm

{

    public partial class AssociationFormSimpleApprovalWF : CodeCounsel.SharePoint.Workflow.AssociationForm

    {

        protected TextBox txtApprovador;

        protected override void OnPreRender(EventArgs e)

        {

            base.OnPreRender(e);

        }

 

        protected override string GetAssocationCustomData()

        {

            return txtApprovador.Text;

        }

 

        protected void btnAceptar_Click(object sender, EventArgs e)

        {

             CommitAssocation();

        }

        protected void btnCancel_Click(object sender, EventArgs e)

        {

            CancelAssociation();

        }

    }

}

 

Por último lo que debemos hacer es modificar el archivo WorkFlow.xml, para relacionar nuestro formulario con el WorkFlow, para ello utilizaremos el atributo “AssociationUrl” de elemento “Workflow” dentro del archivo xml. El formulario en nustro 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 6 vemos el código completo del archivo WorkFlow.xml .

Sección 6

  <Workflow
     Name="SimpleApprovalWorkflow Asociation Forms"
     Description="SimpleApprovalWorkflow Asociation Forms"
     Id="737F046D-8715-4803-811E-111EDAF2F65D"
     CodeBesideClass="Siderys.Blog.Workflow.WorkShop.SimpleApprovalWorkflowAssosiationForm"
     CodeBesideAssembly="Siderys.Blog.Workflow.WorkShop.SimpleApprovalWorkflowAssosiationForm, 
Version=1.0.0.0, Culture=neutral, PublicKeyToken=711eed342842acee"
AssociationUrl="_layoutsSimpleApprovalFileFormsAssociationFormAssociationFormSimpleApprovalWF.aspx"> <Categories/> <MetaData> <StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl> </MetaData> </Workflow> </Elements>

Lo que nos resta es tomar la información cargada en nuestro formulario dentro del WorkFlow, para lo cual utilizando la propiedad “AssociationData” que se encuentra dentro de las WorkflowProperties podemos acceder a la información introducida en nuestro formulario. En la sección 7 vemos el código del evento “Invoked” que se dispara cuando se inicia el WorkFlow.

 

Sección 7

        private void onWorkflowActivated_Invoked(object sender, ExternalDataEventArgs e)

        {

            mUserApprover = onWorkflowActivated_WorkflowProperties.AssociationData;

        }

En la imagen 5 podemos ver nuestro formulario en ejecución cuando estamos asociando un WorkFlow

 

Imagen 5

 5_AsosciadonForm_Ejecucion

En próximos artículos seguiremos mostrando como utilizar este Add-In en nuestros WorkFlow y vamos a estar empezando a trabajar sobre la plataforma 2008 con Sharepoint 2007 y así ver las ventajas que se han introducido en el nuevo Visual Studio para trabajar con Sharepoint.

WorkFlow de aprobación para Sharepoint 2007 paso a paso

Hace días que estoy viendo cómo armar este artículo y mostrar cómo hacer un WorkFlow para Sharepoint 2007 utilizando Visual Studio 2005, las extensiones de WorkFlow Foundation y las extensiones para Windows Sharepoint Services, pensé mostrar un WorkFlow de aprobación para varios usuarios, donde la tarea de aprobación fuese asignada a mas de un aprobador, pero analizando me di cuenta que eso podía ser el final de todo esto y entonces decidí crear una serie de artículos que mostraran como hacer un WorkFlow paso a paso y luego ir añadiendo funcionalidades.

Para poder realizar este WorkFlow vamos a necesitar tener un ambiente de desarrollo montado de la siguiente forma, en mi caso tengo una maquina virtual con todo instalado y es la que utilizo para desarrollar soluciones para Sharepoint 2007.

1)      Windows 2003 cualquier versión con SP1 instalado.

2)      SQL Server 2005 con SP1 instalado. También podemos utilizar SQL Express 2005

3)      Framework 3.0. Necesitamos las extensiones de WorkFlow Foundation.

4)      Sharepoint 2007.

5)      Visual Studio 2005 cualquier versión.

6)      Extensiones de Windows Sharepoint Services

7)      Infopath 2007 para realizar los formularios.

Bien una vez tenemos el ambiente montado y configurado, lo que demos hacer es ponernos a construir el WorkFlow. Para lo cual abrimos el Visual Studio 2005 y creamos un nuevo proyecto basándonos en la plantilla instalada. El WorkFlow que vamos a crear en este caso es uno secuencial, en un próximo artículo vamos a crear un WorkFlow de estado. En la imagen 1 vemos la pantalla de creación de proyectos de Visual Studio donde seleccionamos el templete y le ponemos un nombre.

Imagen 1

1_Visual_Studio_Template

Una vez que tenemos creado el proyecto el Visual Studio se tiene que ver como en la imagen 2, el proyecto creado, nuestro WorkFlow creado y al abrir el mismo nos encontramos con una única actividad que nos crea el template de proyecto, la actividad  “OnWorkflowActivated” la cual es invocada cuando se inicia el WorkFlow y la podemos utilizar para inicializar propiedades, conectarnos algún repositorio de datos para extraer información.

Imagen 2
2_Workflow_Abierto

Lo que debemos hacer ahora es configurar nuestro WorkFlow, lo primero que vamos hacer es crear un “CorrelationToken”, esto es necesario para que la actividad quede vinculada a nuestro WorkFlow y el árbol de mensajes del WorkFlow quede cargado. Para realizar esto seleccionamos la actividad “OnWorkflowActivated” y en el cuadro de propiedades escribimos un nombre para la propiedad “CorrelationToken” y seleccionamos cual es la actividad padre, en este caso seleccionamos el propio WorkFlow debido a que es nuestra primera actividad, en la imagen 3 vemos como nos debería quedar cargada.

Imagen 3
3_Correlation_Token_OnActiveted

Lo próximo que debemos configurar es la propiedad “WorkflowProperties” de la actividad “OnWorkflowActivated”, la cual nos permitirá acceder a datos de donde está corriendo el WorkFlow como ser el Site, Web, List, Item al que está asociado, etc. Para ellos seleccionamos la actividad, y en el cuadro de propiedades vamos a ver la propiedad “WorkflowProperties” y presionamos el botón “…” que aparece para que se nos despliegue una ventana donde vamos a crear el valor para la misma. Esta ventana tiene dos pestañas, en una podemos seleccionar las propiedades que ya estén creadas como lo muestra la imagen 4 y en la otra pestaña podemos crear una nueva propiedad como lo muestra la imagen 5. Nosotros vamos a crear una nueva propiedad, para lo cual introducimos un nombre y presionamos OK. En este caso vamos a crear un atributo de nuestro WorkFlow y no una propiedad.

Imagen 4
4_WorkFlowProperties

Imagen 5
5_WorkFlowProperties_Bind

Una vez creado el atributo el mismo quedara cargado en la actividad y ahora vamos a crear nuestro WorkFlow de aprobación. Para ello vamos a crear 3 actividades que son de las extensiones de Windows Sharepoint Services, estas actividades, nos permitirán crear una nueva tarea de aprobación para el o los usuarios que se designen, esperar que el mismo acceda a la tarea, la aprueba o rechace y por ultimo darla por completada, las actividades que usaremos son las siguientes:

1)      CreateTask Activity: Esta actividad creara una tarea para un usuario determinado en la lista de tareas asociada al WorkFlow.

2)      OnTaskChange Activity: Esta tarea se quedara esperando que el usuario modifique la misma, o sea la apruebe o la rechace.

3)      CompleteTask Activity: Esta tarea completara la misma de forma automática.

Lo que haremos es colocar una actividad adicional en nuestro WorkFlow, esta actividad actuara como contenedora de las tres actividades mencionadas anteriormente y esto se debe a que las 3 tareas deben tener el mimo “CorrelationToken”. Así que vamos a agregar una “SecuentialActivity” para englobar a las otras 3 actividades mencionadas, la imagen 6 nos muestra cómo nos debería quedar nuestro WorkFlow después de agregar todas las actividades descriptas.

Imagen 6
6_WorkFlow

La siguiente tabla muestra cómo debemos configurar las propiedades de cada una de las actividades recién agregadas:

CreateTask:

Name: CreateTaskApproval

CorrelationToken: CreateTaskCorrelationToken

OwnerActivityName: ApprovalActivity

TaskID: CreateTaskApproval_TaskId. Para configurar esta propiedad utilizaremos la ventana mostrada en las imagenes 4 y 5. Crearemos un atributo nuevo y lo asignaremos al WorkFlow.

TaskProperties: CreateTaskApproval_TaskProperties. Para configurar esta propiedad también utilizaremos la ventana mostrada en las imágenes 4 y 5.  Crearemos un atributo nuevo y lo asignaremos al WorkFlow.

OnTaskChange:

Name: TaskChangedApproval

CorrelationToken: CreateTaskCorrelationToken. Seleccionamos el mismo creado para la actividad “CreateTask” puesto que deben ser iguales. Esto se debe a que es una unidad atómica de trabajo.

AfterProperties: TaskChangedApproval_AfterProperties. Para configurar esta propiedad utilizaremos la ventana mostrada en las imágenes 4 y 5, pero en vez de crear un atributo vamos a crear un property. Esta propiedad nos servirá para recuperar el valor modificado de una columna de la lista..

BeforeProperties: TaskChangedApproval_BeforeProperties. Para configurar esta propiedad utilizaremos la ventana mostrada en las imágenes 4 y 5, pero en vez de crear un atributo vamos a crear un property. Esta propiedad nos servirá para recuperar el valor original de una columna de la lista.

TaskID: CreateTaskApproval_TaskId. Para configurar esta propiedad utilizaremos la ventana mostrada en las imagenes 4 y 5, pero en vez de crear una nueva, lo que haremos es seleccionar el atributo creado en la actividad “CreateTask, dado que estas actividades deben tener el mismo TaskId para que queden asociadas.

CompleteTask:

Name: CompleteTaskApproval

CorrelationToken: CreateTaskCorrelationToken. Seleccionamos el mismo creado para la actividad “CreateTask” puesto que deben tener el mismo. Esto se debe a que es una unidad atómica de trabajo.

TaskID: CreateTaskApproval_TaskId. Para configurar esta propiedad utilizaremos la ventana mostrada en las imagenes 4 y 5, pero en vez de crear una nueva, lo que haremos es seleccionar el atributo creado en la actividad “CreateTask, dado que estas actividades deben tener el mismo TaskId para que queden asociadas.

TaskOutcome: Cargaremos un texto que se mostrara en la ventana del WorkFlow una vez que el mismo termine.

Una vez configuradas todas las propiedades del WorkFlow el mismo nos tiene que quedar como se muestra en la imagen 7 y ahora lo que vamos hacer colocar el código para asociar al usuario.

Imagen 7
7_WorkFlow_Configurado

Para este ejemplo lo que vamos hacer es siempre asignarle el mismo usuario, así que cada vez que se cree una tarea se le asignara un único usuario. Podemos colocar el código necesario para sacar los aprobadores de cualquier repositorio de datos o cargarlos cuando el WorkFlow se instancie. Para este primer artículo vamos a mostrar como cargarlo, en los siguientes vamos a construir un formulario que nos permitirá seleccionar los usuarios aprobadores al iniciarse el WorkFlow. Todas las actividades contienen una serie de eventos que nosotros podemos programar y que los mismos serán disparados cuando las tareas sean invocadas. En este ejemplo solo vamos a codificar el evento “MethodInvoking” de la actividad “CreateTask” y en la imagen 8 vemos como asociar el método que va a manejar dicho evento.

Imagen 8
8_MethodInvoking

En la sección 1 vemos el código para el método asociado al evento “MethodInvoking” recién creado y en el cual cargamos todas las propiedades de la tarea.

Sección 1

private void CreateTaskApproval_MethodInvoking(object sender, EventArgs e)

{

 CreateTaskApproval_TaskId = Guid.NewGuid();

 CreateTaskApproval_TaskProperties.TaskType = 0;

CreateTaskApproval_TaskProperties.AssignedTo = "sidom\fabiani";

CreateTaskApproval_TaskProperties.Title = "Tarea de aprobación para Fabian";

CreateTaskApproval_TaskProperties.Description = "Fabian debe aprobar esta tarea";

CreateTaskApproval_TaskProperties.SendEmailNotification = false;

}

Una vez que tenemos creado nuestro WorkFlow lo que tenemos que hacer es crear un formulario para aprobar o rechazar la tarea. En esta versión lo vamos hacer utilizando Infopath y el formulario lo único que tendrá será un botón para aprobar y otro para rechazar la tarea asignada al usuario. En la imagen 9 vemos el formulario creado.

Imagen 9
9_Formulario_Infopath

Una vez creado lo que tenemos que hacer es configurar el botón “Aprobar”, para lo cual vamos hacer una regla para enviar a nuestro Workflow que la tarea fue aprobada. En la imagen 10 vemos como la regla creada para el botón “Aprobar”.

 

Imagen 10
10_ReglaFormularios_Infopath

 

Por último vamos a configurar las opciones del formulario y a publicarlos en nuestro servidor de Sharepoint para que quede disponibl. Para ello en Infopath accedemos a las opciones del formulario en la opción “Herramientas” y aplicamos las siguientes configuraciones:

1) En la sección Examinar seleccionamos el idioma de nuestro formulario, en este caso Ingles puesto que el Sharepoint está en Ingles.

2) En la sección Compatibilidad seleccionamos el Checkbox “Diseñe una plantilla de formulario que se pueda abrir en el Explorador” esto nos permitirá abrir este formulario Infopath como una página Web y no necesitaremos tener Infopath instalado en la maquina cliente y en el cuadro de texto “Especifique la dirección URL…” cargamos la dirección URL del servicio Web de FormsServices, tal cual se muestra así (debe sustituir los valores correctos correspondientes a su servidor) http://<servidor>:<puerto>/_vti_bin/FormsServices.asmx. Una vez que aceptemos los cambios realizados Infopath intentara conectarse con el servidor Sharepoint y de ser necesario le deberemos proporcionar las credenciales correctas para que se conecte y nos valide el formulario.

Una vez configurado, lo que debemos hacer es publicarlo, para realizar esto accedemos a la opción de publicar del formulario Infopath presionando “Archivo” y después “Publicar”. Se nos abrirá un Wizard para realizar la publicación, en el primer paso vamos a seleccionar la opción “En un servidor de Sharepoint con o sin Infopath Forms Services”, en el segundo paso cargamos la URL de nuestro portal Sharepoint (http://<servidor>:<puerto>/), en el tercer paso seleccionamos al última opción del mismo “Plantilla de formulario aprobada por el administrador”, en el cuarto paso seleccionamos una ruta de red en el servidor donde colocaremos el formulario (En este punto vamos a colocar el formulario dentro de la carpeta “FeatureFiles “ que se encuentra en “DeploymentFiles” dentro de la raíz de nuestro proyecto y le vamos asignar un nombre, para nuestro ejemplo le asignamos “FormApprovalTaskBlog.xsn”. Nos debería quedar así “\<servidor><ruta al proyecto> DeploymentFilesFeatureFilesFormApprovalTaskBlog.xsn”), en el quinto paso damos siguiente (No vamos a crear ninguna columna personalizada para asociar a nuestro WorkFlow) y por último presionamos el botón publicar.

Ahora debemos configurar los archivos XML de configuración y realizar nuestro deploy del Workflow. Cada vez que creamos un proyecto utilizando el template de WorkFlow se nos agregan una serie de archivos XML y Bat que nos facilitaran la tarea de realizar la instalación de nuestro Workflow. En la imagen 11 vemos la estructura de archivos creada por el template de proyectos una vez creado el mismo y que a continuación detallaremos.

Imagen 11
11_Estructura_Archivos

Feature.xml: este archivo se utiliza para crear un feature en nuestro portal Sharepoint y asociar el Workflow, el cual podremos utilizar en cualquier sitio de la colección de sitios.

Workflow.xml: Este archivo declara el WorkFlow y asocia los formularios Infopath creados al mismo.

PostBuildActions.bat: Este bat es invocado por Visual Studio y realiza la instalación del Feature en nuestro Sharepoint, lo activa, instala el Assembly en al GAC y realiza un IISRESET para que los cambios sean tenidos en cuenta. Lo único que debemos hacer es editarlo y cambiar la dirección URL “http://localhost” por la dirección URL de nuestro sitio “http://<servidor>:<puerto>/”

Unistall.bat: Este archivo desinstala y desactiva el feature de nuestro portal. Lo único que debemos hacer es editarlo y cambiar la dirección URL “http://localhost” por la dirección URL denuestro sitio “http://<servidor>:<puerto>/”

Todos los demás valores utilizados por estos dos archivos Bat son pasados en el Visual Studio una vez que se termine la compilación del proyecto. En la imagen 12 vemos la pantalla de propiedades del proyecto en el Visual Studio. Si queremos que el WorkFlow sea instalado una vez terminemos de complicar debemos indicarlos colocando la palabra “DEPLOY” en el cuadro de texto “Post-Build envent command line” y si no queremos que el WorkFlow sea instalado después que terminamos de complicar colocamos la palabra “NODEPLOY” para seguir trabajando en el mismo y no se produzca una instalación cada vez que compilamos.

Imagen 12
12_Porpiedades_VisualStudio

En la sección 2 vemos el código completo para el archivo “feature.xml” y en la sección 3 vemos el código completo para el archivo “workflow.xml”.

Sección 2

<Feature Id="A93B9917-3038-4aea-8C5F-01A0F78DA4A6"

Title="Simple Approval WorkFlow"

Description="This feature is a simple approval workflow."

Version="12.0.0.0"

Scope="Site"

ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"

ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver" xmlns="http://schemas.microsoft.com/sharepoint/">

<ElementManifests>

<ElementManifest Location="workflow.xml" />

</ElementManifests>

<Properties>

<Property Key="GloballyAvailable" Value="true" />

<!– Value for RegisterForms key indicates the path to the forms relative to feature file location –>

<!– if you don’t have forms, use *.xsn –>

<Property Key="RegisterForms" Value="*.xsn" />

</Properties>

</Feature>

El archivo feature.xml define la funcionalidad dentro de Sharepoint y asocia el archivo “workflow.xml” que es el que tiene toda la definición del WorkFlow. Básicamente nosotros debemos colocarle un guid para el ID, un nombre y una descripción, las demás propiedades las podemos dejar como nos la genera el template de proyecto del Visual Studio.

Sección 3

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<Workflow

Name="Simple Approval WorkFlow"

Description="This simple approval workflow."

Id="45229254-E7C9-4846-8D49-D28AF6A88315"

CodeBesideClass="Siderys.Blog.SimpleApprovalWorkFlowInfopathForms"

CodeBesideAssembly="Siderys.Blog.Workflow.SimpleApprovalWorkFlowInfopathForms. SimpleApprovalWorkFlow, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a82cbe50b3eab825"

StatusUrl="_layouts/WrkStat.aspx"

TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"

ModificationUrl="_layouts/ModWrkflIP.aspx">

<Categories/>

<!– Tags to specify InfoPath forms for the workflow; delete tags for forms that you do not have –>

<MetaData>

<Task0_FormURN>urn:schemas-microsoft-com:office:infopath:FormApprovalTaskBlog:-myXSD-2008-01-04T13-20-14</Task0_FormURN>

<StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>

</MetaData>

</Workflow>

</Elements>

En el archivo “workflow.xml” define al WorkFlow que acabamos de crear y en este archivo debemos cargar el Assembly del WorkFlow (el Assembly debe estar firmado con un Strong Name) para ello usamos la propiedad “CodeBesideAssembly”, en la propiedad “CodeBesideClass” declaramos el espacio de nombre y la clase donde esta codificado y en la propiedad “Task0_FormURN” declaramos el formulario Infopath creado, para obtener el valor para dicha propiedad abrimos el formulario Infopath utilizando Infopath 2007 en las propiedades del mismo copiamos el valor que se encuentra en la caja de texto “Id”. Podemos declarar varios formularios Infopath, tantos como necesitemos, lo único que tenemos que variar de esta propiedad es el nombre, por ejemplo si quisiéramos colocar otro formulario la propiedad debería llamarse “Task1_FormURN” y así sucesivamente. Para asociar el formulario a nuestro WorkFlow lo hacemos utilizando la propiedad “TaskType” que se encuentra en la propiedad “TaskProperties” creada para nuestra actividad “CreateTaskApproval”, en la sección 1 podemos ver la asignación de esta propiedad.

Una vez instalado nuestro WorkFlow lo que debemos hacer es asociarlo a la lista que deseamos tener aprobación de elementos. Para configurar el WorkFlow debemos acceder a la configuración de WorkFlow de la lista y presionar “Agregar WorkFlow”. En la pantalla de configuración seleccionamos nuestro WorkFlow de la lista disponible de WorkFlows, le colocamos un nombre, la lista de tareas donde vamos a estar creando las tareas, la lista histórica de movimientos y como queremos que el WorkFlow se inicialice. En la imagen 13 vemos como debería quedarnos configurado una vez cargado todos los valores y al terminar presionamos el botón Ok que asociara nuestro WorkFlow a la lista.

Imagen 13
13_WorkFlow_Asociado

Ahora tenemos asociado el WorkFlow, lo próximo que debemos hacer es crear un elemento en la lista y el WorkFlow se dispara dado que lo configuramos para que se inicie al crear un elemento. En la imagen 14 vemos como después de crear un elemento en la lista la instancia del WorkFlow está en progreso.

Imagen 14
14_Instancia_Corriendo

Una vez creado podemos acceder a la lista de tareas y nos encontramos con una nueva tarea creada y asignada al usuario que cargamos dentro del WorkFlow, al seleccionar la misma se nos abrirá el formulario Infopath asociado para que aprobemos o rechacemos la tarea, en la imagen 15 vemos el formulario abierto.

Imagen 15
15_Formulario_Infopath

Cuando el WorkFlow termina podemos acceder a la historia de la ejecución, para eso seleccionamos el link “Completado”, que se encuentra en el elemento que acabamos de aprobar y accedemos a la pantalla de estado de la instancia del WorkFlow que acabamos correr. En dicha pantalla, ver imagen 16, veremos cómo se fue ejecutando nuestro WorkFlow de aprobación y veremos el mensaje cargado en la propiedad “TaskOutcome” de la actividad CompteTask.

Imagen 16
16_Estado_WorkFlow

La idea de este artículo fue mostrar como armar un WorkFlow simple de aprobación, paso a paso y explicando cómo vamos creando y configurando el mismo. En el próximo articulo vamos hablar de cómo podemos utilizar formularios de aprobación hechos en Asp.Net, para llevar a cabo esto, vamos a utilizar un AddIn que me recomendó un amigo Haaron Gonzalez y que se puede descargar de Codeplex y buscaremos no depender de Forms Services e Infopath.

Estadísticas Acceso y Uso en Sharepoint 2007 – WebPart

En el último artículo vimos como se podía configurar las estadísticas de Acceso y Uso en Sharepoint 2007 y vimos como Sharepoint nos da acceso a los informes creados en la administración de la colección de sitios.
Sharepoint nos provee clases para acceder a la información almacenada en la base de datos y que nosotros podemos utilizar para presentar de forma mas amigable todos los registros sucedidos.
La siguiente WebPart trata de mostrar cómo podemos construir una funcionalidad que le permita al usuario final ver la información almacenada, sin tener que acceder a la administración de la colección de sitios.
En las imágenes siguientes vemos la WebPart terminada y funcionando. La idea de la misma es que el usuario seleccione un tipo de evento y la WebPart despliegue toda la información almacenada para dicho evento.


Imagen 1
1_WebPart   

Imagen 2
   2_WebPart_DropDownLoad

Imagen 3
   3_WebPart_Eventos_Desplegados

Lo primero que vamos hacer es crear un proyecto del tipo WebPart en el Visual Studio 2005, en este proyecto vamos a dibujar la WebPart  y cargar toda la información almacenada de los eventos y accesos sucedidos hasta el momento.
Nuestra WebPart al ser cargada lo primero que realizara es inicializar una Hashtable, como clave vamos a utilizar los tipos de eventos provistos por Sharepoint, los cuales están definidos en la enumeración llamada ” SPAuditEventType” y como valor vamos a proveer una instancia de una clase personalizada llamada “AuditItemType”, la cual creamos pare representar un evento especifico y la cual posee una colección de instancias de la clase personalizada “AuditItemInfo” la cual representa un evento en particular.
En la sección 1 vemos el método que se encarga de inicializar la Hashtable con sus valores por defecto.

Sección 1

private void InitializeHassTable()
{
     lAuditInfoCharts = new Hashtable();
     foreach (object spEvent in SPAuditEventType.GetValues(typeof(SPAuditEventType)))
     {
         lAuditInfoCharts.Add(Enum.ToObject(typeof(SPAuditEventType), spEvent), new AuditItemType(spEvent.ToString()));
     }
}

Una vez inicializada la Hashtable, lo siguiente que vamos a realizar es cargar toda la información de los eventos y sucesos almacenada en Sharepoint dentro de la Hashtalbe. Para almacenar dicha información la Hashtable contiene la instancia  de la clase “AuditItemType” la cual representa a un evento en particular, por ese en el constructor de la misma pasamos en nombre del evento.
Para almacenar la información específica de un evento o acceso en particular contamos con una clase llamada “AuditItemInfo” donde obtenemos los valores del evento y los almacenamos en atributos de dicha clase.
Sharepoint nos provee la información de los eventos y accesos a través de la clase “SPAuditEntry” y algunas de sus propiedades son la siguiente:

11)    DocLocation: Esta propiedad provee la ubicación del elemento visto o accedido.

22)    Event: Tipo de evento sucedido.

33)    ItemId: Id del elemento al que se accedió.

44)    Occurred: Fecha y hora en la que sucedió el acceso.

55)    SiteId: Id (Guid) del sito donde está almacenado el elemento accedido.

66)    UserId: Id del usuario que accedió al elemento.

Esta son algunas de las propiedades que nos provee las clase “SPAuditEntry”, dentro de unas tantas que tiene la misma.

Para poder acceder a la información almacenada en Sharepoint debemos ejecutar una consulta utilizando la clase “SPAuditQuery” a la cual le debemos pasar el site que queremos consultar las estadísticas. Una vez creada la instancia de dicha clase debemos ejecutar el método “Getentries”  y le pasamos la instancia de la clase “SPAuditQuery” creada anteriormente. En nuestro caso vamos a utilizar el contexto de Sharepoint (SPContext) actual y vamos a acceder al método “” a través de la propiedad “Audit” del sitio en ejecución. En la sección 2 vemos como es dicha llamada.

Sección 2

SPAuditQuery lQuery = new SPAuditQuery(SPContext.Current.Site);
SPAuditEntryCollection lCollAudit = SPContext.Current.Site.Audit.GetEntries(lQuery);

Por último lo que hacemos es mostrar toda la información almacenada en la Hashtable dentro de una tabla de HTML. Para ellos lo primero que vamos hacer es dibujar en DropDownList en el método “CreateChildControls” y después en el evento “SelectedIndexChanged” del DropDownList cargamos la información para el evento seleccionado. A continuación todo el código fuente necesario para poder dibujar esta WebPart.

Sección 3 – AuditItemInfo

 

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.SharePoint; 

namespace Siderys.Blogs.WebPart

{

    public class AuditItemInfo

    {

        SPUser mUserAcces = null;

        SPSite mSite = null;

        string mItemViewd = string.Empty;

        DateTime mOcurredView = DateTime.MinValue; 

        public string ItemViewd

        {

            get { return mItemViewd; }

        } 

        public DateTime OcurredView

        {

            get { return mOcurredView; }

        }  

        public SPUser UserAcces

        {

            get { return mUserAcces; }

        }      

        public SPSite Site

        {

            get { return mSite; }

        } 

        public AuditItemInfo(int pIdUser, Guid pSite, Guid pListItem, string pItemViewed, DateTime pOcurredView)

        {

            mItemViewd = pItemViewed;

            mOcurredView = pOcurredView;

            mUserAcces = GetUser(pIdUser);

            mSite = GetSite(pSite);

        }

        //Obtiene el usuario de Sharepoint.

        private SPUser GetUser(int pIdUser)

        {

            return SPContext.Current.Web.AllUsers.GetByID(pIdUser);

        }

        //Obtiene el sitio de donde se vio el elemento.

        private SPSite GetSite(Guid pSite)

        {

            return new SPSite(pSite);

        }

Sección 4 – AuditItemType

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.SharePoint; 

namespace Siderys.Blogs.WebPart

{

    public class AuditItemType

    {

        string mEventType = string.Empty;

        List<AuditItemInfo> mColAudit = null;

        int mElemntAcces = 0; 

        public List<AuditItemInfo> Eventos

        {

            get { return mColAudit; }

        } 

        public int ElemntAcces

        {

            get { return mColAudit.Count; }

        }

        public string EventType

        {

            get { return mEventType; }

        } 

        public AuditItemType(string pEventType)

        {

            mEventType = pEventType;

            mColAudit = new List<AuditItemInfo>();

        } 

        public void Add(AuditItemInfo pAuditInfo)

        {

            mColAudit.Add(pAuditInfo);

        }

    }

}

 

Sección 5 – AuditingInformationWebPart

namespace Siderys.Blogs.WebPart {

    [Guid("8eac29c5-5b86-481f-8675-2b40914d7bcc")]

    public class AuditingInformationWebPart : System.Web.UI.WebControls.WebParts.WebPart

    { 

        Hashtable lAuditInfoCharts = null;

        DVDropDownList ddEvent = null;

        public AuditingInformationWebPart()

        {

            this.ExportMode = WebPartExportMode.All;

            bool p = this.DesignMode;

            InitializeHassTable();

            GetDataCharts();

            ddEvent = new DVDropDownList();

            ddEvent.SelectedIndexChanged += new EventHandler(ddEvent_SelectedIndexChanged);

            ddEvent.AutoPostBack = true;

            LoadItemDDL();

        } 

        protected override void Render(HtmlTextWriter writer)

        {

            base.Render(writer);

        } 

        protected override void CreateChildControls()

        {

            base.CreateChildControls();           

            HtmlTable lTable = new HtmlTable();

            HtmlTableRow lRow = null;

            HtmlTableCell lCell = null;

            lTable.Border = 0;

            lCell = new HtmlTableCell();

            lRow = new HtmlTableRow();

            lCell.InnerHtml = "<b>Seleccionar Evento: </b>";

            lRow.Controls.Add(lCell);

            lCell = new HtmlTableCell();

            lCell.Controls.Add(ddEvent);

            lRow.Controls.Add(lCell);

            lTable.Controls.Add(lRow);

            Controls.Add(lTable);

        } 

        private void ddEvent_SelectedIndexChanged(object sender, EventArgs e)

        {

            string pEventSelected = ((DVDropDownList)sender).SelectedItem.Text;

            Controls.Add(GetInfoEvetSelected(pEventSelected));

        }

        private HtmlTable GetInfoEvetSelected(string pEventSelected)

        {

//Agregue el código necesario para dibujar las entradas
}

        private void LoadItemDDL()

        {

            ddEvent.Items.Add("Seleccionar...");

            foreach(object lKey in lAuditInfoCharts.Keys)

            {

                string lValue = lKey.ToString();

                ddEvent.Items.Add(lValue);

            }

        }

        private object GetKeyHastTable(string pKey)

        {

            object lReturn = null;

            foreach (object spEvent in SPAuditEventType.GetValues(typeof(SPAuditEventType)))

            { 

                if (spEvent.ToString().EndsWith(pKey)) 

                {

                    lReturn = spEvent;

                    break;

                }                

            }

            return lReturn;

        }

        private void InitializeHassTable()

        {

            lAuditInfoCharts = new Hashtable();

            foreach (object spEvent in SPAuditEventType.GetValues(typeof(SPAuditEventType)))

            {

                lAuditInfoCharts.Add(Enum.ToObject(typeof(SPAuditEventType), spEvent), new AuditItemType(spEvent.ToString()));

            }

        }

        //Carga la información de los eventos en la Hashtable para despues utilizarla.

        private void GetDataCharts()

        {

            SPAuditQuery lQuery = new SPAuditQuery(SPContext.Current.Site);

            SPAuditEntryCollection lCollAudit = SPContext.Current.Site.Audit.GetEntries(lQuery);

            SPAuditEntry p;

            foreach (SPAuditEntry lAuditInfo in lCollAudit)

            {

                switch (lAuditInfo.Event)

                {

                    case SPAuditEventType.Update:

                        ((AuditItemType)lAuditInfoCharts[SPAuditEventType.Update]).Add(new AuditItemInfo(lAuditInfo.UserId, lAuditInfo.SiteId, lAuditInfo.ItemId, lAuditInfo.DocLocation, lAuditInfo.Occurred));

                        break;

                    case SPAuditEventType.View:

                        ((AuditItemType)lAuditInfoCharts[SPAuditEventType.View]).Add(new AuditItemInfo(lAuditInfo.UserId, lAuditInfo.SiteId, lAuditInfo.ItemId, lAuditInfo.DocLocation, lAuditInfo.Occurred));

                        break;

//Agregue el mismo código para todos los tipos de evento y cambien lo necesario para cada tipo de evento.

                 }

            }

        } 

    }

}

La idea de esta WebPart fue demostrar cómo podemos mejorar la forma de mostrar la información que Sharepoint va almacenando.

Fabián Imaz

Siderys Elite Software