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)

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)

WorkflowRuntime + WorkflowInstance

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

Como dije en la introducción, uno de los trabajos que WorkflowRuntime tiene es recibir y dar notificaciones. Recibe notificaciones desde las instancias de flujos de trabajo (WorkflowInstance) que está ejecutando. Nos provee con eventos que podemos utilizar para procesar esas notificaciones que recibió y también para procesar notificaciones que él mismo emite. Podemos separar los eventos que WorkflowRuntime expone en dos partes:

Eventos propios de WorkflowRuntime:
Started – El motor de flujos de trabajo inició. Podemos iniciar el motor de flujos de trabajo llamando al método StartRuntime de WorkflowRuntime o iniciando una instancia de un flujo de trabajo. Cuando llamamos al método Start de WorkflowInstance, si el motor de flujos de trabajo no está en ejecución, automáticamente la instancia lo inicia. Además, se inician los servicios y se establece la propiedad IsStarted (de WorkflowRuntime) en True.
Stopped – El motor de flujos de trabajo se detuvo. Esto pasa si llamamos al método StopRuntime de WorkflowRuntime. Cuando llamamos a StopRuntime, el motor detiene todos los servicios que tiene asociados.

Eventos que WorkflowRuntime recibe para después notificarlos:
WorkflowAborted – Notifica cuando una instancia de un flujo de trabajo ha sido abortada. Abortamos una instancia en ejecución de un flujo de trabajo llamando al método Abort de WorkflowInstance.
WorkflowCompleted – Notifica cuando una instancia de un flujo de trabajo ha completado de forma normal (eso es, no se llamó a Abort ni se propagó una excepción).
WorkflowCreated – Notifica que se ha creado una instancia de un flujo de trabajo.
WorkflowIdled – Notifica que la instancia de un flujo de trabajo ha entrado en estado de ausencia. Este evento es muy importante. A parte de avisar que una instancia ha entrado en ausencia, si está habilitado el servicio de persistencia aquí podemos llamar a los métodos Unload y TryUnload de WorkflowInstance para persistir la instancia del flujo de trabajo. También es aquí en donde podemos aplicar cambios dinámicos al flujo de trabajo desde fuera de él (por ejemplo, desde la aplicación que lo aloja). Cuando un flujo de trabajo entra en estado de ausencia, va a salir de ese estado por su cuenta, no tenemos que hacer nada. Entra en estado de ausencia con actividades como DelayActivity y HandleExternalEventActivity.
WorkflowLoaded – Notifica que la instancia de un flujo de trabajo que estaba persistida se ha cargado en memoria.
WorkflowPersisted – Notifica que la instancia de un flujo de trabajo ha sido persistida.
WorkflowResumed – Notifica que la ejecución de la instancia de un flujo de trabajo continúa luego de haber estado suspendida. Resumimos la ejecución llamando al método Resume de WorkflowInstance.
WorkflowStarted – Notifica que comenzó la ejecución de una instancia de un flujo de trabajo. Para correr una instancia llamamos al método Start de WorkflowInstance.
WorkflowSuspended – Notifica que se ha suspendido la ejecución de una instancia de un flujo de trabajo. Suspendemos la ejecución llamando al método Suspend de WorkflowInstance. El motor de flujos de trabajo también es capaz de suspender la ejecución por ejemplo cuando tiene que aplicar un cambio. Al suspender la instancia luego podemos reanudarla. No interviene la persistencia ya que no se considera a la instancia como ausente. Podemos utilizar la actividad SuspendActivity (cuando de antemano sabemos que precisamos modificar la instancia del flujo de trabajo) para suspender la ejecución, aplicar un cambio y luego reanudar la ejecución.
WorkflowTerminated – Notifica que la instancia de un flujo de trabajo ha terminado de forma anormal. La aplicación que aloja la instancia del flujo de trabajo pudo haber llamado al método Terminate de WorkflowInstance, puede que haya una actividad TerminateActivity, o se propagó una excepción.
WorkflowUnloaded – Notifica que se descargó la instancia de un flujo de trabajo.

Tags:

