Permitir sólo una instancia en ejecución de una aplicación (How to run a single instance of an application)

by Nicolás Ferreira 2. February 2009 00:00

Para esto, pueden utilizar la siguiente clase:

using System;
using System.Threading;

namespace NicolasFerreira.Utilities
{
    //* **************************************************
    //* Author: Nicolás Ferreira (http://nicolasferreira.com/)
    //* **************************************************
    /// <summary>
    /// This class uses EventWaitHandle. It lets you know if another instance of your application is already running. Also, if there is already a running instance of your application and someone try to start another, the main one will be notified.
    /// </summary>
    public static class SingleInstanceApplication
    {
        private static EventWaitHandle _EventWaitHandle;
        private static object _lock = new object();
        private static bool _Opened;
        private static Thread _WaitOneHandlerThread;
        private static bool _RaiseAnotherTriedToRunEvent;
        public delegate void AnotherTriedToRunEventHandler();
        public static event AnotherTriedToRunEventHandler AnotherTriedToRun;
        /// <summary>
        /// Gets a value that indicates whether single instance application monitor is open.
        /// </summary>
        public static bool Opened
        {
            get
            {
                return _Opened;
            }
        }
        /// <summary>
        /// Opens the single instance application monitor.
        /// </summary>
        /// <param name="uniqueInstanceName">A name that identifies your application as unique.</param>
        /// <returns>true if the system-wide EventWaitHandle could be created (the calling instance is single). false if the calling instance should terminate.</returns>
        public static bool Open(string uniqueInstanceName)
        {
            return Open(uniqueInstanceName, false);
        }
        /// <param name="local">true if each terminal server session can run an instance of your application. Otherwise, false.</param>
        public static bool Open(string uniqueInstanceName, bool local)
        {
            if (string.IsNullOrEmpty(uniqueInstanceName))
                throw new ArgumentException("uniqueInstanceName cannot be blank.");
            else if (_Opened)
                throw new InvalidOperationException("Single instance application monitor is open.");
            lock (_lock)
            {
                bool createdNew;
                _EventWaitHandle = new EventWaitHandle(
                    false,
                    EventResetMode.AutoReset,
                    string.Concat(local ? "Local\\" : "Global\\", uniqueInstanceName),
                    out createdNew);
                if (createdNew)
                {
                    _Opened = true;
                    _RaiseAnotherTriedToRunEvent = true;
                    _WaitOneHandlerThread = new Thread(new ThreadStart(EventWaitHandleHandler));
                    _WaitOneHandlerThread.Start();
                }
                else
                {
                    _Opened = false;
                    _RaiseAnotherTriedToRunEvent = false;
                    _EventWaitHandle.Set();
                    _EventWaitHandle.Close();
                    _EventWaitHandle = null;
                }
            }
            return _Opened;
        }
        private static void EventWaitHandleHandler()
        {
            if (_EventWaitHandle != null)
            {
                while (_Opened)
                {
                    _EventWaitHandle.WaitOne();
                    if ((_RaiseAnotherTriedToRunEvent) && (AnotherTriedToRun != null))
                        ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object state)
                        {
                            AnotherTriedToRun();
                        }));
                }
            }
        }
        /// <summary>
        /// Closes the single instance application monitor and frees resources.
        /// </summary>
        public static void Close()
        {
            lock (_lock)
            {
                if (!_Opened)
                    throw new InvalidOperationException("Single instance application monitor is not open.");
                _Opened = false;
                _RaiseAnotherTriedToRunEvent = false;
                _EventWaitHandle.Set();
                _EventWaitHandle.Close();
                _EventWaitHandle = null;
            }
        }
    }
}

Ejemplo en una aplicación consola:

        static void Main(string[] args)
        {
            SingleInstanceApplication.AnotherTriedToRun += new SingleInstanceApplication.AnotherTriedToRunEventHandler(SingleInstanceApplication_AnotherTriedToRun);
            if (!SingleInstanceApplication.Open("{63BAA9C4-849C-4b4a-8B9C-FC1D640F09E2}"))
            {
                Console.WriteLine("Another instance of this application is currently running.");
            }
            else
            {
                Console.WriteLine("This is the main instance of your application.");
                Console.ReadLine();
                if (SingleInstanceApplication.Opened)
                {
                    SingleInstanceApplication.Close();
                }
            }
        }
        static void SingleInstanceApplication_AnotherTriedToRun()
        {
            Console.WriteLine(string.Format("{0}: Another instance of this application tried to run.",
                DateTime.Now.ToString()));
        }

Ejemplo en una aplicación Windows Forms:

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            if (SingleInstanceApplication.Open("{4C8C7928-55FD-45e8-A13E-895C5EE4D0C3}"))
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
                if (SingleInstanceApplication.Opened)
                {
                    SingleInstanceApplication.Close();
                }
            }
            else
            {
                MessageBox.Show("Another instance of this application is currently running.");
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            SingleInstanceApplication.AnotherTriedToRun += new SingleInstanceApplication.AnotherTriedToRunEventHandler(SingleInstanceApplication_AnotherTriedToRun);
        }

        void SingleInstanceApplication_AnotherTriedToRun()
        {
            MessageBox.Show(string.Format("{0}: Another instance of this application tried to run.",
                DateTime.Now.ToString()));
        }

Descargar el código.

Tags:

.NET Development

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS)

by Nicolás Ferreira 10. January 2009 00:00

Existe un error (o al menos existió al momento de escribir este artículo). Si alojamos el servicio en "Windows Process Activation Services" utilizando el protocolo "net.tcp" y el nombre del equipo contiene caracteres no-ASCII, no seremos capaces de establecer la conexión con el servicio.

Para poner en funcionamiento un servicio "Windows Communication Foundation (WCF)" hay que alojarlo. Podemos alojar los servicios en:
- Aplicaciones Windows Forms/WPF
- Aplicaciones consola
- Servicios de Windows
- Internet Information Services
- Windows Process Activation Services (WAS)

"Windows Process Activation Services" no es nuevo. De hecho fue introducido por primera vez en "Windows Vista", está presente en "Windows Server 2008" y continúa en "Windows 7".
"Windows Process Activation Services" puede ser una buena opción si pensamos en correr servicios "Windows Communication Foundation" en dichos sistemas operativos.

Crear el servicio "Windows Communication Foundation"

1 – Abrimos "Visual Studio" y elegimos crear un proyecto de tipo "WCF Service Library". De nombre le ponemos "ServicioHolaMundo". Una vez creado el proyecto, borramos los archivos "IService1.cs" y "Service1.cs" que "Visual Studio" crea por defecto.

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Crear proyecto

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Borrar archivos

2 – Tenemos que crear un contrato (interfaz). Para esto, en el explorador de solución hacemos clic derecho sobre "ServicioHolaMundo" y elegimos "Add" -> "New Item...". Una vez abierto "Add New Item" elegimos "Interface" y de nombre le ponemos "IServicioHolaMundo.cs". Hacemos clic en "Add".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Crear interfaz (Paso 1)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Crear interfaz (Paso 2)

3 – Dejamos la interfaz así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace NicolasFerreira.Servicios
{
    [ServiceContract(Name = "ServicioHolaMundo",
    Namespace = "http://nicolasferreira.com/2009/01/09")]
    public interface IServicioHolaMundo
    {
        [OperationContract()]
        string HolaMundo();
    }
}

