lunes, 21 de noviembre de 2011

QI for IEnumVARIANT failed on the unmanaged server

Bueno, por si alguna vez les ha ocurrido este error al tratar de ingresar a la página de administración del servicio de búsqueda.


Inclusive al intentar recuperar algún valor mediante PowerShell este mismo error aparece, una posible solución que a mí me funciono fue reiniciar el IIS utilizando el siguiente comando

iisreset /noforce

Esto en cada uno de los servidores de crawling

Happy Configuring!

Crear custom workflow tasks forms [No Infopath]

Como recordaran en el post anterior, mostré un ejemplo de cómo crear columnas de sitio y content types programáticamente, en este post voy a dar un ejemplo de cómo crear un formulario personalizado para el tipo de contenido que creamos en ese ejemplo anterior.

Es común que creemos tipos de contenido personalizados (custom content types) para asociarlos a la lista de tareas de workflows (Workflow Tasks), esto con el fin de poder recolectar más información del usuario cuando se le asigna una tarea dentro de un flujo de trabajo, y también es muy común que necesitemos personalizar el formulario de captura que el usuario utiliza para ingresar esa información adicional que necesitamos recolectar dentro del flujo de trabajo. Por lo anterior y utilizando el tipo de contenido que creamos en el post anterior, y que de manera premeditada hice que heredara del tipo de contenido WorkflowTask (recuerdan?).
También es importante mencionar que el custom approval workflow task form que crearemos será utilizando un formulario ASP.NET, creo que la mayoría sabe que no simpatizo mucho con InfoPath, además que ejemplos de esos hay bastantes en la red.
El escenario de ejemplo es el siguiente, cree una lista llamada “Solicitud Vacaciones”, en la que los usuarios podrán capturar las peticiones para tomar días de vacaciones, una vez que registren sus solicitud se enviará un correo de aviso al usuario responsable y se le asignará una tarea para que apruebe o rechace dicha solicitud, en caso de aprobación o rechazo se le notificará al usuario solicitante.
Como primer paso, creamos un flujo de trabajo sencillo, en este caso construiré un flujo de aprobación de solicitud de vacaciones, como se aprecia en la siguiente imagen:
   
No voy a explicar el cómo construir el flujo de trabajo, dado que la finalidad de este post no es mostrar cómo hacer un flujo de trabajo,  adicional a que hay muchos ejemplos en la red. Sin embargo voy a compartir el código por si a alguien le interesa ver cómo fue construido el Workflow.

Como se aprecia en la siguiente imagen, se agregó una actividad de tipo CreateTaskWithContentType la cual recibe el Id del Content Type a partir del cual se creara la tarea que será asignada al usuario responsable de autorizar la solicitud de vacaciones.








 

Como se aprecia en la imagen, el ID del tipo de contenido se asocia a una propiedad declarada en código, la que manipularemos para buscar y asignar el ID de tipo de contenido correcto cuando el Workflow se inicialice, en el evento WorkflowActivated.


private void onWorkflowActivated(object sender, ExternalDataEventArgs e)
{
 WorkflowContext.Initialize(workflowProperties);
 workflowID = workflowProperties.WorkflowId;

 //si el tipo de contenido no ha sido asociado a la lista de tareas
 try
 {
  if (workflowProperties.TaskList.ContentTypes[contentTypeName] == null)
  {
   SPContentType ctype = WorkflowContext.Web.AvailableContentTypes[contentTypeName];
   if (ctype != null)
   {
    workflowProperties.TaskList.ContentTypes.Add(ctype);
    workflowProperties.TaskList.Update();
   }
  }

  SPContentType customContentType = WorkflowContext.Web.AvailableContentTypes[contentTypeName];
  this.CustomContentTypeId = customContentType.Id.ToString();

 }
 catch (Exception ex)
 {
  //registramos la excepcion
 }
}

Estableciendo el formulario de edición al tipo de Contenido

