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)

Las plantillas de Visual Studio 2005 desaparecen después de instalar "Visual Studio 2005 Extensions for Windows Workflow Foundation (EN)"

by Nicolás Ferreira 5. April 2008 00:00

Cuando instalé "Visual Studio 2005 Extensions for Windows Workflow Foundation (EN)" y abrí Visual Studio para crear un nuevo proyecto me encontré con que las plantillas que tenía instaladas habían desaparecido quedando sólo las de Workflow. Buscando encontré que correr "devenv /InstallVSTemplates" podía solucionar el problema pero no fue así. Abrí la carpeta que Visual Studio utiliza por defecto para levantar las plantillas de proyecto ("C:\Archivos de programa\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplates\") y me encontré con la siguiente estructura:

|
+—CSharp
| +—Database
| | | TemplateIndex.vstdir
| | |
| | \—1033
| | SqlServerLib.zip
| |
| +—Office
| | | office.vstdir
| | |
| | \—1033
| | VSTOExcelTemplate.zip
| | VSTOExcelWorkbook.zip
| | VSTOOutlookAddin.zip
| | VSTOWordDocument.zip
| | VSTOWordTemplate.zip
| |
| +—SmartDevice
| | | TemplateIndex.vstdir
| | |
| | +—PocketPC2003
| | | | TemplateIndex.vstdir
| | | |
| | | \—1033
| | | PocketPC2003-ClassLibrary.zip
| | | PocketPC2003-ConsoleApplication.zip
| | | PocketPC2003-EmptyProject.zip
| | | PocketPC2003-WindowsApplication.zip
| | | PocketPC2003-WindowsControlLibrary.zip
| | | PocketPCV1-ClassLibrary.zip
| | | PocketPCV1-ConsoleApplication.zip
| | | PocketPCV1-EmptyProject.zip
| | | PocketPCV1-WindowsApplication.zip
| | |
| | +—Smartphone2003
| | | | TemplateIndex.vstdir
| | | |
| | | \—1033
| | | Smartphone2003-ClassLibrary.zip
| | | Smartphone2003-ConsoleApplication.zip
| | | Smartphone2003-EmptyProject.zip
| | | Smartphone2003-WindowsApplication.zip
| | |
| | \—WindowsCE
| | | TemplateIndex.vstdir
| | |
| | \—1033
| | WindowsCE-ClassLibrary.zip
| | WindowsCE-ConsoleApplication.zip
| | WindowsCE-EmptyProject.zip
| | WindowsCE-WindowsApplication.zip
| | WindowsCE-WindowsControlLibrary.zip
| |
| +—Starter Kits
| | | StarterKits.vstdir
| | |
| | \—1033
| | MovieCollection.zip
| | ScreenSaver.zip
| |
| +—Test
| | | Test.vstdir
| | |
| | \—1033
| | TestProject.zip
| |
| +—Web
| | \—1033
| | WebApplicationProject.zip
| | WebServiceProject.zip
| |
| +—Windows
| | | Windows.VSTDIR
| | |
| | \—1033
| | ClassLibrary.zip
| | ConsoleApplication.zip
| | CRWindowsApplication.zip
| | EmptyProject.zip
| | WebControl.zip
| | WindowsApplication.zip
| | WindowsControl.zip
| | WindowsService.zip
| |
| \—Workflow
| ActivityLibrary.zip
| EmptyWorkflowProject.zip
| SequentialWorkflowConsoleApplication.zip
| SequentialWorkflowLibrary.zip
| StateMachineWorkflowConsoleApplication.zip
| StateMachineWorkflowLibrary.zip
| TemplateIndex.vstdir
| |
+—VisualBasic
| +—Database
| | | TemplateIndex.vstdir
| | |
| | \—1033
| | SqlServerLib.zip
| |
| +—Office
| | | office.vstdir
| | |
| | \—1033
| | VSTOExcelTemplate.zip
| | VSTOExcelWorkbook.zip
| | VSTOOutlookAddin.zip
| | VSTOWordDocument.zip
| | VSTOWordTemplate.zip
| |
| +—SmartDevice
| | | TemplateIndex.vstdir
| | |
| | +—PocketPC2003
| | | | TemplateIndex.vstdir
| | | |
| | | \—1033
| | | PocketPC2003-ClassLibrary.zip
| | | PocketPC2003-ConsoleApplication.zip
| | | PocketPC2003-EmptyProject.zip
| | | PocketPC2003-WindowsApplication.zip
| | | PocketPC2003-WindowsControlLibrary.zip
| | | PocketPCV1-ClassLibrary.zip
| | | PocketPCV1-ConsoleApplication.zip
| | | PocketPCV1-EmptyProject.zip
| | | PocketPCV1-WindowsApplication.zip
| | |
| | +—Smartphone2003
| | | | TemplateIndex.vstdir
| | | |
| | | \—1033
| | | Smartphone2003-ClassLibrary.zip
| | | Smartphone2003-ConsoleApplication.zip
| | | Smartphone2003-EmptyProject.zip
| | | Smartphone2003-WindowsApplication.zip
| | |
| | \—WindowsCE
| | | TemplateIndex.vstdir
| | |
| | \—1033
| | WindowsCE-ClassLibrary.zip
| | WindowsCE-ConsoleApplication.zip
| | WindowsCE-EmptyProject.zip
| | WindowsCE-WindowsApplication.zip
| | WindowsCE-WindowsControlLibrary.zip
| |
| +—Starter Kits
| | | StarterKits.vstdir
| | |
| | \—1033
| | MovieCollection.zip
| | ScreenSaver.zip
| |
| +—Test
| | | Test.vstdir
| | |
| | \—1033
| | TestProject.zip
| |
| +—Web
| | \—1033
| | WebApplicationProject.zip
| | WebServiceProject.zip
| |
| +—Windows
| | | Windows.VSTDIR
| | |
| | \—1033
| | ClassLibrary.zip
| | ConsoleApplication.zip
| | CRWindowsApplication.zip
| | EmptyProject.zip
| | WebControl.zip
| | WindowsApplication.zip
| | WindowsControl.zip
| | WindowsService.zip
| |
| \—Workflow
| ActivityLibrary.zip
| EmptyWorkflowProject.zip
| SequentialWorkflowConsoleApplication.zip
| SequentialWorkflowLibrary.zip
| StateMachineWorkflowConsoleApplication.zip
| StateMachineWorkflowLibrary.zip
| TemplateIndex.vstdir
|

Notarán que la carpeta "Workflow" tanto dentro de "CSharp" como dentro de "VisualBasic" no sigue el mismo patrón que las demás, que es tener una carpeta llamada "1033" (U.S. English en mi caso ya que mi versión de Visual Studio está en inglés) y un archivo ".vstdir". Es dentro de "1033" que van los archivos .zip que en el caso de Workflow están sueltos. Dejé la estructura de la siguiente manera:

| \—Workflow
| | TemplateIndex.vstdir
| |
| \—1033
| ActivityLibrary.zip
| EmptyWorkflowProject.zip
| SequentialWorkflowConsoleApplication.zip
| SequentialWorkflowLibrary.zip
| StateMachineWorkflowConsoleApplication.zip
| StateMachineWorkflowLibrary.zip
|

Después fui a la carpeta "ItemTemplates" ("C:\Archivos de programa\Microsoft Visual Studio 8\Common7\IDE\ItemTemplates\") y me encontré con la siguiente estructura:

|
+—CSharp
| | ActivityWithCode.zip
| | ActivityWithCodeSeparation.zip
| | SequentialWorkflow.zip
| | SequentialWorkflowCodeSeparation.zip
| | StateMachineWorkflow.zip
| | StateMachineWorkflowCodeSeparation.zip
| | WorkflowWebConfig.zip
| |
| \—1033
| AboutBox.zip
| AppConfig.zip
| AppConfigInternal.zip
| AssemblyInfo.zip
| AssemblyInfoInternal.zip
| BitMap.zip
| BrowserFile.zip
| Class.zip
| ClassDiagram.zip
| CodeFile.zip
| Component.zip
| ContentPage.zip
| CrystalReport.zip
| Cursor.zip
| CustomControl.zip
| DataSet.zip
| EmptyDatabase.zip
| Form.zip
| GenericTest.zip
| GlobalAsax.zip
| Handler.zip
| HelpTest.zip
| HTMLPage.zip
| Icon.zip
| Installer.zip
| Interface.zip
| JScript.zip
| LoadTest.zip
| ManualTestTextFormat.zip
| ManualTestWordFormat.zip
| MasterPage.zip
| MDIParent.zip
| NestedWebConfig.zip
| NETCFv1-Class.zip
| NETCFv1-Component.zip
| NETCFv1-CustomControl.zip
| NETCFv1-Form.zip
| NETCFv1-Resource.zip
| NETCFv2-BlankDatabase.zip
| NETCFv2-Class.zip
| NETCFv2-Component.zip
| NETCFv2-CustomControl.zip
| NETCFv2-DesignTimeAttributes.zip
| NETCFv2-Form.zip
| NETCFv2-UserControl.zip
| OrderedTest.zip
| RecordWebTest.zip
| Report.zip
| Resource.zip
| ResourceInternal.zip
| Service.zip
| Settings.zip
| SettingsInternal.zip
| SimpleUnitTest.zip
| SiteMap.zip
| SkinFile.zip
| SqlAggregate.zip
| SqlFunction.zip
| SqlStoredProcedure.zip
| SqlTestScript.zip
| SqlTrigger.zip
| SqlUserDefinedType.zip
| StyleSheet.zip
| TextFile.zip
| UnitTestWizard.zip
| UserControl.zip
| VBScript.zip
| Visualizer.zip
| VSTOActionsPaneControl.zip
| WebClass.zip
| WebConfig.zip
| WebCustomControl.zip
| WebForm.zip
| WebHtmlPage.zip
| WebService.zip
| WebuserControl.zip
| WinScript.zip
| XMLFile.zip
| XMLSchema.zip
| XSLTFile.zip
|
+—VisualBasic
| | ActivityWithCode.zip
| | ActivityWithCodeSeparation.zip
| | SequentialWorkflow.zip
| | SequentialWorkflowCodeSeparation.zip
| | StateMachineWorkflow.zip
| | StateMachineWorkflowCodeSeparation.zip
| | WorkflowWebConfig.zip
| |
| \—1033
| AboutBox.zip
| AppConfiguration.zip
| AppConfigurationInternal.zip
| AssemblyInfoInternal.zip
| Bitmap.zip
| BrowserFile.zip
| Class.zip
| ClassDiagram.zip
| CodeFile.zip
| ComClass.zip
| Component.zip
| ContentPage.zip
| CrystalReport.zip
| cursor.zip
| CustomControl.zip
| Dataset.zip
| Dialog.zip
| EmptyDatabase.zip
| Explorer.zip
| Form.zip
| GenericTest.zip
| GlobalAsax.zip
| Handler.zip
| HelpTest.zip
| HTMLPage.zip
| Icon.zip
| Installer.zip
| Interface.zip
| JScript.zip
| LoadTest.zip
| LoginForm.zip
| ManualTestTextFormat.zip
| ManualTestWordFormat.zip
| MasterPage.zip
| MDIParent.zip
| Module.zip
| MTSTransactionClass.zip
| NestedWebConfig.zip
| NETCFv1-Class.zip
| NETCFv1-Component.zip
| NETCFv1-CustomControl.zip
| NETCFv1-Form.zip
| NETCFv1-Resource.zip
| NETCFv2-BlankDatabase.zip
| NETCFv2-Class.zip
| NETCFv2-Component.zip
| NETCFv2-CustomControl.zip
| NETCFv2-DesignTimeAttributes.zip
| NETCFv2-Form.zip
| NETCFv2-UserControl.zip
| OrderedTest.zip
| RecordWebTest.zip
| Report.zip
| resource.zip
| ResourceInternal.zip
| Settings.zip
| SettingsInternal.zip
| SimpleUnitTest.zip
| SiteMap.zip
| SkinFile.zip
| SplashScreen.zip
| SqlAggregate.zip
| SqlFunction.zip
| SqlStoredProcedure.zip
| SqlTestScript.zip
| SqlTrigger.zip
| SqlUserDefinedType.zip
| StyleSheet.zip
| Text.zip
| UnitTestWizard.zip
| UserControl.zip
| UserService.zip
| VSTOActionsPaneControl.zip
| WebConfig.zip
| webcontrol.zip
| WebForm.zip
| WebHtmlPage.zip
| WebService.zip
| WebuserControl.zip
| XMLFile.zip
| XSDSchema.zip
| XSLTFile.zip
|

Entonces moví los .zip de Workflow que están "sueltos" a la carpeta "1033". Finalmente, borré las carpetas "ProjectTemplatesCache" e "ItemTemplatesCache" ("C:\Archivos de programa\Microsoft Visual Studio 8\Common7\IDE\") que son las que realmente utiliza Visual Studio para levantar las plantillas, y corrí "devenv /InstallVSTemplates". Abrí Visual Studio y vi que las plantillas que tenía anteriormente estaban en su lugar y además aparecían las nuevas de Workflow en su respectiva categoría.

Tags:

off-topic

Windows SharePoint Services vs. Microsoft Office SharePoint Server

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

Cuando empecé a trabajar con Microsoft SharePoint la primera duda que me surgió fue ¿qué diferencia existe entre Windows SharePoint Services y Microsoft Office SharePoint Server?

Para empezar, ambos productos fueron desarrollados por el equipo de Microsoft Office. Windows SharePoint Services trabaja con Windows Server 2003 pero también puede instalarse en Windows Server 2008. El modelo de licenciamiento de Windows SharePoint Services depende de las licencias del sistema operativo, mientras que Microsoft Office SharePoint Server es un producto separado que tiene su propio modelo de licenciamiento que incluye licencias del lado del servidor y licencias de acceso de clientes (CALs).

La "plataforma base" es Windows SharePoint Services, mientras que Microsoft Office SharePoint Server aporta valor agregado. Microsoft Office SharePoint Server (MOSS) incluye funcionalidades que Windows SharePoint Services (WSS) no trae como por ejemplo: Perfiles de usuario. A su vez, MOSS, tiene diferentes versiones: Standard Edition y Enterprise Edition.

¿Cuál es la tecnología de SharePoint más adecuada para usted?

Tags:

WSS 3.0/MOSS 2007

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

(Son you were mistaken), You are obsolete - Parte I

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

A menudo me enfrento con diferentes tipos de personas (es decir, gente común, ingenieros de sistemas, etc.) y veo que no tienen ni idea (y lo peor de todo es que se cierran en lo que creen o en lo que aprendieron y no están abiertos a nuevas posibilidades) de parámetros, tipos de valor, tipos de referencia, la clase String.

Mucha gente anda diciendo por ahí que String no es una clase. Algunos me hablan de "Data Type" (más tarde vamos a ver qué es realmente un Data Type). Otros le asocian una característica similar que la de int o bool (tipos primitivos) debido a que Visual Studio cuando estamos trabajando en C# pinta de azul la palabra "string" al igual que "int" o "bool". Para justificar todo esto que dicen ponen de ejemplo el siguiente código:

        void HolaMundo()
        {
            string lHolaMundo = "Hola Mundo";
            ChauMundo(lHolaMundo);
            Console.WriteLine(lHolaMundo);
        }
        void ChauMundo(string pCadena)
        {
            pCadena = "Chau Mundo";
        }

En la línea 05 la salida es "Hola Mundo" y no "Chau Mundo".

Primero voy a empezar con algunos conceptos:

String (Clase): String es una clase. El valor del objeto String es el contenido de la colección secuencial y ese valor es inmutable. Un objeto String se denomina inmutable (de sólo lectura) porque no se puede modificar su valor una vez que se ha creado. Los métodos que aparentemente modifican un objeto String devuelven en realidad un nuevo objeto String que contiene la modificación. Si realmente es necesario modificar el contenido de un objeto de tipo cadena, hay que utilizar la clase System.Text.StringBuilder.

Palabras clave de C#: bool, byte, ..., object, string... Estas palabras claves son alias para tipos del .NET Framework. Es lo mismo bool que System.Boolean, o string que System.String. Que Visual Studio pinte de azul o del color que sea por igual a bool y a string no significa que sean diferentes a System.Boolean o a System.String o que String sea un tipo de valor. Lo que importa es el CTS (Common Type System) que es por así decirlo una de las cosas que un lenguaje de programación que quiera trabajar con .NET debe cumplir.

Object.Equals (Object, Object): Determina si las instancias de Object especificadas se consideran iguales.

Vamos a modificar un poco el código ejemplo:

        string mHolaMundo = "Hola Mundo";

        void HolaMundo()
        {
            ChauMundo(mHolaMundo);
            Console.WriteLine(mHolaMundo);
        }
        void ChauMundo(string pCadena)
        {
            VerificarReferencias(mHolaMundo, pCadena);
            pCadena = "Chau Mundo";
            VerificarReferencias(mHolaMundo, pCadena);
        }
        void VerificarReferencias(object pObjeto1, object pObjeto2)
        {
            if (object.Equals(pObjeto1, pObjeto2))
            {
                Console.WriteLine("Misma referencia");
            }
            else
            {
                Console.WriteLine("Distinta referencia");
            }
        }

En la línea 10, cuando se llama al método VerificarReferencias la salida es "Misma referencia". Esto significa que las variables mHolaMundo y pCadena hacen referencia al mismo objeto. En la línea 11, la variable pCadena va a pasar a tener como referencia un nuevo objeto de tipo String, pero no va a modificar la variable mHolaMundo ni el objeto al que hace referencia mHolaMundo. Podemos comprobar esto en la línea 12 viendo que la salida es "Distinta referencia". Si en la línea 11 se hubiera hecho:

            pCadena = null;

La variable pCadena iba a quedar apuntando a null, pero mHolaMundo iba a seguir igual (esto es un comportamiento general, no solo de la clase String).

Vamos a ver lo siguiente:

        class Contenedor
        {
            public string Cadena;
        }

        Contenedor mContenedor = new Contenedor();

        void HolaMundoContenedor()
        {
            mContenedor.Cadena = "Hola Mundo";
            ChauMundoContenedor(mContenedor);
            Console.WriteLine(mContenedor.Cadena);
        }

        void ChauMundoContenedor(Contenedor pContenedor)
        {
            pContenedor.Cadena = "Chau Mundo";
        }

En este ejemplo en la línea 12 la salida va a ser "Chau Mundo". Esto es porque mContenedor y pContenedor tienen como referencia el mismo objeto y lo que estamos cambiando es la información a la que apuntan las referencias. Lo que se modifique "interno" en pContenedor (ejemplo el valor de un miembro de clase) se va a ver reflejado en mContenedor. Aún así, si hacemos en la línea 17 lo siguiente:

            pContenedor = null;

La variable mContenedor no va a quedar en null. Razón: por defecto los parámetros se pasan por valor. Si hiciéramos a pContenedor un parámetro por referencia al hacer pContenedor = null; mContenedor automáticamente quedaría en null.

        class Contenedor
        {
            public string Cadena;
        }

        Contenedor mContenedor = new Contenedor();

        void HolaMundoContenedor()
        {
            mContenedor.Cadena = "Hola Mundo";
            ChauMundoContenedor(ref mContenedor);
            Console.WriteLine(mContenedor.Cadena);
        }

        void ChauMundoContenedor(ref Contenedor pContenedor)
        {
            pContenedor = null;
        }

El ejemplo va a tirar una excepción.

En la próxima parte voy a hablar de parámetros. Esto es una simple introducción al comportamiento de pasar parámetros por valor y por referencia con algunas aclaraciones de la clase String y las palabras claves de C#.

Tags:

.NET Development

Tip #2 - Do More with More

by Nicolás Ferreira 16. August 2007 00:00

Leyendo MCPmag me encuentro con un artículo llamado "Do More with More". Estando en la ventana de comandos si abrimos paréntesis "(" y presionamos Enter vamos a obtener algo así:

C:\>(
More?

Podemos escribir un comando por línea. Cuando terminamos escribimos ")" y presionamos Enter para que todos los comandos que fueron escritos se ejecuten. Por ejemplo:

C:\>(
More? ECHO %OS%
More? ECHO Hello World
More? )

Esto produce:

Windows_NT
Hello World

Esto puede ser de gran utilidad cuando lo que queremos es mostrar varias listas de archivos y subdirectorios de varios directorios y redirigir la salida a un archivo. Por ejemplo:

C:\>(
More? TIME /T
More? DIR C:\*.EXE
More? DIR C:\WINDOWS\*.EXE
More? ) >C:\Resultados.txt

C:\>

Tags:

off-topic