4 – Tenemos que crear una clase (la implementación del contrato) que implemente la interfaz "IServicioHolaMundo". Para esto, en el explorador de solución hacemos clic derecho sobre "ServicioHolaMundo" y elegimos "Add" -> "New Item...". Una vez abierto "Add New Item" elegimos "Class" y de nombre le ponemos "ServicioHolaMundo.cs". Hacemos clic en "Add".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Crear clase

5 – Dejamos la clase así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NicolasFerreira.Servicios
{
    public class ServicioHolaMundo : IServicioHolaMundo
    {
        #region IServicioHolaMundo Members

        public string HolaMundo()
        {
            return "¡Hola mundo!";
        }

        #endregion
    }
}

6 – En el explorador de solución y bajo el proyecto "ServicioHolaMundo" buscamos el archivo "App.config". Hacemos clic derecho sobre él y elegimos la opción "Edit WCF configuration". Aparecerá la aplicación "Microsoft Service Configuration Editor". En el panel "Configuration" bajo la carpeta "Services" seleccionamos "ServicioHolaMundo.Service1". Cambiamos el valor de la propiedad "Name" por "NicolasFerreira.Servicios.ServicioHolaMundo".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Editar configuración WCF (Paso 1)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Editar configuración WCF (Paso 2)

7 – Expandir las siguientes carpetas: "NicolasFerreira.Servicios.ServicioHolaMundo" -> "Endpoints" hasta ver dos "Endpoints" que no tienen nombre: "(Empty Name)". Hacemos clic sobre el primero. Podemos ver que el valor de la propiedad "Binding" es "wsHttpBinding". Lo cambiamos a "netTcpBinding". El valor de la propiedad "Contract" es "ServicioHolaMundo.IService1". Lo cambiamos a "NicolasFerreira.Servicios.IServicioHolaMundo". Ahora hacemos clic sobre el segundo "Endpoint". Podemos ver que el valor de la propiedad "Binding" es "mexHttpBinding". Lo cambiamos a "mexTcpBinding".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Configurar Endpoints (Paso 1)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Configurar Endpoints (Paso 2)

8 – Expandir las siguientes carpetas: "Advanced" -> "Service Behaviors" -> "ServicioHolaMundo.Service1Behavior" hasta ver "serviceMetadata". Seleccionamos "serviceMetadata" y cambiamos el valor de la propiedad "HttpGetEnabled" a "False".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Configurar serviceMetadata

9 – Cerramos la aplicación y cuando nos pregunte si queremos guardar los cambios le decimos que sí.

10 – Por último agregamos un archivo llamado "ServicioHolaMundo.svc" al proyecto.

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Agregar ServicioHolaMundo.svc

11 – Lo dejamos así:

<%@ ServiceHost Language="C#" Service="NicolasFerreira.Servicios.ServicioHolaMundo" %>

12 – Compilamos la solución y cerramos "Visual Studio".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Compilar solución

Instalar y configurar "Windows Process Activation Services" para alojar el servicio

1 – En "Windows 7" vamos a "Control Panel". Una vez que estemos ahí, hacemos clic sobre el vínculo "Programs" y después bajo "Programs and Features" hacemos clic en el vínculo "Turn Windows features on or off". Cuando aparezca el cuadro "Windows Features" marcamos las siguientes opciones:

- "IIS Management Console" que se encuentra en: "Internet Information Services" -> "Web Management Tools".
- ".NET Extensibility" que se encuentra en: "Internet Information Services" -> "World Wide Web Services" -> "Application Development Features".
- "Windows Communication Foundation Non-HTTP Activation" que se encuentra en: "Microsoft .NET Framework 3.5.1"

Finalmente hacemos clic en "OK".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Control Panel

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Programs

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Turn Windows features on or off

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Windows Features

2 – Volvemos a "Control Panel" pero esta vez hacemos clic sobre el vínculo "All Control Panel Items" para así poder ir a "Administrative Tools" y elegir "Internet Information Services (IIS) Manager".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): All Control Panel Items

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Administrative Tools

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): IIS Manager

3 – Por defecto en "Windows 7" ya vienen configurados algunos "Bindings" para el sitio "Default Web Site". Esto lo comprobamos expandiendo el nombre del equipo, luego "Sites" y luego eligiendo "Default Web Site". Si hacemos clic en "Bindings..." veremos lo siguiente:

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Default Web Site

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Site Bindings

4 – Para este ejemplo utilizaremos la configuración por defecto (el protocolo "net.tcp" y el puerto "808").
5 – Hacemos clic derecho sobre "Default Web Site" y seleccionamos la opción "Add Application...". En "Alias" escribimos "ServicioHolaMundo" y en "Physical path" escribimos "C:\inetpub\wwwroot\ServicioHolaMundo" (previamente hay que crear la carpeta "ServicioHolaMundo" en "C:\inetpub\wwwroot"). Hacemos clic en "OK".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Add Application (Paso 1)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Add Application (Paso 2)

6 – Una vez que hicimos clic en "OK", quedará seleccionada la aplicacion que acabamos de crear. En el panel "Actions" hacemos clic en la opción "Advanced Settings...". Aparecerá el cuadro "Advanced Settings". El valor de la propiedad "Enabled Protocols" es "http". Lo cambiamos por "net.tcp", hacemos clic en "OK" y cerramos "Internet Information Services (IIS) Manager".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Advanced Settings

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS): Enabled Protocols

7 – Vamos a la carpeta "C:\inetpub\wwwroot\ServicioHolaMundo" y pegamos los archivos "ServicioHolaMundo.svc" y "app.config". Renombramos el archivo "app.config" por "Web.config". Creamos una carpeta con el nombre de "Bin" y le pegamos el archivo "ServicioHolaMundo.dll".

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS)

Probar el servicio

1 – Podemos probar lo que hemos hecho yendo a Visual Studio, creando un proyecto de tipo aplicación consola y agregando una referencia al servicio.

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS) : Add Service Reference (Paso 1)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS) : Add Service Reference (Paso 2)

Alojar un servicio Windows Communication Foundation en Windows 7 utilizando Windows Process Activation Services (WAS)

Won’t you pour me a Cuban breeze Gretchen

Tags:

.NET Development | Windows Communication Foundation (WCF)

Publicar formularios InfoPath con código en SharePoint

by Nicolás Ferreira 12. July 2008 00:00