Regresando a la definición del tipo de contenido que creamos en el post anterior, que cómo podrán recordar fue provisionado programáticamente. El siguiente paso es establecer por lo menos el formulario de edición del tipo de contenido, es decir, el formulario que el usuario visualizará cuando vaya a aprobar o rechazar la tarea asignada. Para que el Content Type sepa que formulario debe invocar, utilizaremos la propiedad EditFormUrl la que estableceremos con el valor del formulario ASP.NET que vamos a crear en el siguiente paso.

Noten la línea 131:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;

namespace WF1.Features.Feature1
{
    /// 
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// 
    /// 
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// 

    [Guid("dd6b6ec4-3391-46de-a659-55244237f346")]
    public class Feature1EventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWeb web = null;
                if (properties.Feature.Parent is SPSite)
                {
                    SPSite sites = (SPSite)properties.Feature.Parent;
                    web = sites.RootWeb;
                }
                else
                {
                    web = (SPWeb)properties.Feature.Parent;
                }
                if (web == null)
                    return;

                //Nombre del grupo de Content Types que crearemos
                string contentTypeGroupName = "Blog - Content Types";
                //Nombre del tipo de contenido que crearemos
                string contentTypeName = "Blog - Response Workflow Content Type";
                //Nombre del grupo de columnas de sitio que crearemos
                string columnGroupName = "Blog - Site Columns";
                //nombre de la columna de sitio que permitira al usuario seleccionar su respuesta (Approve | Reject)
                string choiceFieldName = "ChoiceWFResponse";
                //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta
                string commentsFieldName = "CommentsWFResponse";


                SPFieldMultiLineText approveRejectCommentsField = null;
                SPFieldChoice approveRejectChoiceField = null;

                /*Columna de sitio para seleccionar Aprobar o Rechazar*/
                //mucho ojo! Validamos si la columa de sitio ya existe
                if (!web.Fields.ContainsField(choiceFieldName))
                {
                    string approveReject = web.Fields.Add(choiceFieldName, SPFieldType.Choice, true);
                    approveRejectChoiceField = (SPFieldChoice)web.Fields.GetFieldByInternalName(approveReject);
                    approveRejectChoiceField.Group = columnGroupName;
                    approveRejectChoiceField.EditFormat = SPChoiceFormatType.RadioButtons;
                    approveRejectChoiceField.Choices.Add("Approve");
                    approveRejectChoiceField.Choices.Add("Reject");
                    approveRejectChoiceField.DefaultValue = "Approve";
                    approveRejectChoiceField.Update();
                }
                //Si ya existe, solo obtenemos una referencia a la columna
                else
                {
                    approveRejectChoiceField = web.Fields[choiceFieldName] as SPFieldChoice;
                }

                /*Columna de sitio para permitir al usuario ingresar un comentario*/
                if (!web.Fields.ContainsField(commentsFieldName))
                {
                    string comments = web.Fields.Add(commentsFieldName, SPFieldType.Note, true);
                    approveRejectCommentsField = (SPFieldMultiLineText)web.Fields.GetFieldByInternalName(comments);
                    approveRejectCommentsField.Group = columnGroupName;
                    approveRejectCommentsField.Update();
                }
                else
                {
                    approveRejectCommentsField = web.Fields[commentsFieldName] as SPFieldMultiLineText;
                }

                //
                //Ahora creamos el tipo de contenido
                //

                //heredamos del WorkflowTask Content Type
                SPContentType workflowTaskCType = web.AvailableContentTypes[SPBuiltInContentTypeId.WorkflowTask];
                SPContentType approvalRejectCType = null;
                bool isUpdating = false;

                //validamos si el tipo de contenido ya existe
                if (web.AvailableContentTypes[contentTypeName] == null)
                {
                    approvalRejectCType = new SPContentType(workflowTaskCType, web.ContentTypes, contentTypeName);
                    approvalRejectCType = web.ContentTypes.Add(approvalRejectCType);
                    approvalRejectCType.Group = contentTypeGroupName;
                }
                else
                {
                    approvalRejectCType = web.ContentTypes[contentTypeName];
                    isUpdating = true;
                }

