[Tip] Tarea de MSBuild + Template de aplicación WPF = Dolor de cabeza

Este post es resultado de un comportamiento extraño que presentaba la aplicación en la que ahora trabajamos. En Plain Concepts utilizamos la metodología SCRUM, por lo que nuestros proyectos siempre cuentan con integración continuas a través de la generación de builds con MSBuild.

Bien, nuestra build generaba un instalador ClickOnce en un servidor Microsoft IIS, solo había que acceder a la dirección del instalador y la aplicación se desplegaba sin problemas. A la hora de ejecutar la aplicación ya desplegada e intentar acceder a cualquiera de los menús, no había ninguna respuesta de la interface gráfica, pero los servicios si eran consumidos y los datos llegaban al cliente. Vamos, que el comportamiento de la aplicación, después de un par de sesiones de debug, era normal, a no ser por el pequeño detalle de que la interface de usuario no hacía nada 😛

Mi compañero J. se dió cuenta que la propiedad Template de los tipos derivados de la clase Control tenía valor null en la aplicación desplegada, mientras que en nuestras máquinas de desarrollo tenía el valor correcto, extraído de Generic.xaml.

Bien, ya tenemos el misterio, ahora vamos a ver quién es el asesino.

Para utilizar las plantillas de Generic.xaml de manera automática hay que cumplir dos condiciones:

  • El fichero Generic.xaml debe de estar colocado en Themes/Generic.xaml
  • El ensamblado debe de estar decorado con el atributo ThemeInfo para indicar dónde se encuentran los diccionarios de recursos. La línea es la siguiente:

[assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)]

Y aquí tenemos al asesino : Para soportar el versionado de ensamblados, una tarea de nuestra build sustituía nuestro AsemblyInfo.cs, por lo que perdíamos la decoración con el atributo necesario. La solución provisional fue indicar explícitamente la ruta al diccionario de plantillas en la sección Application.Resource del fichero App.xaml del proyecto de WPF de la siguiente manera:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source=»Themes/Generic.xaml»/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

 

Una vez detectado el problema, la correción definitiva pasa por que la tarea de MSBuild conserve esa decoración del ensamblado.

La verdad es que ha sido un fallo difícil de localizar, pero en ocasiones venideras creo que es algo que tendremos muy en cuenta… 🙂

Accediendo a Active Directory con System.DirectoryServices.AccountManagement

En el Microsoft Framework .NET 3.5 se ha incluido el namespace System.DirectoryServices.AccountManagement que nos permite acceder de una manera más transparente a grupos y usuarios de un Active Directory que con las clases DirectoryEntry y DirectorySearcher. Para ello contamos con las clases UserPrincipal y GroupPrincipal, que hereda de la clase abstracta Principal y nos permitirán crear nuestras plantillas de búsqueda. Después ejecutaremos nuestras peticiones contra el Active Directory a través de la clase PrincipalSearcher, con sus métodos FindOne() si queremos obtener solo un resultado o FindAll() si queremos obtener una collección de resultados en un objeto de tipo PrincipalSeachResult.

El primer paso será inicializar un PrincipalContext que será nuestra conexión contra el Active Directory:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

Y ahora vamos a hacer una búsqueda sobre un usuario del Active Directory cuyo correo electrónico sea vicente.garcia@plainconcepts.com y vamos a cambiarle su propiedad Description. Lo primero, deberemos inicializar un objecto de la clase UserPrincipal con su propiedad EmailAddress a “vicente.garcia@plainconcepts.com” y, después, ejecutar esa búsqueda a través del método FindOne() del objeto PrincipalSearcher:

UserPrincipal userQuery = new UserPrincipal(context);

userQuery.EmailAddress = «vicente.garcia@plainconcepts.com»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(userQuery);

UserPrincipal result = principalSearcher.FindOne() as UserPrincipal;

Ahora solo queda modificar la propiedad Description y llamar al método Save() del objeto de tipo UserPrincipal:

result.Description = «Development Advisor»;

result.Save();

 

Para utilizar el método FindAll() haremos una búsqueda por todos los elementos cuya propiedad EmailAddress pertenezca al dominio plainconcepts.com e imprimiremos su nombre por salida estándar:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