.NET Development | Windows Workflow Foundation (WF)

Windows Workflow Foundation – Introducción

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

Pero todo esto ¿para qué? ¿Qué es Windows Workflow Foundation? ¿Un flujo de trabajo secuencial? ¿Actividades? ¿WorkflowRuntime?

Como lo dice su nombre, Windows Workflow Foundation es una tecnología que nos permite trabajar con flujos de trabajo. La realidad es que podemos hacer cualquier cosa con WF.

Por ejemplo: tenemos un requisito para la aplicación que estamos construyendo que dice que tenemos que obtener un grupo de personas que está almacenado en una base de datos. Para cada una de esas personas pertenecientes al grupo, enviar un correo electrónico y esperar por la respuesta de cada persona para continuar. Si todos respondieron que están de acuerdo, procedemos a imprimir una factura.

Normalmente crearíamos métodos, clases para obtener las personas, podríamos crear un servicio Web que reciba el identificador de la persona y si quiere aceptar o rechazar. Una vez que tengamos todas las respuestas, analizamos y si las respuestas fueron todas que sí (que están de acuerdo) entonces tendríamos otro método o como sea que imprima la factura.

Todo este procesamiento secuencial a grandes rasgos lo podríamos modelar con WF teniendo una actividad ReplicatorActivity que se encargue de enviar un correo a cada persona. Es más, podríamos crear una actividad personalizada de tipo compuesta que se encargue de esa parte. Siguiente, podríamos exponer nuestro flujo de trabajo como servicio Web utilizando WebServiceInputActivity a la espera de las respuestas de las personas y luego otra actividad personalizada que imprima la factura en el caso que las respuestas sean de aceptación.

Todo esto lo podríamos hacer a mano, pero también utilizando WF. WF tiene la ventaja que corre cada instancia de un flujo de trabajo en un hilo independiente al de nuestra aplicación principal. Además, tiene servicios que nos permiten descargar instancias de un flujo de trabajo cuando están en estado de ausencia (en el ejemplo que di anteriormente, el flujo de trabajo podría entrar en ausencia cuando se encuentra esperando a que se llame al método Web de aceptación o rechazo) y dado un evento externo levantar esas instancias que estaban persistidas (deshidratar y rehidratar) y ponerlas en ejecución nuevamente manteniendo el mismo estado que tenían antes de ser descargadas.

Los servicios y las actividades built-in que vienen con WF facilitan nuestro trabajo.

Utilizamos flujos de trabajo en todo momento. Declaramos dichos flujos de trabajo utilizando un modelo intuitivo que cualquiera puede entender. Por un lado tenemos reglas de negocio y por otro el código implementación que creamos para satisfacer dichas reglas. Hay una clara separación entre el modelo visual y la implementación.

Existen dos tipos de flujos de trabajo que podemos crear: secuenciales y máquina de estados. Los secuenciales ejecutan nuestras actividades en forma de secuencia, primero una, después otra y así sucesivamente. Por el contrario, tenemos los flujos de trabajo de máquina de estados (en otro artículo profundizaremos en ellos). Están diseñados especialmente cuando se requiere de mucha interacción con eventos externos. Los secuenciales tienen como actividad padre a SequentialWorkflowActivity.

SequentialWorkflowActivity es una actividad compuesta. Compuesta quiere decir que acepta actividades hijas. Las actividades compuestas heredan de CompositeActivity (System.Workflow.ComponentModel.CompositeActivity).

Las actividades son bloques de construcción que utilizamos para definir nuestros flujos de trabajo. Es la unidad básica de trabajo.

La definición de un flujo de trabajo es lo que construimos, las actividades que arrastramos, las propiedades que establecemos, etc. La instancia de un flujo de trabajo es lo que está en ejecución (o se va a ejecutar). WorkflowRuntime crea dichas instancias.

WorkflowRuntime es lo que utilizamos para iniciar el motor de flujos de trabajo, agregar los servicios (como el de persistencia por ejemplo), crear instancias de flujos de trabajo, recibir notificaciones desde las instancias de un flujo de trabajo en ejecución y desde el mismo motor. Podemos iniciar el motor sólo una vez por AppDomain.

