Creo que lo que mejor manera de definir a los que nos dedicamos a
esto del software es llamarnos vagos. ¿Para qué vamos a hacer lo
mismo dos, tres, N veces, si podemos programarlo una vez y
despreocuparnos? Esto me ha pasado con los archivos de
configuración de WCF y quiero compartir con vosotros cómo lo he
resuelto.
No creo que os descubra nada nuevo si digo que WCF "mola un
huevo". Podemos montar la estructura de comunicaciones tanto en un
archivo de configuración como directamente por código. En los
proyectos en los que he participado, con el archivo de
configuración me ha sobrado; por eso, seguramente, es el método que
más me gusta.
Un archivo XML en el que se basan las comunicaciones de nuestro
sistema. Los archivos de configuración han estado siempre ahí,
siempre los hemos usado, pero gracias a la abstracción, patrones de
diseño y todo lo que ha hecho que el desarrollo de software se
convierta en una ingeniería, podemos tener componentes tan
generales como personalizables. ¿Veis por dónde voy? A mí no me
gusta desarrollar software: me encanta. Lo haga mejor o peor. Y
aunque sepa que es una ingeniería, personalmente lo considero un
arte.
El "problema" cuando estamos desarrollando con WCF es a la hora de
poner el sistema en un entorno de desarrollo, de preproducción o en
producción. Me explico. En desarrollo, montamos nuestros servicios
en nuestra máquina local, decidimos qué bindings nos convienen más,
creamos los servicios, los configuramos y empezamos con los
consumidores. Todo perfecto; esto es la leche. Pero luego toca
montarlo todo en un(os) servidor(es) de desarrollo, y entonces
saltan las alarmas: tenemos que trastear con los archivos de
configuración para que los servicios funcionen en el(los)
servidor(es) de desarrollo. ¿Tenemos que cambiarlos a mano? ¿En
preproducción y en producción también?
Trivial: tenemos los proyectos de instalación [1] y LINQ to XML
[2]… Qué tonto puedo parecer algunas veces.
En este artículo vamos a ver cómo con un simple instalador vamos a
poder desplegar todo el sistema sin tener que estar tocando a mano
esos archivos de configuración. Con un instalador en plan
"Siguiente, Siguiente, Aceptar" tendremos nuestro sistema
desplegado y configurado.
Para simplificar un poco, en Visual Studio 2008 tendremos una
solución con dos proyectos de consola: uno llamado Servidor, que va
a servir de host a un servicio WCF, y otro (Cliente) que va a hacer
de cliente.
Una vez que añadimos un servicio WCF al proyecto Servidor, ya
tenemos el primer App.config de la "discordia": el que nos va a
permitir configurar la forma de comunicarse con los clientes.
Las operaciones que publique nuestro servicio no son relevantes
para este artículo, ya que lo que nos interesa es cómo automatizar
el proceso de actualización de los archivos App.config. Y digo
"los" porque al agregar la referencia al servicio WCF en el
cliente, también nos va a aparecer otro… que también habrá que
configurar en producción.
Ahora lo que nos queda es crear un proyecto de instalación para
nuestras aplicaciones, tanto el servidor como la aplicación
cliente. Vamos a ver cómo crearíamos el proyecto de instalación del
proyecto Servidor para que nos pregunte por la dirección en la que
queremos publicar nuestros servicios.
Lo primero es añadir un proyecto de instalación a la solución
(figura 1). En el cuadro de diálogo de tipos de proyecto, debemos
seleccionar el nodo "Setup and Deployment" (Instalación y
despliegue), que se encuentra en la categoría "Other Project Types"
(Otros tipos de proyectos), y la plantilla "Setup Project"
(Proyecto de instalación).
A continuación, tenemos que añadir el resultado de la compilación
del proyecto Servidor, indicándole así al proyecto de instalación
qué es lo que queremos instalar. Para ello, en el Explorador de
soluciones hacemos clic con el botón derecho en el proyecto de
instalación, y seleccionamos la opción "Add" | "Project Output". En
el cuadro de diálogo que aparece, indicamos que queremos añadir la
salida de compilación del proyecto Servidor. Allí, en el
desplegable "Configuration" podemos indicar que se utilice la
configuración activa, "Debug" o "Release". Esto va a depender de
los gustos y de lo olvidadizos que seamos a la hora de generar y
compilar nuestros ensamblados. Personalmente, prefiero la opción
"Release"; así no tengo que acordarme de cambiar la configuración
de compilación de Visual Studio a la hora de generar el instalador.
Una vez agregada la salida del proyecto Servidor, tenemos que
añadir una acción personalizada (Custom Action) para la
instalación. Para ello, en el Explorador de soluciones hacemos clic
con el botón derecho en el proyecto Instalador y seleccionamos en
el menú contextual "View" | "Custom Actions". En la pestaña que
aparece, hacemos clic con el botón derecho en "Custom Actions" y
seleccionamos "Add Custom Action". Y en el cuadro de diálogo
subsiguiente, nos vamos a "Application Folder" y seleccionamos la
salida principal (Primary Output) del proyecto que acabamos de
añadir.
Si en este momento compilamos y ejecutamos el instalador, nos
preguntará por la ruta en la que queremos instalar. Y en esa ruta
nos copiará tanto el ejecutable como su archivo de configuración.
Solo nos queda hacer que el instalador nos pregunte en qué
dirección queremos publicar los servicios. Tenemos que añadir un
cuadro de diálogo a la interfaz del instalador con un cuadro de
texto en el que se podrá indicar esa dirección. Para ello, en el
Explorador de soluciones hacemos clic con el botón derecho sobre el
proyecto Instalador y seleccionamos en el menú contextual "View" |
"User Interface". En la nueva pestaña que aparece, nos vamos al
nodo "Install" | "Start", hacemos clic con el botón derecho y
seleccionamos "Add Dialog", y luego el diálogo predefinido
"TextBoxes (A)" (figura 2).
Una vez añadido el diálogo, en el árbol donde se nos muestran los
diferentes pasos del proceso de instalación aparecerá un nuevo
elemento. Debemos posicionarlo después del de bienvenida
("Welcome"). El cuadro de diálogo predefinido que hemos añadido
ofrece cuatro cuadros de texto. Como solo necesitamos uno, en las
propiedades de ese cuadro de dialogo cambiaremos los valores de
Edit2Visible, Edit3Visible y Edit4Visible a false, para que no
aparezcan durante la instalación.
Si en este punto compilamos y ejecutamos el instalador, veremos
éste incluirá ahora un nuevo paso con el cuadro de diálogo que
recién hemos añadido después de la bienvenida.
Por último, solo nos queda recoger el dato que se introduzca en el
cuadro de texto del diálogo y actualizar con él el archivo de
configuración. Para ello, necesitamos agregar a la solución un
nuevo proyecto de tipo Librería de clases que contenga una clase
especial que va a ser llamada cuando se ejecute la instalación.
Llamaremos a este proyecto AyudaInstalacion. Al crearlo, Visual
Studio nos agregará por defecto una clase que no nos va a hacer
falta, así que la eliminamos. Entonces añadimos al proyecto un
elemento de tipo InstallerClass, al que llamaremos
ActualizarConfiguracion (figura 3). Con esto se añadirán al
proyecto las referencias a todos los ensamblados necesarios que van
a hacer posible la ejecución del código que escribamos cuando se
instale nuestro Servidor.
Si nos vamos a la vista de código del elemento añadido, veremos
que se trata de una clase que hereda de la clase Installer de .NET
Framework. Esta clase implementa toda la funcionalidad necesaria
para operar en el contexto de una instalación, y nos permite
sobrescribir ciertos métodos que se van a ejecutar en determinados
momentos de la instalación. Aquí lo que queremos es que en el
momento de la instalación se actualice el código del archivo de
configuración con la dirección que se haya suministrado durante el
proceso.
Tenemos que añadir otro "Primary Output" al proyecto de
instalación, como ya hicimos anteriormente, pero esta vez añadimos
el proyecto AyudaInstalación. La idea es que el instalador
despliegue con el proyecto la librería de clases, a la que
llamaremos para que se encargue de actualizar el archivo de
configuración.
Como cabría esperar, la llamada a la librería de clases debemos
implementarla como otra acción personalizada, aunque esta vez la
añadimos debajo del nodo "Install" (figura 4). Y como deberemos
pasarle el texto que se haya introducido en el cuadro de texto para
que sepa con qué actualizar el App.config, seleccionamos "Primary
output from Ayudainstalacion" y modificamos su propiedad
CustomActionData como se muestra en la figura 4. De esta manera
indicamos que lo que se escriba en el cuadro de texto con nombre
"EDITA1" se debe guardar en el contexto de la instalación bajo la
clave "direccion", lo que hará que ese valor sea accesible para el
código de la acción personalizada que acabamos de añadir.
Finalmente, vamos al proyecto AyudaInstalacion y escribimos el
código necesario para actualizar el archivo App.config. En la clase
ActualizarConfiguracion, sobrescribimos el método Install como se
muestra en el listado 1.
En el código, inicialmente se obtiene la ruta completa del archivo
de configuración, que va a ser la concatenación de la ruta de
instalación y el nombre del archivo de configuración. La ruta de
instalación también se pasa como un parámetro en el contexto de la
instalación, y el nombre del archivo de configuración es
Servidor.exe.config (o Cliente.exe.config para el proyecto
Cliente). A continuación, y una vez obtenida la dirección que se ha
pasado al instalador (que debería validarse mediante expresión
regular u otra técnica), se carga el contenido del archivo de
configuración en un XElement y se modifican los elementos y los
atributos que nos interesan. Lo que necesitamos es cambiar el valor
del atributo baseAddress, reemplazando en él "localhost" por la
dirección pasada al instalador. Gracias a LINQ to XML, esta tarea
puede llevarse a cabo de una manera muy sencilla.
Conclusión
A lo largo de este artículo, hemos mostrado cómo crear un proyecto
de instalación personalizado que nos permita especificar en tiempo
de instalación la dirección de nuestros servicios WCF. De esta
manera, cada vez que vayamos a desplegar nuestro servidor no
tendremos que tocar las configuraciones a mano, cosa muy tendiente
a errores. Solo tendremos que pasarle la dirección durante la
instalación, y nuestros servicios quedarán configurados,
preocupándonos tan solo una vez sobre cómo vamos a configurarlos en
desarrollo, y olvidándonos de este tema en lo adelante. Esta misma
técnica podemos aplicarla exactamente de la misma manera a las
aplicaciones clientes.