UserPrincipal userQuery = new UserPrincipal(context);

userQuery.EmailAddress = «*@plainconcepts.com»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(userQuery);

PrincipalSearchResult<Principal> result = principalSearcher.FindAll();

foreach (Principal item in result)

{

      Console.WriteLine(((UserPrincipal)item).Name);

}

 

La única consideración que hay que tener es que los métodos FindAll() y FindOne() devuelven siempre o colección de objetos de clase Principal o un objeto de tipo Principal, por lo que será necesario castearlo.

Para recuperar un grupo o grupos de un Active Directory con la funcionalidad que este nuevo namespace nos provee debemos operar de una manera muy similar. Por ejemplo, vamos a recuperar todos los grupos cuya propiedad Name comience por la letra P e imprimiermos su propiedad Description:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

GroupPrincipal groupQuery = new GroupPrincipal(context);

groupQuery.Name = «P*»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(groupQuery);

PrincipalSearchResult<Principal> result = principalSearcher.FindAll();

foreach (Principal item in result)

{

     Console.WriteLine(((GroupPrincipal)item).Description);

}

Manejador global de excepciones en ADO.NET Data Services

Si tenemos la necesidad de incorporar a nuestro servicio WCF un manejador global de excepciones para capturar y tratar las excepciones que no manejamos en nuestros bloques try-catch, podemos seguir los siguientes pasos que se indican en este post.

Pero si nuestra necesidad es el incluir un manejador global de excepciones en un servicio WCF ADO.NET Data Services, no nos servirá esta solución, ya que solo se capturarán las excepciones que se generen en el método de inicialización del servicio InitializeService, ni las excepciones no manejadas en nuestos ServiceOperation ni en los ChangeInterceptor serán capturadas.

Para tal propósito, tenemos a nuestra disposición el método virtual HandleException, que recibe un solo parámetro de tipo HandleExceptionArgs, en el que tenemos toda la información disponible sobre la excepción que se acaba de producir y, además, nuestra ya conocida propiedad booleana UseVerboseErrors. Hay que recordar que el valor de esta propiedad solo influye en la información que va a ser mostrada en el error de la request, la información que va a ser escrita a log será siempre la misma.

Por poner un ejemplo en código, trataremos las excepciones no capturadas por nuestros boques try – catch traceándolas al log del aplicación. Básicamente, tenemos un ServiceOperation que, al ser llamado, provoca una excepción no manejada. Si colocamos un punto de interrupción en el método HandleException, veremos cómo ese método se ejecuta y nuestra excepción no tratada se tracea:

namespace Plainconcepts.Server

{

    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]

    public class PlainDataService : DataService<PlainEntities>

    {

       public static void InitializeService(DataServiceConfiguration config)

        {

            config.SetEntitySetAccessRule("*", EntitySetRights.All);

            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);

            config.UseVerboseErrors = true;

        }

 

        [WebGet()]

        public IQueryable<Master> GetMasterById(int id)

        {

            throw new InvalidOperationException();

            return this.CurrentDataSource.Master.Where(m => m.IdMaster == id);

        }

 

       protected override void HandleException(HandleExceptionArgs args)

       {

          if (!EventLog.SourceExists("Operations"))

                 EventLog.CreateEventSource("Operations", "Application");

 

         EventLog.WriteEntry("Operations", args.Exception.ToString());

        }

    }

}

[How-to] Información de excepciones en ADO.NET Data Services

Por defecto, al crear un servicio ADO.NET Data Sevices, no tenemos información de errores y, si se lanza alguna excepción o hemos cometido algún fallo, obtendremos un mensaje con tan poca información como este:

First

Por lo que no tenemos ni idea de lo que está pasando con nuestro servicio. Realmente, nuestro error ha sido el siguiente:

Second

No hemos introducido el nombre del EntitySet al que queremos asignarle la regla de acceso correspondiente y no hemos introducido el nombre de la operación de servicio a la que queremos asignarle los derechos correspondiente.

 

Bien, podemos obtener toda la información de las anomalías que se están produciendo añadiendo a nuestro servicio dos líneas de código:

Third