Tags:

.NET Development | Windows Workflow Foundation (WF)

Creando un simple flujo de trabajo

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

Voy a construir un simple flujo de trabajo de tipo secuencial en donde explicaré algunas de las actividades built-in que vienen con WF. Como entorno de desarrollo voy a utilizar Visual Studio 2008.

El flujo de trabajo va a recibir como parámetro una lista de nombres que previamente debemos pasar de la siguiente manera: "WorkflowSample1 Daniel Eduardo Jorge Manuel Santiago" al ejecutar la aplicación. Para cada nombre, vamos a escribir un saludo. La idea es mostrar cómo pasar parámetros a un flujo de trabajo desde la aplicación que lo aloja y además entender para qué sirven las actividades CodeActivity y ReplicatorActivity.

CodeActivity: especificamos un método al que después CodeActivity llamará. Se intenta que el código del método que hayamos especificado sea liviano ya que CodeActivity bloquea la ejecución de la instancia del flujo de trabajo hasta que nuestro método termina de ejecutarse. Si queremos implementar lógica de negocio lo mejor es crear una actividad personalizada.

ReplicatorActivity: dada una colección de objetos que implemente IList, crea y ejecuta sólo una actividad hija tantas veces como objetos haya en la colección. Es similar a la sentencia for each. Tenemos eventos como ChildInitialized que se dispara después que una actividad hija es inicializada y ChildCompleted después que ReplicatorActivity termina de correr la instancia de una actividad hija.

Para aclarar todo esto, abrimos Visual Studio y creamos un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample1. 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.

Lo primero es arrastrar desde el Toolbox una instancia de la actividad Replicator y colocarla en la superficie de diseño (en donde dice "Drop Activities to create a Sequential Workflow"). Inmediatamente vamos a ver un signo de exclamación que nos indica que la actividad Replicator debe contener una actividad. Todas estas pistas visuales que el diseñador muestra las actividades las proveen creando una clase que derive de la clase ActivityValidator y algunas otras cosas más que en otro artículo escribiré.

Creando un simple flujo de trabajo: Diseñador (Paso 1)

Luego, dentro de la actividad Replicator (replicatorActivity1) que acabamos de agregar, agregamos una instancia de la actividad Code, quedando de la siguiente manera.

Creando un simple flujo de trabajo: Diseñador (Paso 2)

Ahora podemos observar que replicatorActivity1 no tiene más errores pero sí los tiene codeActivity1, y es que no hemos especificado el método que codeActivity1 tiene que llamar cuando comience su ejecución. Por ahora lo dejamos así.

Vamos a concentrarnos en replicatorActivity1. La aplicación que aloja el flujo de trabajo va a pasar la colección de nombres a una propiedad declarada a nivel de flujo de trabajo a la que replicatorActivity1 estará atada. Entonces para crear esa propiedad y a la vez decirle a replicatorActivity1 que se ate a esa propiedad, vamos a buscar la propiedad InitialChildData de replicatorActivity1 y vamos a hacer clic en el botón "..." que aparece cuando nos paramos sobre InitialChildData en el explorador de propiedades. Una vez hecho esto, nos va a aparecer un cuadro de diálogo llamado "Bind 'InitialChildData' to an activity's property" y va a estar seleccionada la solapa "Bind to an existing member", pero como nosotros no tenemos el miembro (propiedad) ya creado, nos vamos a la solapa "Bind to a new member" que nos va a pedir un nombre de miembro y si queremos crearlo como campo (variable) o propiedad. Vamos a poner de nombre "Nombres" y dejamos seleccionado "Create Property" para crear el nuevo miembro como propiedad.

Creando un simple flujo de trabajo: Crear propiedad Nombres

Bien. Con esto lo que conseguimos es que replicatorActivity1 pueda recorrer cada elemento que haya dentro de la propiedad Nombres que si nos fijamos Visual Studio fue suficientemente inteligente como para darse cuenta que el tipo de dato que da soporte a la propiedad tiene que ser de tipo "System.Collections.IList".