                if (!isUpdating)
                {
                    //agregamo la columna de Seleccion
                    SPFieldLink approveRejectFieldRef = new SPFieldLink(approveRejectChoiceField);
                    approveRejectFieldRef.Required = true;
                    approveRejectFieldRef.DisplayName = "Approve or Reject the task";
                    approvalRejectCType.FieldLinks.Add(approveRejectFieldRef);

                    //agregamo la columna de Comentarios
                    SPFieldLink commentsFieldRef = new SPFieldLink(approveRejectCommentsField);
                    commentsFieldRef.DisplayName = "Comments";
                    approvalRejectCType.FieldLinks.Add(commentsFieldRef);
                }
                else
                {
                    //Actualizamos la columna de Seleccion
                    SPFieldLink approveRejectFieldRef = approvalRejectCType.FieldLinks[approveRejectChoiceField.Id];
                    approveRejectFieldRef.Required = true;
                    approveRejectFieldRef.DisplayName = "Approve or Reject the task";

                    //Actualizamos la columna de Comentarios
                    SPFieldLink commentsFieldRef = approvalRejectCType.FieldLinks[approveRejectCommentsField.Id];
                    commentsFieldRef.DisplayName = "Comments";
                }

                approvalRejectCType.EditFormUrl = "_layouts/WF1/CustomApprovalTask.aspx";

                //actualizamos el tipo de contenido
                approvalRejectCType.Update(true);
            }
            catch (Exception ex)
            {
                //log Exception
            }
        }

    }
}

Existen algunas otras propiedades con las cuales podemos indicar que formulario debe mostrarse dependiendo de la vista que sea solicitada, tales como: DisplayFormUrl, MobileDisplayFormUrl, etc, etc

Creación del formulario

El siguiente paso es la creación del formulario que será invocado cuando el usuario haga clic en editar tarea. Para esto agregamos un elemento Application Page y la nombramos como CustomApprovalTask.aspx, el cual usaremos para crear la forma de edición para el tipo de contenido.



A continuación el código del formulario, en realidad es bastante sencillo solo presenta un par de controles, una caja de texto para capturar los comentarios y un dropdownlist para elegir una opción (Aprobar, Rechazar).



using System;
using System.Collections;
using System.Web;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Workflow;

namespace WF1.Layouts
{
    public partial class CustomApprovalTask : LayoutsPageBase
    {
        protected SPList targetTasksList;
        protected SPListItem targetTask;
        protected SPWorkflow workflowInstance;
        protected SPWorkflowModification workflowModification;

        string choiceFieldName = "ChoiceWFResponse";
        //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta
        string commentsFieldName = "CommentsWFResponse";


        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            /**/
            // Retrieve the current task list and task item
            targetTasksList = Web.Lists[new Guid(Request.Params["List"])];
            targetTask = targetTasksList.GetItemById(int.Parse(Request.Params["ID"]));

            //Obtiene el autorizador de la tarea
            string Aprobador = Convert.ToString(targetTask[SPBuiltInFieldId.AssignedTo]);
            int IdAporbador = Convert.ToInt32(Aprobador.Remove(Aprobador.IndexOf(';'), Convert.ToInt32(Aprobador.Length - Aprobador.IndexOf(";"))));

            if ((Web.CurrentUser.ID != IdAporbador) )
            {
                ClosePopup();
            }

            /**/
            if (!this.Page.IsPostBack)
            {
                // Populate fields on the form
                lblTitle.Text = targetTask["Title"] != null ? targetTask["Title"].ToString() : string.Empty;
                txtCommnents.Text = targetTask[commentsFieldName] != null ? targetTask[commentsFieldName].ToString() : string.Empty;

                ListItem selectedItem = ddlResponse.Items.FindByValue(targetTask[choiceFieldName].ToString());

                if (selectedItem != null)
                {
                    this.ddlResponse.ClearSelection();
                    selectedItem.Selected = true;
                }
            }

        }

        protected void UpdateTaskFromControls(SPListItem targetTask, System.Collections.Hashtable taskProperties)
        {
            // Update task item fields
            taskProperties[SPBuiltInFieldId.TaskStatus] = SPWorkflowStatus.Completed;
            taskProperties[commentsFieldName] = txtCommnents.Text;
            taskProperties[choiceFieldName] = ddlResponse.SelectedValue;
        }