El atributo ServiceBehavior lleva en el Framework .NET desde la versión 3.0 en la que se incluyó WCF y nos provee de la capacidad de especificar el comportamiento de la implementación del contrato. Con la propiedad booleana IncludeExceptionDetailInFaults podemos decidir si las excepciones no manejadas del servicio son devueltas como errores SOAP.

Este atributo puede decorar cualquier servicio WCF en general, aunque también podemos indicarlo en nuestro fichero de configuración de la siguiente manera:

<system.serviceModel>
    <services>
      <service name=»PlainConcepts.Server.PlainService» behaviorConfiguration=»Debug» />
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name=»Debug»>
          <serviceDebug includeExceptionDetailInFaults=»true»/>
        </behavior>
      </serviceBehaviors>
     </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled=»true»/>
</system.serviceModel>

 

La otra línea que hemos incluido tiene que ver con el objeto de la clase DataServiceConfiguration (en versiones anteriores, de la interfaz IDataServiceConfiguration), que provee de la propiedad booleana UseVerboseErrors, a través de la cual podemos indicar a nuestro servicio que el comportamiento por defecto para todos los errores de sus respuestas sean detallados. La interfaz IDataServiceConfiguration se ha incluido desde el Framework .NET versión 3.5 SP1.

Bien, con estas modificaciones, ahora obtendremos el siguiente error para la misma configuración errónea de nuestro servicio:

The server encountered an error processing the request. The exception message is 'Exception has been thrown by the target of an invocation.'. See server logs for more details. The exception stack trace is:
   at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at System.Data.Services.DataServiceConfiguration.InvokeStaticInitialization(Type type)
   at System.Data.Services.DataServiceConfiguration.Initialize(Type type)
   at System.Data.Services.DataService`1.CreateConfiguration(Type dataServiceType, IDataServiceMetadataProvider provider)
   at System.Data.Services.DataService`1.CreateProvider()
   at System.Data.Services.DataService`1.EnsureProviderAndConfigForRequest()
   at System.Data.Services.DataService`1.HandleRequest()
   at System.Data.Services.DataService`1.ProcessRequestForMessage(Stream messageBody)
   at SyncInvokeProcessRequestForMessage(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

Siempre recordar que este tipo de configuraciones son con el propósito de obtener más información de las excepciones que provoca nuestro servicio, por lo que es recomendable eliminar estas ayudas en entorno de producción.

Decir también que el comportamiento es equivalente cuando se consume el servicio a través de la librería de cliente System.Data.Services.Client o, incluso, cuando se incluye la referencia al servicio en el cliente para la generación del proxy, ya que si no tenemos esta ayuda, el servicio fallará y la información adicional que obtendremos será “The server encountered an error processing the request. See server logs for more details.”

[How to] Visual Studio Integrated Virtual Debugger de VMware

Visual Studio Integrated Virtual Debugger de la compañía VMware son unas extensiones para el entorno de desarrollo Microsoft Visual Studio 2005 Service Pack 1 o superior que nos permitiran, fundamentalmente, realizar las siguientes tareas:



  • Ejecutar una aplicación en una máquina virtual con un sistema operativo Microsoft Windows 98 o superior con y sin opción de debug activada



  • Adjuntar (to attach) una aplicación desarrollada en Microsoft Visual Studio a un proceso que corre en una máquina virtual con un sistema operativo Microsoft Windows 98 o superior.


Soporta aplicaciones escritas en lenguajes Visual Basic, Visual C# y C/C++ (tanto manejado como nativo).

Vamos a definir nuestro escenario para un sencillo ejemplo. Utilizaremos el producto VMware Workstation 6.5 beta e.x.p. build – 91182 y una máquina virtual con Microsoft Windows XP SP3, Microsoft .NET Framework 3.5 y las VMware Virtual Machine Tools.

Es muy importante tener las siguientes consideraciones en cuenta:



  • El usuario y la contraseña del host y de la máquina virtual tienen que ser el mismo.



  • El usuario debe de tener permisos de administrador en el sistema operativo de la máquina virtual.



  • El Firewall del sistema operativo de la máquina virtual debe de estar desactivado.



  • El nombre de red del sistema operativo de la máquina virtual debe de ser único en el dominio.



  • La configuración de red de la máquina virtual debe de ser Bridged o Host-Only.



  • En máquinas virtuales con Microsoft Windows XP Professional debemos ir a la opción Control Panel > Administrative Tools > Local Security Policy > Local Policies > Security Options page y colocar la opción  «Network access: Sharing and security model for local accounts» en «Classic – local users authenticated as themselves».



  • Para permitir la ejecución de programas a través de red en nuestra máquina virtual, debemos de ir al menú Tools del Microsoft Internet Explorer y en Internet Options > Security > Local Intranet hacer click en Sites, click en Advanced y Add new Web site file://*..host


Ahora, en nuestro entorno de desarrollo Microsoft Visual Studio 2008, nos encontraremos con el menú VMware (o con una toolbar con las opciones equivalentes):

 cabecera


 


Vamos a proceder a crear un perfil en nuestra configuración para ejecutar nuestra aplicación en una máquina virtual determinada. En Options hacemos click en New:


New Configuration


 


Personalizamos nuestra configuración indicando, en la sección General, la ruta del ejecutable en el campo Command, que, en nuestro caso, es «C:UsersVicenteDocumentsVisual Studio 2008ProjectsEjemploVMwareEjemploVMwarebinDebugEjemploVMware.exe». Por lo que ya habréis podido suponer que hay que compilar la aplicación antes de crear nuestro entorno de ejecución virtualizado.


fill general


 


La opción de Run Command as hace referencia a la ruta donde va a estar colocado el ejecutable, y podrá ser un directorio en el sistema operativo que hace de host o en el que se encuentra en la máquina virtual:


Run command as


 


Las siguientes opciones de Remote Debug Monitor y Remote Debug Monitor Name sirven para especificar la ruta del Remote Debugger (msvsmon.exe) en el equipo host y el nombre que queremos que tenga su instancia.


 


En el apartado Virtual Machine colocaremos la ruta del fichero .vmx de la máquina virtual en la que correremos nuestra aplicación, en nuestro caso «C:UsersVicenteDocumentsVirtual MachinesWindows XP Professional SP3Windows XP Professional SP3.vmx», y, opcionalmente, podemos especificar la ruta de directorios compartidos entre el host y el sistema operativo de la máquina virtual en el campo Shared Folders.


fill virtual machine


 


En el apartado Pre-Debug Event podremos realizar diferentes labores de inicialización dentro del sistema operativo de la máquina virtual, con el objetivo de crear el entorno que nosotros deseemos antes de la ejecución de nuestra aplicación. La opción Revert to Parent Snapshot nos permitirá hacer regresar a la máquina virtual al una especie de punto de restauración llamado Snapshot que previamente hemos creado; por ejemplo, tener el sistema operativo recién instalado, sin ningún tipo de modificación. En la opción Copy Files  podemos indicarle un conjunto de ficheros separados por «;» para copiar del sistema operativo host a nuestra máquina virtual antes de que el proceso de debug comience. Command Line contiene un conjunto de comandos separados por «;» para ser ejecutados en la máquina virtual antes de la ejecución de nuestra aplicación.


fill pre debug event


 


El apartado de Post-Debug Event es similar al anterior, ya que son las acciones que se llevan a cabo una vez terminado el proceso de debug en la máquina virtual. La opción de Command Line nos permitirá ejecutar un conjuto de comandos separados por «;» una vez terminada la ejecución. Y, por último, la opción de Termination Mode nos permite elegir el estado que queremos que la máquina virtual tome una vez acabado el proceso de debug; podemos no realizar ninguna opción, apagar o suspender la máquina virtual o volver al estado anterior a través de un Snapshot.


fill post debug event


 


Bien, una vez definida nuestra configuración para la prueba sobre la máquina virtual con Microsoft Windows XP, vamos a proceder a la ejecución de nuestra aplicación. Una vez compilada nuestra solución, hacemos click en el menú VMware y, en esta ocásión, elegiremos Start.


Ahora tendremos una ventana de inicio de sesión con nuestro usuario y contraseña para poder pasar a ejecutar la aplicación en nuestra máquina virtual:


log in to run


Recordemos que el usuario y la contraseña deben de coincidir en el equipo host y en la máquina virtual y que, además, debe de tener permisos de administrador en la máquina virtual.


Y ahora vemos el resultado de nuestra ejecución en la máquina virtual, con nuestra aplicación ejecutándose y el monitor de debug:


virtual machine


 


Evidentemente, es un ejemplo muy simple, pero las posibilidades de ejecución y debug en un escenario controlado son muy interesantes para aplicaciones en las que nuestos equipos puedan correr algún tipo de riesgo o en el proceso de debug de proyectos de dudosa reputación 😛

[Herramienta] SQL Scripter

He estado probando un generador de scripts para SQL que me ha parecido bastante simple a la par que útil. Se trata del SQL Scripter, una herramienta gratuita con la que podremos:




  • Generar cualquier tipo de scripts SQL de nuestras bases de datos a través de un sencillo Wizard.


  • Exportar los datos contenidos en una base de datos a ficheros de texto o CSV.


  • Exportar reports a Microsoft Excel y Adobe PDF.


  • Descargar los reports de una base de datos Reporting Services de una manera fácil y sencilla.

Además, soporta transacciones, da posibilidad de separar los scripts en diferentes ficheros y nos da la posibilidad de tener una lista con nuestros servidores SQL favoritos.


Una pequeña herramienta que, cuanto menos, merece ser descargada.


 

Comparación de rendimiento de Entity Framework y SQLClient

Parece que los chicos de ADO.NET se han puesto las pilas y han querido desmostrar con una comparación de rendimiento cómo Entity Framework se pega con un SQLClient. Crean un entorno de pruebas que utilizan con los modelos y a medir rendimiento se ha dicho. Interesante lectura que nadie debería perderse.


 http://blogs.msdn.com/adonet/archive/2008/03/27/ado-net-entity-framework-performance-comparison.aspx


 

Influencias del Open Source en Windows 2008 Server y viceversa

Curioseando un poco por Internet sobre el nuevo sistema operativo Windows 2008 Server me he encontrado con este artículo de Sam Ramji sobre los principios del Open Source en los que se han inspirado para desarrollar alguna de las funcionalidades del nuevo sistema operativo de Microsoft. Además, me ha servido para conocer la iniciativa The Open Source Community at Microsoft Port25, en la que se pueden encontrar cosas realmente interesantes. No es apto para talibanes de ninguno de los dos bandos 😛


 Por contra, en el OSBC Inforworld de Marzo de este año está programada la sesión “what Open Source can learn from Microsoft


 Como se puede comprobar, aquí todo el mundo copia («se inspira» como me corregiría Rodrigo) en tecnologías supuestamente contrarias para ofrecer soluciones de calidad. La diferencia es que, hace años, también se hacía pero no se decía.


 El modelo Open Source ha pasado de ser una filosofía a un incipiente modelo de negocio. Pero quiero seguir pensando que, en esencia, todo el código liberado y a la vista es una fuente inagotable de conocimiento al alcance de cualquier persona que tenga ganas de aprender.

Rendimiento de Entity Framework II

Como hace unos días apuntaba Unai, en el blog del equipo de desarrollo de ADO.NET están publicando una serie de artículos sobre cómo mejorar el rendimiento en la utilización de Entity Framework en su beta 3.

La primera parte podéis encontrarla aquí: http://blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx

Y ya han publicado ya la segunda parte: http://blogs.msdn.com/adonet/archive/2008/02/11/exploring-the-performance-of-the-ado-net-entity-framework-part-2.aspx

El aspecto del rendimiento en las aplicaciones siempre me ha parecido muy interesante y creo que es necesario, una vez que la aplicación funciona, el revisar puntos negros para mejorar la latencia y elevar la rapidez.

Además, antes de meterse de lleno en el tema de la mejora del rendimiento, hacen un repaso por el funcionamiento de Entity Framework, cosa muy útil, ya que antes de hacer algo más rápido, hay que saber qué es ese algo

Post de bienvenida

Hola a todos, mi nombre es Vicente y soy el último fichaje de Plain Concepts. Llevo una semana en Madrid y los compañeros de la empresa ya han hecho que me sienta como en casa. Ahora me toca a mí corresponder no defraudándoles 🙂


Un saludo a todos 🙂