Creando un simple flujo de trabajo: Tipo de dato de propiedad Nombres

Ahora, nos vamos a las propiedades de replicatorActivity1, seleccionamos para que se muestren los eventos y hacemos doble clic sobre ChildInitialized para crear un manejador de evento. En el manejador de evento que se acaba de crear, vamos a agregar el siguiente código.

        private void replicatorActivity1_ChildInitialized(object sender, ReplicatorChildEventArgs e)
        {
            _Nombre = e.InstanceData.ToString();
        }

Tenemos que recordar que ChildInitialized es disparado por cada elemento que haya en la colección Nombres. Luego que se dispare ChildInitialized, se va a ejecutar la actividad codeActivity1 que va a tener que mostrar el saludo para el nombre actual sobre el que replicatorActivity1 está parado, es por eso que en el método replicatorActivity1_ChildInitialized le asignamos a una variable llamada "_Nombre" que aún no hemos creado, el valor de la propiedad InstanceData que trae ReplicatorChildEventArgs (que nos da el elemento sobre el que replicatorActivity1 está parado). Declaramos la variable "_Nombre" de la siguiente manera:

        private string _Nombre;

Nos vamos al diseñador visual de flujos de trabajo y para los eventos de codeActivity1 (que tiene uno solo) hacemos doble clic sobre ExecuteCode para crear un manejador de evento. Una vez creado el manejador, lo dejamos de la siguiente manera:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine(string.Format("Hola {0}. Buenos días.", _Nombre));
        }

Una vez que el método codeActivity1_ExecuteCode terminó de escribir la cadena a la salida de la consola, la actividad va a cerrar (terminar) y replicatorActivity1 seguirá con el próximo elemento en la lista y volverá a crear una nueva instancia de codeActivity1.

Por último, nos queda pasar los nombres desde la aplicación que aloja al flujo de trabajo a la instancia del flujo de trabajo. Nos vamos al código de Program.cs en donde vamos a ver que Visual Studio agregó código como para iniciar el runtime de flujos de trabajo e iniciar una instancia de Workflow1 (nuestro flujo de trabajo). Le vamos a hacer una modificación. La línea:

        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowSample1.Workflow1));

La vamos a remplazar por las siguientes:

            Dictionary<string, object> lParametros = new Dictionary<string, object>();
            lParametros.Add("Nombres", args);
            WorkflowInstance instance =
                workflowRuntime.CreateWorkflow(typeof(WorkflowSample1.Workflow1), lParametros);

El método CreateWorkflow de WorkflowRuntime tiene una sobrecarga que acepta un tipo que va a ser el tipo de flujo de trabajo que vamos a querer ejecutar, y un diccionario del tipo Dictionary<string, object> que va a tener como clave el nombre de la propiedad que intentamos establecer y como valor el valor que queremos establecer para esa propiedad. En este caso para la propiedad "Nombres" que se encuentra definida en el flujo de trabajo vamos a establecer el valor de "args" que son los argumentos que vienen cuando ejecutamos la aplicación consola.

Compilamos y ejecutamos de la siguiente manera: "WorkflowSample1 Daniel Eduardo Jorge Manuel Santiago". Si todo va bien, deberíamos ver lo siguiente:

Creando un simple flujo de trabajo: Ejecutar

Tags:

.NET Development | Windows Workflow Foundation (WF)

Convert.ChangeType (Nullable<>)

by Nicolás Ferreira 13. January 2008 00:00