        public void btnSaveTask_Click(object sender, EventArgs e)
        {
            try
            {
                Hashtable taskProperties = new Hashtable();
                UpdateTaskFromControls(targetTask, taskProperties);
                SPWorkflowTask.AlterTask(targetTask, taskProperties, false);

            }
            catch (Exception exception)
            {
                SPUtility.Redirect("Error.aspx", SPRedirectFlags.RelativeToLayoutsPage,
                HttpContext.Current,
                "ErrorText=" + SPHttpUtility.UrlKeyValueEncode(exception.Message));
            }
            ClosePopup();
        }

        public void Cancel_Click(object sender, EventArgs e)
        {
            ClosePopup();
        }

        protected void ClosePopup()
        {
            this.Response.Clear();
            this.Response.Write("Closing ...");
            this.Response.Flush();
            this.Response.End();
        }
    }
}





Pueden descargar el código de aquí

Happy Coding!

Etiquetas

SharePoint 2010 (38) Microsoft (32) Desarrollo SharePoint (31) Gerardo Reyes Ortiz (27) SharePoint (20) SharePoint 2013 (18) Errores SharePoint (12) México (10) PowerShell (9) Silverlight (8) Visio Services (7) Features (6) MVP (6) Silverlight 3 (6) WebCast (6) Workflows (6) Configuracion SharePoint 2010 (5) D.F. (5) API REST (4) Configuracion SharePoint 2010; (4) Troubleshooting (4) Visual Studio 2010 (4) Visual studio (4) WSS (4) Web parts (4) Apps (3) Comunidad SharePoint (3) Configuración SharePoint 2013 (3) ODATA (3) SharePoint Server (3) SharePoint; Instalación SharePoint; Troubleshooting; Search Service (3) Silverlight 3.0 (3) Silverlight Toolkit (3) WebParts (3) javascript (3) jquery (3) Eventos SharePoint (2) Office 2010 (2) PeoplePicker (2) REST (2) SQL Server (2) Scripting (2) Search Service Application (2) SharePoint Designer (2) UPA (2) UPS (2) Workflows SharePoint (2) host header (2) Apps Development (1) Big Bang (1) CAS (1) CSOM (1) Codeplex (1) CompartiMOSS (1) Configuracion SharePoint 2010; Errores SharePoint (1) Configuracion SharePoint 2010; SharePoint 2010 (1) Custom Actions (1) Custom Editor Parts (1) Delegate Controls (1) Deployment (1) DisableLoopbackCheck (1) Document Library (1) Entrevista (1) Examenes de Certificación (1) Extract WSP (1) FBA (1) FS4SP (1) Fakes (1) Fast Search Server 2010 For SharePoint (1) Fiddler (1) HTTP.SYS (1) HTTPS (1) JSON (1) Language Pack's (1) Latam (1) MAXDOP (1) MCSM (1) MSExpertos (1) MVC (1) Microsoft México (1) Microsoft; Codeplex; Screencast; (1) My Sites (1) SQL Server 2012 (1) SQL Server Reporting Services (1) Screencast (1) Screencast; (1) Service Applications (1) Service Pack (1) SharePoint 2007 (1) SharePoint 2010 SP 1 (1) SharePoint API (1) SharePoint Conference (1) SharePoint Emulators (1) SharePoint Farm (1) SharePoint Health Analyzer (1) SharePoint Magazine (1) SharePoint Online (1) SharePoint Search (1) SharePoint Test (1) SharePoint; Desarrollo SharePoint (1) Shims (1) Simposio (1) Simposio Latinoamericano (1) SkyDrive Pro (1) Soporte Microsoft (1) Templates (1) Tip (1) VSeWSS (1) Virtual Machine (1) Visual Studio 2012 (1) WCF (1) WSS; IIS 7 (1) Web API (1) Web Content Management (1) Web Services (1) Windows 8 (1) Windows Live ID (1) Xml (1) appcmd (1) iOS (1) jqGrid (1) onload function (1)