"Microsoft Office Forms Server 2007" permite que usuarios puedan trabajar con formularios de "Microsoft Office InfoPath 2007" sin la necesidad de tener instalado InfoPath.
Para que un formulario pueda ser visualizado en un navegador Web, tenemos que cambiar la configuración de compatibilidad de la plantilla. Además, si queremos escribir código administrado (C# o Visual Basic .NET), el proceso de despliegue y activación del formulario es distinto. A las plantillas de formulario que utilizan código administrado se las conoce como "administrator-approved form templates". Debido a que código administrado puede ser un problema de seguridad, los formularios que contengan código deben ser aprobados por un administrador.

Diseñar el formulario

1 – Abrimos "Microsoft Office InfoPath 2007" y en "Introducción" seleccionamos "Diseñar una plantilla de formulario...". Elegimos "En blanco" y hacemos clic en "Aceptar".
2 – En el "Panel de tareas", hacemos clic en "Verificador de diseño" y luego "Cambiar configuración de compatibilidad...". En "Opciones de formulario" seleccionamos la categoría "Compatibilidad y marcamos la opción "Diseñe una plantilla de formulario que se pueda abrir en un explorador o en InfoPath". Finalmente hacemos clic en "Aceptar".

Publicar formularios InfoPath con código en SharePoint: Verificador de diseño

Publicar formularios InfoPath con código en SharePoint: Compatibilidad de explorador

3 – Volvemos a "Tareas de diseño" y hacemos clic en "Controles". Arrastramos hacia la superficie de diseño un control "Cuadro de texto" y un control "Botón". Cuando el usuario haga clic en "Botón", cambiaremos a mayúsculas el texto que haya en el cuadro de texto.

Publicar formularios InfoPath con código en SharePoint: Agregar controles

4 – Para que nuestro formulario pueda utilizar código administrado, tiene que tener plena confianza. Vamos al menú "Herramientas" y hacemos clic en "Opciones de formulario...". Seleccionamos la categoría "Seguridad y confianza" y le sacamos la marca a "Determinar automáticamente el nivel de seguridad (recomendado)" para así poder elegir "Plena confianza". Elegimos la opción "Plena confianza (el formulario puede obtener acceso a los archivos y a la configuración del equipo)". Hacemos clic en "Aceptar".

Publicar formularios InfoPath con código en SharePoint: Plena confianza

Agregar código

1 – Podemos elegir escribir el código del formulario en C# o Visual Basic .NET. Para configurar esto, vamos al menú "Herramientas" y hacemos clic en "Opciones de formulario...". Bajo "Programación" elegimos "C#" en "Lenguaje del código de la plantilla de formulario" y hacemos clic en "Aceptar".

Publicar formularios InfoPath con código en SharePoint: Programación 

2 – Una vez que estamos en el diseñador, hacemos clic derecho sobre "Botón" y seleccionamos "Propiedades de Botón". Hacemos clic sobre "Editar código del formulario...", veremos un mensaje diciendo que para poder agregar código tenemos que guardar la plantilla primero. Le damos a "Aceptar" y elegimos dónde guardar la plantilla. Después, se abrirá "Microsoft Visual Studio Tools para aplicaciones".

Publicar formularios InfoPath con código en SharePoint: Propiedades de Botón

3 – En el manejador "CTRL2_5_Clicked" para el evento "Clicked" de "CTRL2_5" escribimos el siguiente código:

        public void CTRL2_5_Clicked(object sender, ClickedEventArgs e)
        {
            XPathNavigator lcampo1 =
                e.Source.SelectSingleNode("/my:misCampos/my:campo1", this.NamespaceManager);
            lcampo1.SetValue(lcampo1.InnerXml.ToUpper());
        }

"/my:misCampos/my:campo1" es la expresión XPath al cuadro de texto que agregamos anteriormente. Para obtener la expresión, en "Tareas de diseño" seleccionamos "Origen de datos" y buscamos el campo que está enlazado al control cuadro de texto. Hacemos clic derecho sobre el campo y seleccionamos "Copiar XPath".

Publicar formularios InfoPath con código en SharePoint: Copiar XPath

Activar la característica "Características de colección de sitios de Office SharePoint Server Enterprise"

1 – Vamos al sitio de nivel superior en la colección de sitios para el sitio que queremos mostrar el formulario de InfoPath.
2 – En el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de la colección de sitios" elegimos "Características de la colección de sitios". Buscamos "Características de colección de sitios de Office SharePoint Server Enterprise" y hacemos clic en el botón "Activar".

Publicar el formulario

1 – Vamos al menú "Archivo" y hacemos clic en "Publicar...", nos saldrá un mensaje para guardar el formulario debido a que ha sido modificado desde la última vez que fue guardado. Hacemos clic en "Aceptar" y en el asistente para la publicación, elegimos "En un servidor de SharePoint con o sin InfoPath Forms Services".

Publicar formularios InfoPath con código en SharePoint: Asistente para la publicación (Paso 1)

2 – Hacemos clic en "Siguiente". Especificamos la ubicación del sitio de SharePoint o InfoPath Forms Services. En mi caso http://equipo:2667/.

Publicar formularios InfoPath con código en SharePoint: Asistente para la publicación (Paso 2)

3 – Hacemos clic en "Siguiente". Vamos a ver que la única opción que tenemos es "Plantilla de formulario aprobada por el administrador (avanzado)".

Publicar formularios InfoPath con código en SharePoint: Asistente para la publicación (Paso 3)

4 – Hacemos clic en "Siguiente". En "Especifique una ubicación y un nombre de archivo para la plantilla de formulario", hacemos clic en "Examinar..." y elegimos en dónde guardar la plantilla de formulario publicada para que un administrador luego la suba y la active.

Publicar formularios InfoPath con código en SharePoint: Asistente para la publicación (Paso 4)

5 – Hacemos clic en "Siguiente" dos veces hasta llegar al paso en el que tenemos que hacer clic en "Publicar".

Publicar formularios InfoPath con código en SharePoint: Asistente para la publicación (Paso 5)

Instalar la plantilla de formulario

1 – Abrimos "Símbolo del sistema" y nos vamos a la ubicación en la que se encuentra la herramienta "STSADM.EXE" (en mi caso "C:\Archivos de programa\Archivos comunes\Microsoft Shared\web server extensions\12\BIN").
2 – Ejecutamos los siguientes comandos (cambiando por supuesto los valores específicos a nuestro entorno):

stsadm.exe -o deactivateformtemplate -url "http://equipo:2667/" -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o removeformtemplate -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o execadmsvcjobs
stsadm.exe -o uploadformtemplate -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o execadmsvcjobs
stsadm.exe -o activateformtemplate -url "http://equipo:2667/" -filename "C:\Plantillas\Plantilla1_Publicada.xsn"

Publicar formularios InfoPath con código en SharePoint: Instalar la plantilla de formulario

deactivateformtemplate, removeformtemplate – Nos aseguramos de desactivar y remover el formulario por si estamos actualizando y no instalando por primera vez.
execadmsvcjobs – Ejecuta todos los trabajos administrativos del temporizador inmediatamente en lugar de esperar a que se ejecute el trabajo del temporizador.
uploadformtemplate, activateformtemplate – Subimos la plantilla de formulario y la activamos.

Mostrar la plantilla de formulario en un sitio

1 – Vamos al sitio para el que queremos mostrar el formulario (tiene que ser un sitio que pertenezca a la colección de sitios para la que activamos la plantilla).
2 – En el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de sitios" elegimos "Bibliotecas y listas del sitio".
3 – En "Bibliotecas y listas del sitio" elegimos "Crear nuevo contenido" y bajo la sección "Bibliotecas" seleccionamos "Biblioteca de formularios".
4 – Establecemos los parámetros de creación y hacemos clic en "Crear".

Publicar formularios InfoPath con código en SharePoint: Crear biblioteca de formularios

5 – Una vez que hicimos clic en "Crear" vamos a ser redirigidos a la biblioteca. En "Configuración" elegimos "Configuración de Biblioteca de formularios" y bajo "Configuración general" seleccionamos "Configuración avanzada". En "Tipos de contenido" elegimos "Sí" para "¿Desea permitir la administración de tipos de contenido?" y en "Documentos habilitados por el explorador" elegimos "Mostrar como página Web". Hacemos clic en "Aceptar".

Publicar formularios InfoPath con código en SharePoint: Configuración biblioteca de formularios

6 – Volvemos a "Personalizar Formularios InfoPath". Bajo "Tipos de contenido" hacemos clic en "Agregar a partir de tipos de contenido de sitio" y buscamos en el grupo "Microsoft Office InfoPath" el tipo de contenido "Plantilla1_Publicada". Lo agregamos haciendo clic en "Agregar" y hacemos clic en "Aceptar".

Publicar formularios InfoPath con código en SharePoint: Agregar tipo de contenido

7 – Bajo "Tipos de contenido" hacemos clic en "Formulario" y elegimos "Eliminar este tipo de contenido".

Probar

1 – Vamos a la biblioteca "Formularios InfoPath" y hacemos clic en "Nuevo", "Plantilla1_Publicada".

Publicar formularios InfoPath con código en SharePoint: Nuevo plantilla

Tags:

WSS 3.0/MOSS 2007

ASP.NET Web Parts: crear un EditorPart y desplegar el Web Part en SharePoint

by Nicolás Ferreira 30. June 2008 00:00

Todo desarrollo Web Part que tenga como objetivo WSS 3.0/MOSS 2007 debería utilizar como clase base "System.Web.UI.WebControls.WebParts.WebPart". "Microsoft.SharePoint.WebPartPages.WebPart" se mantiene principalmente por un tema de compatibilidad con versiones anteriores de SharePoint. En este artículo vamos a crear un simple Web Part que tendrá una propiedad que el usuario establecerá para recibir respuesta. El despliegue del ensamblado y su configuración lo haremos manualmente.

Abrimos Visual Studio y creamos un nuevo proyecto de tipo Biblioteca de clases al que llamaremos "WebPartSaludo". Antes de empezar a codificar:

1 – Agregamos una referencia al ensamblado "System.Web". Esta referencia es necesaria para hacer que nuestro Web Part herede de "System.Web.UI.WebControls.WebParts.WebPart".
2 – Renombramos "Class1.cs" por "SaludoWebPart.cs" que automáticamente debería cambiar el nombre de la clase. Si no es así cambiamos el nombre de la clase.
3 – Hacemos heredar la clase "SaludoWebPart" de "System.Web.UI.WebControls.WebParts.WebPart".

Nuestro Web Part pedirá al usuario un nombre y mostrará por ejemplo "Hola Nicolás. Buenos días.". Vamos a la vista de código de "SaludoWebPart.cs" y creamos una propiedad llamada "Nombre" de tipo string. También tenemos que crear un campo que será el que realmente almacenará la información:

        private string _Nombre;

        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }

Tenemos que aplicar a la propiedad "Nombre" los siguientes atributos:
- "System.Web.UI.WebControls.WebParts.Personalizable". Se utiliza para que el "Web Part Manager" persista el valor de la propiedad. Si especificamos un "PersonalizationScope" de "Shared" los cambios que se hagan a la propiedad "Nombre" serán vistos por todos los usuarios (todos compartirán el mismo valor). Si especificamos "User" cada usuario podrá especificar un valor y ese valor será visto sólo por el usuario que lo especificó.
- "System.Web.UI.WebControls.WebParts.WebBrowsable". Si especificamos un valor de "true" la propiedad se mostrará utilizando una interfaz de usuario de edición genérica. La interfaz se crea basándose en el tipo de la propiedad. El objetivo de este artículo es mostrar nuestro propio EditorPart y presentar la propiedad al usuario como nos parezca mejor, por lo que al atributo "WebBrowsable" le vamos a especificar "false".

        private string _Nombre;
        [Personalizable(PersonalizationScope.User)]
        [WebBrowsable(false)]
        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }

Para proveer al usuario de una interfaz para que pueda ingresar un nombre tenemos que crear una clase que herede de "System.Web.UI.WebControls.WebParts.EditorPart". Un "EditorPart" se utiliza para editar controles Web Part una vez que el usuario puso el Web Part en modo de edición. Puede haber más de un "EditorPart" por Web Part y estarán en una colección de tipo "EditorPartCollection".

1 – Creamos una clase llamada "SaludoEditorPart".
2 – Cambiamos la declaración de la clase "SaludoEditorPart" para que sea pública.
3 – La hacemos heredar de "System.Web.UI.WebControls.WebParts.EditorPart".

Hay tres métodos principales que tenemos que sobrescribir/implementar:
- "CreateChildControls" – Lo utilizamos para agregar controles al "EditorPart". Los controles que agreguemos aquí le aparecerán al usuario para que pueda editar el Web Part.
- "SyncChanges" (abstract) – El propósito del método es recuperar los valores actuales del control "WebPart" al que se hace referencia en la propiedad "WebPartToEdit" y actualizar los campos del control "EditorPart" con dichos valores para que un usuario pueda editarlos.
- "ApplyChanges" (abstract) – El propósito del método es guardar los valores que ha especificado un usuario para un control "EditorPart" en las propiedades correspondientes del control "WebPart" al que se hace referencia en la propiedad "WebPartToEdit".

using System.Web.UI.WebControls.WebParts;
using System.Web.UI.WebControls;
using System.Web.UI;

namespace WebPartSaludo
{
    public class SaludoEditorPart : EditorPart
    {
        private TextBox _Nombre = null;
        public SaludoWebPart SaludoWebPart
        {
            get { return ((SaludoWebPart)this.WebPartToEdit); }
        }
        public override bool ApplyChanges()
        {
            this.EnsureChildControls();
            this.SaludoWebPart.Nombre = this._Nombre.Text;
            return true;
        }
        public override void SyncChanges()
        {
            this.EnsureChildControls();
            this._Nombre.Text = this.SaludoWebPart.Nombre;
        }
        protected override void CreateChildControls()
        {
            this.Controls.Add(new LiteralControl("Ingrese su nombre:"));
            this._Nombre = new TextBox();
            this._Nombre.Width = new Unit("100%");
            this.Controls.Add(this._Nombre);
        }
    }
}

Una vez que tenemos nuestro "SaludoEditorPart" vamos a la vista de código para "SaludoWebPart.cs". Tenemos que sobrescribir el método "CreateEditorParts" y especificar nuestro "SaludoEditorPart". Además, tenemos que mostrar el saludo al usuario (sobrescribiremos el método CreateChildControls). Toda la clase quedaría así:

using System.Collections.Generic;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI;

namespace WebPartSaludo
{
    public class SaludoWebPart : WebPart
    {
        private string _Nombre;
        [Personalizable(PersonalizationScope.User)]
        [WebBrowsable(false)]
        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }
        public override EditorPartCollection CreateEditorParts()
        {
            List<EditorPart> lEditorParts = new List<EditorPart>(1);
            EditorPart lSaludoEditorPart = new SaludoEditorPart();
            lSaludoEditorPart.ID = string.Concat(this.ID, "_SaludoEditorPart");
            lEditorParts.Add(lSaludoEditorPart);
            EditorPartCollection lBaseEditorParts = base.CreateEditorParts();
            return new EditorPartCollection(lBaseEditorParts, lEditorParts);
        }
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            if (!string.IsNullOrEmpty(this.Nombre))
            {
                this.Controls.Add(new LiteralControl(string.Format("Hola {0}. Buenos días.", this.Nombre)));
            }
        }
    }
}

Desplegar el ensamblado

1 – Compilamos y copiamos "WebPartSaludo.dll" y lo pegamos en la carpeta "Bin" de nuestro directorio virtual de SharePoint para la aplicación a la que le queremos agregar el Web Part (en mi caso "C:\Inetpub\wwwroot\wss\VirtualDirectories\2667\bin").
2 – Abrimos el archivo "web.config" de nuestro directorio virtual de SharePoint para la aplicación a la que le queremos agregar el Web Part (en mi caso "C:\Inetpub\wwwroot\wss\VirtualDirectories\2667\web.config") y bajo "SafeControls" agregamos la siguiente entrada:

<SafeControl Assembly="WebPartSaludo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Namespace="WebPartSaludo" TypeName="*" Safe="True" />

3 – Ejecutamos "IISRESET".

Mostrando el Web Part

1 – Vamos al sitio Web y en el menú "Acciones del sitio" seleccionamos "Configuración del sitio". Bajo la sección "Galerías" elegimos "Elementos Web". En "Galería de elementos Web" hacemos clic en "Nuevo" y buscamos "WebPartSaludo.SaludoWebPart". Lo marcamos y hacemos clic en "Llenar galería".
2 – Volvemos a la página principal y en el menú "Acciones del sitio" seleccionamos "Editar Página". En la zona que nos interese agregar el Web Part seleccionamos la opción de "Agregar elemento Web". Esto abrirá el catálogo con los Web Parts que tenemos disponibles. Buscamos "SaludoWebPart" que generalmente aparece bajo la sección "Varios" y lo marcamos. Luego hacemos clic en "Agregar".
3 – Una vez que quedó agregado el Web Part a la página, en el menú "editar" seleccionamos "Modificar elemento Web compartido". En el panel "SaludoWebPart" que se abrió a la derecha vamos a ver nuestro "EditorPart". Ingresamos un nombre y hacemos clic en "Aceptar". Salimos del modo edición.

ASP.NET Web Parts: crear un EditorPart y desplegar el Web Part en SharePoint: Agregar elemento Web

ASP.NET Web Parts: crear un EditorPart y desplegar el Web Part en SharePoint: Ingrese su nombre

ASP.NET Web Parts: crear un EditorPart y desplegar el Web Part en SharePoint

Tags:

WSS 3.0/MOSS 2007

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services

by Nicolás Ferreira 29. June 2008 00:00

Excel Services permite mostrar y manejar libros de Excel sin que el usuario tenga que instalar controles ActiveX. Está formado por tres componentes: Excel Calculation Services (ECS), Excel Web Access (EWA) y Excel Web Services (EWS). Podemos pensar en Excel Calculation Services como el componente encargado de cargar libros, ejecutar cálculos, en fin, el motor. Excel Web Access convierte libros a HTML permitiendo a usuarios interactuar con ellos. Por último, Excel Web Services permite acceder a libros programáticamente.

Creando una biblioteca de documentos para almacenar libros de Excel

1 – Estando en el sitio que queremos utilizar para mostrar libros de Excel, vamos al menú "Acciones del sitio" y seleccionamos "Configuración del sitio". Bajo la sección "Administración de sitios" elegimos "Bibliotecas y listas del sitio" y por último "Crear nuevo contenido".

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Crear Biblioteca de documentos

2 – Como queremos almacenar documentos de Excel, seleccionamos la opción "Biblioteca de documentos" y la configuramos de la siguiente manera:

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Configurar Biblioteca de documentos

3 – Creamos la biblioteca haciendo clic en "Crear". Esta acción nos llevará a la biblioteca que acabamos de crear en donde seleccionaremos la opción "Nuevo documento" que pertenece al menú "Nuevo".

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Nuevo documento

Creando un libro de Excel para exponer en un sitio SharePoint

1 – Luego de haber hecho clic sobre "Nuevo documento", se debería abrir Microsoft Office Excel 2007. Como ejemplo introducimos los siguientes datos:

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Creando hoja de Excel (Paso 1)

Para comprobar la "efectividad" de "Excel Calculation Services", los datos en el rango "=Hoja1!$B$1:$B$12" se calculan utilizando la función "ALEATORIO()".

2 – Seleccionamos el rango "=Hoja1!$A$1:$B$12" y en la solapa "Insertar" bajo "Gráficos" insertamos un gráfico.

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Creando hoja de Excel (Paso 2)

3 – Seleccionamos el gráfico que acabamos de insertar y en la solapa "Presentación" bajo "Propiedades" le damos un nombre al gráfico de "MiGrafico". Esto es importante ya que después a "Excel Web Access" le vamos a decir que muestre el objeto "MiGrafico" y no todo el libro.

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Creando hoja de Excel (Paso 3)

4 – En el botón de Office bajo "Guardar como" seleccionamos "Libro de Excel". De nombre le ponemos "2008.xlsx" y hacemos clic en "Guardar".

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Creando hoja de Excel (Paso 4)

Configurando Excel Services

1 – Comprobamos si el servicio "Excel Calculation Services" se encuentra corriendo. Para eso, abrimos "Administración central de SharePoint 3.0" y en la solapa "Operaciones" bajo la sección "Topología y servicios" seleccionamos "Servicios del servidor". En la página "Servicios del servidor" buscamos "Excel Calculation Services" que debería decir "Iniciado". Si no es así, lo iniciamos.

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Servicios del servidor

2 – "Excel Services" es un servicio compartido. Para configurarlo tenemos que ir a la herramienta de administración del servicio compartido que la aplicación Web a la que le creamos la biblioteca de documentos tiene asociado. En mi caso, el servicio compartido es "SharedServices1". Accedemos a dicha herramienta haciendo clic sobre "SharedServices1" en el menú de la izquierda.

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Servicio compartido

3 – Una vez que estamos en "Administración de servicios compartidos: SharedServices1", bajo la sección "Configuración de Excel Services" seleccionamos "Ubicaciones de archivos de confianza". Para que "Excel Services" pueda procesar datos, tenemos que especificar una ubicación que nosotros consideremos como segura. Hacemos clic en "Agregar Ubicación de archivo de confianza". Configuramos la sección "Ubicación" de la siguiente manera dejando todo lo demás con sus valores predeterminados. Después, hacemos clic en "Aceptar":

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Agregar ubicación de archivo de confianza

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Ubicaciones de archivos de confianza

Mostrando el gráfico

1 – Vamos al sitio Web y en el menú "Acciones del sitio" seleccionamos "Editar Página". En la zona que nos interese agregar el gráfico seleccionamos la opción de "Agregar elemento Web". Esto abrirá el catálogo con los Web Parts que tenemos disponibles. Buscamos "Excel Web Access" que generalmente aparece bajo la sección "Datos profesionales" y lo marcamos. Luego hacemos clic en "Agregar".

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Agregar elemento Web

Atención: si "Excel Web Access" no aparece en el catálogo, una de las posibles causas es que la característica "Características de sitio de Office SharePoint Server Enterprise" no se encuentre activa. Para activarla, en el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de la colección de sitios" elegimos "Características de la colección de sitios". Buscamos "Características de colección de sitios de Office SharePoint Server Enterprise" y hacemos clic en el botón "Activar".

2 – Una vez que quedó agregado el Web Part a la página, seleccionamos la opción "Haga clic aquí para abrir el panel de herramientas". Bajo la sección "Visualización de libros" tenemos que configurar "Libro" y "Elemento con nombre". En "Libro" buscamos el documento "2008.xlsx" que guardamos en la biblioteca "Ganancia Anual"("/Ganancia Anual/2008.xlsx"). Como queremos mostrar sólo el gráfico y no todo el libro, en "Elemento con nombre" escribimos "MiGrafico" (sin las comillas) que fue el nombre que le dimos al gráfico. Adicionalmente podemos configurar otros aspectos del Web Part como por ejemplo si queremos que aparezca la barra de herramientas o no, etc. Cuando terminamos hacemos clic en "Aceptar" y luego salimos del modo edición.

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Seleccione un libro

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Configurar Web Part

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services: Gráfico