Recientemente me encontré con un código que utilizando Reflection establecía valores a las propiedades de un objeto. El valor que iba a establecer en la propiedad tenía como tipo de dato String y la propiedad podía llegar a tener otro, entonces, utilizaba Convert.ChangeType cuando el tipo de dato de la propiedad era diferente a String:

    class Persona
    {
        int _Edad;

        public int Edad
        {
            get { return _Edad; }
            set { _Edad = value; }
        }
        string _Nombre;

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

        public string Apellido
        {
            get { return _Apellido; }
            set { _Apellido = value; }
        }
        Nullable<int> _CantidadHijos;

        public Nullable<int> CantidadHijos
        {
            get { return _CantidadHijos; }
            set { _CantidadHijos = value; }
        }
        Nullable<int> _CantidadHermanos;

        public Nullable<int> CantidadHermanos
        {
            get { return _CantidadHermanos; }
            set { _CantidadHermanos = value; }
        }
    }

        static void Main(string[] args)
        {
            StringDictionary lDiccionarioPropiedadesValores = new StringDictionary();
            lDiccionarioPropiedadesValores.Add("Edad", "18");
            lDiccionarioPropiedadesValores.Add("Nombre", "Nicolás");
            lDiccionarioPropiedadesValores.Add("Apellido", "Ferreira");
            lDiccionarioPropiedadesValores.Add("CantidadHijos", "");
            lDiccionarioPropiedadesValores.Add("CantidadHermanos", "2");
            Persona lPersona = new Persona();
            foreach (DictionaryEntry lEntradaDiccionario in lDiccionarioPropiedadesValores)
            {
                PropertyInfo lPropiedadPersona = lPersona.GetType().GetProperty(lEntradaDiccionario.Key.ToString(), System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
                if (lPropiedadPersona != null)
                {
                    object lNuevoValorPropiedad = lEntradaDiccionario.Value;
                    if ((lNuevoValorPropiedad != null) && (lPropiedadPersona.PropertyType != lEntradaDiccionario.Value.GetType()))
                    {
                        lNuevoValorPropiedad = Convert.ChangeType(lNuevoValorPropiedad, lPropiedadPersona.PropertyType);
                    }
                    lPropiedadPersona.SetValue(lPersona, lNuevoValorPropiedad, null);
                }
            }
        }

El problema al correr este código es que cuando la propiedad tiene como tipo de dato a Nullable, el método Convert.ChangeType falla. La solución puede ser la siguiente:

        static void Main(string[] args)
        {
            StringDictionary lDiccionarioPropiedadesValores = new StringDictionary();
            lDiccionarioPropiedadesValores.Add("Edad", "18");
            lDiccionarioPropiedadesValores.Add("Nombre", "Nicolás");
            lDiccionarioPropiedadesValores.Add("Apellido", "Ferreira");
            lDiccionarioPropiedadesValores.Add("CantidadHijos", "");
            lDiccionarioPropiedadesValores.Add("CantidadHermanos", "2");
            Persona lPersona = new Persona();
            foreach (DictionaryEntry lEntradaDiccionario in lDiccionarioPropiedadesValores)
            {
                PropertyInfo lPropiedadPersona = lPersona.GetType().GetProperty(lEntradaDiccionario.Key.ToString(), System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
                if (lPropiedadPersona != null)
                {
                    object lNuevoValorPropiedad = lEntradaDiccionario.Value;
                    if ((lNuevoValorPropiedad != null) && (lPropiedadPersona.PropertyType != lEntradaDiccionario.Value.GetType()))
                    {
                        if ((lPropiedadPersona.PropertyType.IsGenericType) && (lPropiedadPersona.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
                        {
                            if (string.IsNullOrEmpty(lNuevoValorPropiedad.ToString()))
                            {
                                lNuevoValorPropiedad = null;
                            }
                            else
                            {
                                lNuevoValorPropiedad = Convert.ChangeType(lNuevoValorPropiedad, new NullableConverter(lPropiedadPersona.PropertyType).UnderlyingType);
                            }
                        }
                        else
                        {
                            lNuevoValorPropiedad = Convert.ChangeType(lNuevoValorPropiedad, lPropiedadPersona.PropertyType);
                        }
                    }
                    lPropiedadPersona.SetValue(lPersona, lNuevoValorPropiedad, null);
                }
            }
        }

El trabajo lo hace la clase NullableConverter, lo otro es simplemente validación.

Tags:

.NET Development

Microsoft .NET Remoting

by Nicolás Ferreira 22. November 2007 00:00

Uno de los temas que hay que preparar para el examen "70-529 - TS: Microsoft .NET Framework 2.0 - Distributed Application Development" es Microsoft .NET Remoting. .NET Remoting es una de las opciones que tenemos para crear aplicaciones distribuidas. Nos permite llamar a objetos que se encuentran en diferentes procesos y en diferentes máquinas como si estuvieran dentro de la misma aplicación. No es recomendable utilizar .NET Remoting cuando la comunicación se va a establecer a través de Internet o la comunicación no es entre plataformas .NET. Un ejemplo de una situación en la que podemos utilizar .NET Remoting puede ser el siguiente:

- Un servicio Windows que puede ser configurado por un administrador y que permite ver el estado de las diferentes funcionalidades que tiene mediante una consola administrativa que es una aplicación de Windows Forms.

En este caso podemos utilizar .NET Remoting con el canal de comunicación IPC (Inter-process communication).

Existen tres tipos de objetos remotos de los cuales dos son de activación servidor y uno de activación cliente. Los objetos de activación servidor los crea el servidor sólo cuando se precisan (cuando el cliente invoca el primer método en el objeto proxy y no cuando utiliza "new" o Activator.GetObject). Dentro de activación servidor tenemos objetos Singleton y objetos SingleCall. Para los objetos Singleton siempre habrá una única instancia para todos los clientes. Para los objetos SingleCall se crea un nuevo objeto por cada invocación que hace el cliente a un método. Cuando el objeto SingleCall no se necesita más, se descarta inmediatamente. Los objetos de activación cliente se crean en el servidor cuando el cliente utiliza "new" o Activator.CreateInstance. El objeto que recibe el cliente es dedicado a pesar que reside en el servidor. Un administrador de tiempo de vida que se basa en arrendamiento administra los objetos para asegurar que los objetos expiren y que no mantengan recursos innecesarios.

Podemos alojar objetos remotos en aplicaciones de consola, Windows Forms, ASP.NET, servicios Windows.

En cuanto a los canales de comunicación .NET Framework 2.0 trae tres que son TCP, HTTP, IPC. Existen algunas reglas que tienen que ver con los canales como por ejemplo tenemos que tener registrado un canal antes de que el propio objeto remoto se registre y cualquiera pueda llamar a sus métodos.

Para que el cliente pueda crear una instancia de un objeto remoto, la aplicación servidor y la aplicación cliente tienen que compartir el mismo ensamblado. Lo que se hace generalmente es crear una biblioteca de clases que contenga interfaces para que sean implementadas por la aplicación servidor y la aplicación cliente. De esta manera la aplicación cliente no está enterada de la implementación que hace la aplicación servidor.

Volviendo al ejemplo anterior, podemos tener un proyecto de tipo biblioteca de clases llamado "AplicacionRemota.Comun" que tenga una interfaz llamada "IEstadoServidor" y que se vea así:

    public interface IEstadoServidor
    {
        int UsuariosConectados { get; }
    }

El proyecto servidor ("AplicacionRemota.Servidor") que para este ejemplo va a ser una aplicación consola, va a tener una clase llamada EstadoServidor que va a implementar la interfaz IEstadoServidor y va a heredar de la clase MarshalByRefObject:

    public class EstadoServidor : MarshalByRefObject, IEstadoServidor
    {
        #region IEstadoServidor Members

        public int UsuariosConectados
        {
            get
            {
                return new Random().Next();
            }
        }

        #endregion
    }

Notar que tenemos que agregar una referencia al ensamblado "System.Runtime.Remoting" en la aplicación servidor.

Nos queda registrar el canal y el tipo remoto para que pueda aceptar solicitudes desde la aplicación cliente. Una de las formas que tenemos es utilizando las clases ChannelServices y RemotingConfiguration. En el "Main" de la aplicación servidor ponemos lo siguiente:

        static void Main(string[] args)
        {
            IpcServerChannel lIpcServerChannel = new IpcServerChannel("NombreCanal");
            ChannelServices.RegisterChannel(lIpcServerChannel, false);
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(EstadoServidor),
                "URIObjeto", WellKnownObjectMode.Singleton);
            Console.ReadLine();
            ChannelServices.UnregisterChannel(lIpcServerChannel);
        }

El proyecto cliente ("AplicacionRemota.Cliente") que para este ejemplo también va a ser una aplicación consola, va a tener el siguiente código en el método "Main":

        static void Main(string[] args)
        {
            IEstadoServidor lIEstadoServidor = (IEstadoServidor)Activator.GetObject
                (typeof(IEstadoServidor), "IPC://NombreCanal/URIObjeto");
            Console.Write(lIEstadoServidor.UsuariosConectados);
            Console.ReadLine();
        }

Tags:

.NET Development

PrivateFontCollection (¿Font Embedding?)

by Nicolás Ferreira 3. October 2007 00:00

Como no podía ser de otra manera el .NET Framework nos trae la clase PrivateFontCollection que pertenece al nombre de espacio System.Drawing.Text que sirve para cargar fuentes en nuestra aplicación sin necesidad de que se encuentren instaladas en el sistema. La clase tiene dos métodos principales:

  • AddFontFile - Agrega una fuente del archivo especificado a esta colección PrivateFontCollection.
  • AddMemoryFont - Agrega una fuente incluida en la memoria del sistema a esta colección PrivateFontCollection.

El ejemplo que voy a mostrar a continuación utiliza el método AddMemoryFont y trabaja con la clase Marshal.

Marshal (Clase):

Proporciona una colección de métodos para asignar memoria no administrada, copiar bloques de memoria no administrados y convertir los tipos administrados en no administrados, así como otros métodos diversos que se utilizan al interactuar con código no administrado.

        PrivateFontCollection _PrivateFontCollection = new PrivateFontCollection();
        private void AddEmbeddedFontToPrivateFontCollection(string pManifestResourceName)
        {
            using (Stream lFontEmbeddedResource = Assembly.GetExecutingAssembly().GetManifestResourceStream(pManifestResourceName))
            {
                byte[] lFontData = new byte[lFontEmbeddedResource.Length];
                lFontEmbeddedResource.Read(lFontData, 0, lFontData.Length);
                IntPtr lUnmanagedMemoryPointer = Marshal.AllocHGlobal(lFontData.Length);
                Marshal.Copy(lFontData, 0, lUnmanagedMemoryPointer, lFontData.Length);
                _PrivateFontCollection.AddMemoryFont(lUnmanagedMemoryPointer, lFontData.Length);
                Marshal.FreeHGlobal(lUnmanagedMemoryPointer);
            }
        }
        private void Form_Load(object sender, EventArgs e)
        {
            AddEmbeddedFontToPrivateFontCollection("WindowsApplication.AlienMushrooms.ttf");
            if (_PrivateFontCollection.Families.Length > 0)
            {
                FontFamily lFontFamily = _PrivateFontCollection.Families[0];
                if (lFontFamily.IsStyleAvailable(FontStyle.Regular))
                {
                    Font lFont = new Font(lFontFamily, 48, FontStyle.Regular);
                    this.label1.Font = lFont;
                }
            }
            this.label1.Text = ".NET Framework";
        }

_PrivateFontCollection es una variable que está declarada a nivel de modulo. Esto es algo muy importante que tenemos que tener en cuenta dependiendo de lo que vayamos a hacer (recuerden que las variables que se encuentran declaradas a nivel de modulo tienen un tiempo de vida hasta que la ejecución del programa finaliza), de lo contrario podemos obtener excepciones como: AccessViolationException (Attempted to read or write protected memory. This is often an indication that other memory is corrupt.)

Llamando al método IsStyleAvailable de la clase FontFamily nos estamos asegurando que la fuente tenga el estilo que queremos representar, de lo contrario se producirá una excepción.

Algunos se estarán preguntando ¿de qué sirve todo esto? Lo que se me ocurre en este momento a modo de ejemplo es un sitio Web que tiene que generar un CAPTCHA con una fuente especial pero nuestro sitio Web va a estar alojado en un servidor Web que no es nuestro y por consiguiente puede o no tener la fuente que queremos utilizar para generarlo.

Tags:

.NET Development