Tags:

WSS 3.0/MOSS 2007

Panel de discusión – obtener discusiones y mensajes (Got a Match?)

by Nicolás Ferreira 28. June 2008 00:00

A continuación lo que se intenta hacer es interpretar discusiones que pertenezcan a una lista de SharePoint de tipo "Panel de discusión" siguiendo la misma estructura de presentación que nos brinda la vista "Encadenada".

Panel de discusión - obtener discusiones y mensajes (Got a Match?): Vista Encadenada

Investigando saqué las siguientes conclusiones:

1 – El tipo de lista "Panel de discusión" por defecto viene con dos tipos de contenido: "Discusión" y "Mensaje".

Panel de discusión - obtener discusiones y mensajes (Got a Match?): Tipo de contenido Discusión

Panel de discusión - obtener discusiones y mensajes (Got a Match?): Tipo de contenido Mensaje

2 – El tipo de contenido "Discusión" tiene como padre al tipo de contenido "Folder".
3 – Ambos tipos de contenido comparten las columnas "Asunto" y "Cuerpo", la diferencia es que el tipo de contenido "Mensaje" tiene la columna "Asunto" oculta ya que no tiene sentido que una respuesta (o mensaje) tenga asunto.
4 – El campo "ThreadIndex" que está oculto y que pertenece a las listas de tipo "Panel de discusión" contiene el nivel al que pertenece un elemento en la jerarquía discusión/mensajes.
5 – La vista "Encadenada" da la sensación de jerarquía utilizando imágenes cuyo ancho podemos calcular así: ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]).Length – 46.

    <!-- Discusión - Cuerpo de la discusión. -->
    <img height="1" width="0" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1 -->
    <img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1.1 -->
    <img height="1" width="20" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1.1.1 -->
    <img height="1" width="30" alt="" src="/_layouts/images/blank.gif" />
    <!-- 2 -->
    <img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />

La estructura "ThreadIndex" de los elementos que aparecen en la primer imagen se ve así:

0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855 – Discusión – Cuerpo de la discusión.
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA85 – 1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC8 – 1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC800000142DF – 1.1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF8550000073CDB – 2

Se me ocurrió el siguiente modelo:

Panel de discusión - obtener discusiones y mensajes (Got a Match?): Diagrama de clases

Como un panel de discusión por defecto lo único que puede contener son discusiones y mensajes (o respuestas) y dado que una discusión y un mensaje comparten ciertos atributos, la clase "ElementoPanelDeDiscusion" sirve como clase base para "Discusion" y "Mensaje".

Notar que la clase "Discusion" tiene la propiedad "Asunto" mientras que la clase "Mensaje" no la tiene.

La propiedad "Padre" en la clase "Discusion" devuelve null, mientras que en la clase "Mensaje" puede devolver "Discusion" o "Mensaje" (ya que un mensaje puede pertenecer a una discusión o a una respuesta dentro de otra respuesta). Por último, "PanelDeDiscusion" es la clase que hace todo el trabajo.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace NicolasFerreira.SharePoint
{
    public abstract class ElementoPanelDeDiscusion
    {
        private string _Cuerpo;

        public string Cuerpo
        {
            get { return _Cuerpo; }
        }
        internal List<Mensaje> _Mensajes;

        public ReadOnlyCollection<Mensaje> Mensajes
        {
            get { return _Mensajes.AsReadOnly(); }
        }

        public abstract ElementoPanelDeDiscusion Padre { get; }

        internal string _ThreadIndex;
        internal ElementoPanelDeDiscusion(string pCuerpo)
        {
            _Mensajes = new List<Mensaje>();
            _Cuerpo = pCuerpo;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace NicolasFerreira.SharePoint
{
    public class Discusion : ElementoPanelDeDiscusion
    {
        private string _Asunto;

        public string Asunto
        {
            get { return _Asunto; }
        }
        internal Discusion(string pCuerpo, string pAsunto)
            : base(pCuerpo)
        {
            _Asunto = pAsunto;
        }

        public override ElementoPanelDeDiscusion Padre
        {
            get { return null; }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace NicolasFerreira.SharePoint
{
    public class Mensaje : ElementoPanelDeDiscusion
    {
        internal ElementoPanelDeDiscusion _Padre = null;

        public override ElementoPanelDeDiscusion Padre
        {
            get { return _Padre; }
        }

        internal Mensaje(string pCuerpo)
            : base(pCuerpo)
        {
        }
    }
}

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

namespace NicolasFerreira.SharePoint.Listas
{
    /// <summary>
    /// Clase encargada de manipular el tipo de lista "Panel de discusión".
    /// </summary>
    public static class PanelDeDiscusion
    {
        /// <summary>
        /// Método fábrica que devolverá un objeto de tipo Discusion o Mensaje dependiendo de T.
        /// </summary>
        /// <typeparam name="T">Tipo de objeto que se desea obtener. El tipo debe heredar de la clase ElementoPanelDeDiscusion.</typeparam>
        /// <param name="pSPListItem">SPListItem que contiene la información necesaria como para crear el tipo de objeto especificado en T.</param>
        private static T CrearElementoPanelDeDiscusion<T>(SPListItem pSPListItem)
             where T : ElementoPanelDeDiscusion
        {
            ElementoPanelDeDiscusion lElementoPanelDeDiscusion = null;
            if (typeof(T) == typeof(Discusion))
            {
                lElementoPanelDeDiscusion = new Discusion((string)pSPListItem[SPBuiltInFieldId.Body],
                    pSPListItem[SPBuiltInFieldId.Title].ToString());
            }
            else if (typeof(T) == typeof(Mensaje))
            {
                lElementoPanelDeDiscusion = new Mensaje((string)pSPListItem[SPBuiltInFieldId.Body]);
            }
            if (lElementoPanelDeDiscusion != null)
            {
                lElementoPanelDeDiscusion._ThreadIndex = ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]);
            }
            return lElementoPanelDeDiscusion as T;
        }
        /// <summary>
        /// Método que devolverá todas las discusiones que haya en una lista.
        /// </summary>
        /// <param name="pSPListPanelDeDiscusion">SPList que contiene la información necesaria como para obtener todas las discusiones.</param>
        public static List<SPListItem> ObtenerDiscusiones(SPList pSPListPanelDeDiscusion)
        {
            List<SPListItem> lDiscusiones = new List<SPListItem>();
            /*Para obtener todas las discusiones de la lista,
             * tenemos que utilizar la propiedad Folders de SPList.*/
            foreach (SPListItem lSPListItemDiscusion in pSPListPanelDeDiscusion.Folders)
            {
                lDiscusiones.Add(lSPListItemDiscusion);
            }
            return lDiscusiones;
        }
        /// <summary>
        /// Método que devolverá un objeto de tipo Discusion con la discusión (incluyendo mensajes y mensajes anidados).
        /// </summary>
        /// <param name="pSPListItemDiscusion">SPListItem que contiene la información necesaria de la discusión que queremos interpretar.</param>
        public static Discusion ObtenerDiscusion(SPListItem pSPListItemDiscusion)
        {
            Discusion lDiscusion = null;
            /*Para obtener todos los mensajes (incluidos los anidados) lo hago con un SPQuery y establezco
             * la propiedad Folder al valor de pSPListItemDiscusion.Folder que es la carpeta de la discusión.*/
            SPQuery lSPQuery = new SPQuery();
            lSPQuery.Folder = pSPListItemDiscusion.Folder;
            SPListItemCollection lSPListItemCollection = pSPListItemDiscusion.ParentList.GetItems(lSPQuery);
            List<SPListItem> lSPListItemMensajes = new List<SPListItem>();
            foreach (SPListItem lSPListItemMensaje in lSPListItemCollection)
            {
                lSPListItemMensajes.Add(lSPListItemMensaje);
            }
            lSPListItemMensajes.Sort(new ThreadIndexComparer()); //Ordeno los mensajes.
            lDiscusion = CrearElementoPanelDeDiscusion<Discusion>(pSPListItemDiscusion); //Creo la discusión para luego agregar mensajes.
            Mensaje lMensaje = null; //Lo utilizo para tener como referencia el último mensaje que agregué a una discusión o a un mensaje.
            foreach (SPListItem lSPListItemMensaje in lSPListItemMensajes)
            {
                //ThreadIndex contiene el nivel al que pertenece el mensaje.
                string lThreadIndex = ((string)lSPListItemMensaje[SPBuiltInFieldId.ThreadIndex]);
                if (lMensaje != null)
                /*Si anteriormente agregué un mensaje en alguna parte entonces puede que el
                 * nuevo mensaje tenga relación o no con el agregado.*/
                {
                    bool lContinuar = true;
                    while (lContinuar)
                    {
                        if (lThreadIndex.StartsWith(lMensaje._ThreadIndex))
                        /*Verifico si existe una relación
                         * (esto es posible gracias a la estructura del campo ThreadIndex).*/
                        {
                            /*Como existe una relación con el mensaje agregado anteriormente,
                             * entonces el nuevo mensaje es hijo del anterior.*/
                            Mensaje lNuevoMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
                            lNuevoMensaje._Padre = lMensaje; //Le indico al nuevo mensaje que su padre va a ser el agregado anteriormente.
                            lMensaje._Mensajes.Add(lNuevoMensaje);
                            lMensaje = lNuevoMensaje; //¡El nuevo mensaje pasa a ser antiguo!
                            lContinuar = false;
                        }
                        else
                        {
                            /*Como no hay relación entre el mensaje agregado anteriormente y el que intento agregar ahora,
                             * me voy moviendo "hacia atrás" en busca de un mensaje que tenga relación
                             * con el que intento agregar.*/
                            if (lMensaje.Padre is Mensaje)
                            {
                                lMensaje = ((Mensaje)lMensaje.Padre);
                            }
                            else
                            {
                                //Ningún mensaje es padre del mensaje que intento agregar, entonces el mensaje es hijo de la discusión.
                                lMensaje = null;
                                lContinuar = false;
                            }
                        }
                    }
                }
                if (lMensaje == null)
                {
                    lMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
                    lDiscusion._Mensajes.Add(lMensaje);
                    lMensaje._Padre = lDiscusion;
                }
            }
            return lDiscusion;
        }
    }
}

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

namespace NicolasFerreira.SharePoint
{
    internal class ThreadIndexComparer : IComparer<SPListItem>
    {
        #region IComparer Members

        public int Compare(SPListItem x, SPListItem y)
        {
            string xThreadIndex = ((string)x[SPBuiltInFieldId.ThreadIndex]);
            string yThreadIndex = ((string)y[SPBuiltInFieldId.ThreadIndex]);

            return string.Compare(xThreadIndex, yThreadIndex);
        }

        #endregion
    }
}

Panel de discusión - obtener discusiones y mensajes (Got a Match?): Inspección

Descargar el código.

Tags:

WSS 3.0/MOSS 2007

Limpiar un control Contact Selector Control

by Nicolás Ferreira 27. June 2008 00:00

Microsoft Office SharePoint Server 2007 viene con un control ActiveX llamado "Contact Selector Control" que podemos utilizar en formularios de Microsoft Office InfoPath 2007 y que permite a los usuarios especificar y validar usuarios y/o grupos.

Si queremos proveer al usuario con un botón para que pueda eliminar los usuarios y/o grupos que especificó lo que podemos hacer es lo siguiente.

1 – Una vez que tenemos todo configurado para utilizar "Contact Selector Control", en el "Panel de tareas" buscamos "Origen de datos" y seleccionamos la opción "Copiar XPath" del grupo al que el control "Contact Selector Control" está enlazado.

Limpiar un control Contact Selector Control: Copiar XPath

2 – Sobre un botón que tengamos en el formulario nos vamos a las propiedades y elegimos la opción de "Editar código del formulario..."
3 – Creado el manejador para el evento Clicked del botón (en mi caso "CTRL3_5_Clicked") ponemos el siguiente código:

        public void CTRL3_5_Clicked(object sender, ClickedEventArgs e)
        {
            // Escriba aquí su código.
            XPathNavigator lgpContactSelector =
                this.CreateNavigator().SelectSingleNode("Expresión XPath", this.NamespaceManager);

            lgpContactSelector.SetValue(string.Empty);
        }

Al método "SelectSingleNode" le pegamos la expresión XPath que obtuvimos en el primer paso (por ejemplo "/my:misCampos/my:gpContactSelector").

Tags:

WSS 3.0/MOSS 2007

Desactivar la papelera de reciclaje en una aplicación Web

by Nicolás Ferreira 26. June 2008 00:00

SharePoint tiene una papelera de reciclaje "dos etapas" que puede recibir listas, elementos de listas, bibliotecas de documentos y documentos. Se le dice "dos etapas" porque cuando el usuario final elimina un elemento, ese elemento va a la papelera de reciclaje del usuario que lo eliminó. Si el usuario decide "eliminar" ese documento de su papelera, ahí entra en juego la segunda etapa. En ese caso, el elemento va a la papelera de reciclaje de la colección de sitios. Cualquier administrador de una colección de sitios puede restaurar elementos o eliminarlos definitivamente.

Para desactivar la papelera de reciclaje en una aplicación Web tenemos que:

1 – Abrir "Administración central de SharePoint 3.0" (generalmente se encuentra en Panel de control/Herramientas administrativas).
2 – Una vez que estamos en "Administración central de SharePoint 3.0" vamos a la solapa "Administración de aplicaciones" y bajo la sección "Administración de aplicaciones Web de SharePoint" hacemos clic en "Configuración general de la aplicación Web".
3 – En "Configuración general de la aplicación Web" elegimos la aplicación Web que queremos modificar y luego en "Papelera de reciclaje" vamos a ver que tenemos dos opciones. Elegimos la opción de "Desactivado" y hacemos clic en "Aceptar".

Desactivar la papelera de reciclaje en una aplicación Web

Tags:

WSS 3.0/MOSS 2007

Agregar actividades en tiempo de ejecución a un flujo de trabajo (desde el mismo flujo de trabajo) y validar dichos cambios

by Nicolás Ferreira 27. May 2008 00:00

Cuando aplicamos cambios en tiempo de ejecución a un flujo de trabajo, puede que estos cambios contengan errores. Para demostrar esto vamos a crear un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample3.

Arrastramos sobre el diseñador visual de flujos de trabajo una instancia de la actividad CodeActivity y la soltamos en la superficie de diseño. Hacemos clic derecho sobre codeActivity1 y seleccionamos la opción de menú "Generate Handlers".

Igual al artículo anterior ("Agregar actividades en tiempo de ejecución a un flujo de trabajo"), vamos a proceder a crear una instancia de la clase WorkflowChanges y agregar una actividad.

En la vista de código para Workflow1.cs, buscamos el método codeActivity1_ExecuteCode y ponemos lo siguiente:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            WorkflowChanges lWorkflowChanges = new WorkflowChanges(this);

            CodeActivity lCodeActivity = new CodeActivity();

            lWorkflowChanges.TransientWorkflow.Activities.Add(lCodeActivity);

            this.ApplyWorkflowChanges(lWorkflowChanges);
        }

Como verán, al constructor de WorkflowChanges le pasamos "this" ya que la actividad raíz es el mismo flujo de trabajo en el que estamos codificando (Workflow1 hereda de SequentialWorkflowActivity). Luego, instanciamos y agregamos a las actividades del flujo de trabajo una instancia de la clase CodeActivity. Por último, llamamos al método ApplyWorkflowChanges (también de Workflow1).

Si ejecutamos la aplicación así como está, obtendremos el siguiente error:

Agregar actividades en tiempo de ejecución a un flujo de trabajo (desde el mismo flujo de trabajo) y validar dichos cambios: WorkflowValidationFailedException

Esto se debe a que en este caso, CodeActivity requiere que manejemos el evento ExecuteCode (e intencionalmente no lo hicimos).

Modificamos el código del método codeActivity1_ExecuteCode dejándolo de la siguiente manera:

 

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            WorkflowChanges lWorkflowChanges = new WorkflowChanges(this);

            CodeActivity lCodeActivity = new CodeActivity();

            lWorkflowChanges.TransientWorkflow.Activities.Add(lCodeActivity);

            ValidationErrorCollection lValidationErrorCollection =
                lWorkflowChanges.Validate();
            if (lValidationErrorCollection.Count > 0)
            {
                foreach (ValidationError lValidationError in lValidationErrorCollection)
                {
                    Console.WriteLine(lValidationError.ErrorText);
                }
            }
            else
            {
                this.ApplyWorkflowChanges(lWorkflowChanges);
            }
        }

Si ejecutamos la aplicación, vamos a ver que no tira excepción pero nos deja el siguiente mensaje:

Agregar actividades en tiempo de ejecución a un flujo de trabajo (desde el mismo flujo de trabajo) y validar dichos cambios

Tags:

.NET Development | Windows Workflow Foundation (WF)

Agregar actividades en tiempo de ejecución a un flujo de trabajo

by Nicolás Ferreira 25. May 2008 00:00

Abrimos Visual Studio y creamos un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample2. Por defecto, Visual Studio nos deja en el diseñador visual de flujos de trabajo para Workflow1.cs. Si no es así, vamos al diseñador para Workflow1.cs. Agregamos las siguientes actividades:

- Tipo: CodeActivity – Nombre: codeActivity1 – Descripción: La vamos a utilizar para escribir la cadena "Actividad 1" a la salida de la consola.
- Tipo: SuspendActivity – Nombre: suspendActivity1 – Descripción: SuspendActivity suspende la ejecución de un flujo de trabajo. WorkflowRuntime levantará el evento WorkflowSuspended avisando que la instancia del flujo de trabajo ha entrado en estado de suspensión, lo que nos va a permitir hacer modificaciones al flujo de trabajo y luego resumir la ejecución.
- Tipo: SequenceActivity – Nombre: sequenceActivity1 – Descripción: SequenceActivity es una actividad compuesta que ejecuta actividades hijas en el orden en que fueron agregadas. En este caso, vamos a utilizarla como "placeholder" para agregar la nueva actividad.
- Tipo: CodeActivity – Nombre: codeActivity2 – Descripción: La vamos a utilizar para escribir la cadena "Actividad 3" a la salida de la consola.

Agregar actividades en tiempo de ejecución a un flujo de trabajo: Diseñador (Paso 1)

Hacemos clic derecho sobre codeActivity1 y seleccionamos la opción de menú "Generate Handlers" (que nos crea un nuevo manejador para el evento ExecuteCode). Volvemos al diseñador y repetimos el mismo paso pero para la actividad codeActivity2.

Agregar actividades en tiempo de ejecución a un flujo de trabajo: Diseñador (Paso 2)

Nos vamos a la vista de código y en los métodos generados (codeActivity1_ExecuteCode y codeActivity2_ExecuteCode) ponemos lo siguiente:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Actividad 1");
        }

        private void codeActivity2_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Actividad 3");
        }

Hecho esto, agregamos una nueva clase al proyecto que llamaremos "EscribirMensajeActivity". Cambiamos la declaración de la clase para que sea pública y la hacemos heredar de Activity (System.Workflow.ComponentModel.Activity).

Activity es la clase base para todas las actividades. El método principal en el que ponemos la lógica de ejecución es Execute y es llamado por el runtime de flujos de trabajo.

Sobrescribimos entonces el método Execute y ponemos lo siguiente:

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Console.WriteLine(Mensaje);
            return ActivityExecutionStatus.Closed;
        }

Tenemos que crear un campo de tipo string llamado _Mensaje que va a servir como contenedor para la cadena que se va a escribir a la salida de la consola. Luego, creamos la propiedad para el campo _Mensaje.

        private string _Mensaje;

        public string Mensaje
        {
            get { return _Mensaje; }
            set { _Mensaje = value; }
        }

Por último vamos a hacer unas modificaciones al código que Visual Studio generó en Program.cs. Nos suscribimos al evento WorkflowSuspended de workflowRuntime:

        static void Main(string[] args)
        {
            using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                workflowRuntime.WorkflowSuspended +=
                    new EventHandler<WorkflowSuspendedEventArgs>(workflowRuntime_WorkflowSuspended);

            ...

En el método encargado de manejar el evento WorkflowSuspended (workflowRuntime_WorkflowSuspended) ponemos lo siguiente:

        static void workflowRuntime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
        {
            WorkflowChanges lWorkflowChanges =
                new WorkflowChanges(e.WorkflowInstance.GetWorkflowDefinition());

            EscribirMensajeActivity lEscribirMensajeActivity = new EscribirMensajeActivity();
            lEscribirMensajeActivity.Mensaje = "Actividad 2";
            ((CompositeActivity)lWorkflowChanges.TransientWorkflow.Activities["sequenceActivity1"]).
                Activities.Add(lEscribirMensajeActivity);

            e.WorkflowInstance.ApplyWorkflowChanges(lWorkflowChanges);
            e.WorkflowInstance.Resume();
        }

Cuando queremos hacer cambios a un flujo de trabajo, los tenemos que hacer utilizando la clase WorkflowChanges que toma como parámetro la actividad raíz. En este caso la actividad raíz para la instancia del flujo de trabajo que queremos modificar la sacamos con el método GetWorkflowDefinition de WorkflowInstance que lo tenemos en "e" (WorkflowSuspendedEventArgs). Después, creamos una nueva instancia de nuestra actividad (EscribirMensajeActivity) y establecemos la propiedad Mensaje.

La propiedad TransientWorkflow de WorkflowChanges devuelve una copia exacta de la estructura del flujo de trabajo que vamos a utilizar para ubicar la actividad sequenceActivity1 que convertiremos a CompositeActivity para poder agregar la actividad hija.

Una vez agregada la actividad hija, llamamos al método ApplyWorkflowChanges de WorkflowInstance pasando la instancia de WorkflowChanges. Finalmente, llamamos al método Resume para continuar con la ejecución del flujo de trabajo.

Compilamos y ejecutamos. Deberíamos ver lo siguiente:

Agregar actividades en tiempo de ejecución a un flujo de trabajo

Tags:

.NET Development | Windows Workflow Foundation (WF)