Welcome to Comunidad .NET de Cd. Juárez Sign in | Join | Help

Browse by Tags

All Tags » .net framework 3.5

Cómo transformar XML sin usar XSLT

El otro día, una compañía externa hizo un una auditoría de seguridad a un sistema grande que manejamos.  Parte de la auditoría consistió en correr la herramienta FxCop contra las DLLs para buscar—entre otras cosas—posibles vulnerabilidades de inyección de SQL.  FxCop produce dichos reportes en XML y eso fue lo que nos entregaron. 

Estos reportes eran enormes y en un formato no muy amigable, así que necesitábamos de alguna forma extraer los datos de ahí para enfocar los esfuerzos de los desarrolladores (qué corregir y en dónde), y también para tabular los datos (número de issues por namespace, DLL, etc.) de manera que se les pudieran presentar a los gerentoides. 

Este es un ejemplo de uno de esos archivos—no te preocupes, todas estas vulnerabilidades ya fueron parchadas, este es un extracto de esos reportes smile_wink.

(Da clic a la imagen para ver un tamaño más grande)

ejemplo de reporte FxCop

Ahora, se me ocurrió que estos archivos se podían transformar a algo menos plano—como otro archivo de XML o un CSV—de manera que pudiera ser importado a Excel fácilmente.  Algo así como lo siguiente:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Vulnerabilities>
  <Vulnerability>
    <Module></Module>
    <Namespace></Namespace>
    <Type></Type>
    <Method></Method>
    <Issue></Issue>
  </Vulnerability>
  <Vulnerability>
    <Module></Module>
    <Namespace></Namespace>
    <Type></Type>
    <Method></Method>
    <Issue></Issue>
  </Vulnerability>
</Vulnerabilities>

La sola idea de hacer una hoja de estilos XSL para transformar eso me daba escalofríos, así que fue una buena oportunidad para practicar mi LINQ-Fu.  Para ello me ayudé de LINQPad—una excelente herramienta para probar snippets de LINQ.

Armando el query

Mi primer query fue este:

XElement xml = XElement.Load(@"C:\Users\Carlos\Desktop\sampleFxCopReport.xml");
 
var query =
    from e in xml.Descendants("Issue")
    where e.Ancestors("Message").Attributes("TypeName").First().Value == "ReviewSqlQueriesForSecurityVulnerabilities"
    select new
    {
        Module = e.Ancestors("Module").Attributes("Name").First().Value,
        Namespace = e.Ancestors("Namespace").Attributes("Name").First().Value,
        Type = e.Ancestors("Type").Attributes("Name").First().Value,
        Method = e.Ancestors("Member").Attributes("Name").First().Value,
        Issue = e.Value
    };

Como puedes ver, cargar el archivo es trivial.  Puedes hacerlo a través del método Load() de la clase XElement o XDocument.

Luego, declaré una variable de tipo var porque no estaba seguro exactamente del tipo de los objetos que regresaría mi query—utilicé un tipo anónimo, el cual explicaré en un minuto.

Si no estás muy familiarizado con queries de LINQ, solo recuerda que en su nivel más básico, tienes un from/where/select

  • Primero le dices a qué objetos quieres hacerle query (from),
  • luego especificas un filtro que deje solo los que te interesen (where) y
  • finalmente le indicas una expresión de cómo quieres proyectar tus datos en la salida (select).

En este caso, se me hizo más fácil elegir primero todos los elementos <Issue> dentro del documento XML, ya que a partir de ellos puedo obtener el resto de la información viendo los nodos ancestros.  Logré hacer esto con una sola línea en el from gracias al método Descendants() de la clase XElement.  En otras palabras estoy diciendo “selecciona los elementos <Issue> en el documento, y por cada uno de ellos dame una variable e de donde extraer más información”.  Puedes pensarlo como algo parecido a un foreach.

Luego, indiqué en el filtro where que únicamente deseo los <Issue> donde el mensaje (<Message>) sea de tipo vulnerabilidad de SQL (TypeName=”ReviewSqlQueriesForSecurityVulnerabilities”).  Como los elementos <Message> son ancestros de <Issue>, utilicé Ancestors() para acceder a ese elemento y luego Attributes() para obtener el valor de atributo TypeName y poder hacer la comparación.

Finalmente, en la proyección el select new crea un tipo (una clase pues) sin nombre con 5 propiedades, que son inicializadas con los valores de las expresiones.  Esto crea una collección de tipo IEnumerable<T> donde T es el tipo anónimo—¿ves por qué fue necesaria la variable var?

El resultado del query es este (da clic para ver la imagen más grande):

resultados del primer query

Pero, ¿qué tal si quisiera ordenar mis resultados primero por Module, luego por Namespace, Type y Method sucesivamente?  Sencillo.  Puedo agregar una cadena de OrderBy’s a mi query original o puedo agarrar el resultado del query y ordenarlo así:

var ordered =
    query.OrderBy(x => x.Method).OrderBy(x => x.Type)
    .OrderBy(x => x.Namespace).OrderBy(x => x.Module);

Esta manera de hacerlo involucra las famosas expresiones lamda, pero no me quiero desviar en explicarlas.  Por ahora sólo tómalas como ejemplo y nota que puedes encadenar las cláusulas.

Esto es un buen comienzo, pero ahora quiero esos datos en algo que se le parezca a un documento XML.

Entonces agregué esto después de mi query:

XDocument xdoc = new XDocument();
xdoc.AddFirst(new XElement("Vulnerabilities"));
 
foreach (var element in query)
{
    xdoc.Root.Add(
        new XElement("Vulnerability", new XElement("Module", element.Module)
                                    , new XElement("Namespace", element.Namespace)
                                    , new XElement("Type", element.Type)
                                    , new XElement("Method", element.Method)
                                    , new XElement("Issue", element.Issue)
                                    ));
}

Primero creo un nuevo documento XML utilizando la clase XDocument, y le agrego un elemento raíz <Vulnerabilities>.  Luego por cada elemento de mi lista ordenada, agrego un elemento <Vulnerability> que contendrá cinco elementos, uno por cada propiedad que me interesó, y que tendrán el valo.  Nota que los constructores “raros” de XElement facilitan enormemente la tarea, y que los métodos AddFirst() y AddNew() se encargará de cerrar los elementos adecuadamente—a diferencia de la forma en que se utiliza un XmlWriter, por ejemplo.

El resultado:

resultados del query

Esto ya es prácticamente lo que quiero.  Sin embargo, mi código hasta ahorita son como 30 líneas—principalmente por el formato, en realidad solo he utilizado como 7 instrucciones—y me gustaría condensar el código más.  Además descubrí que el ordenamiento en realidad no lo necesito—puedo hacerlo en Excel—y que al documento XML le falta la declaración <?xml> al pricipio.

Sintetizando el código

Así que decidí cambiar la proyección (el select pues) de mi query original.  El total del código quedó así:

XElement xml = XElement.Load(@"C:\Users\Carlos\Desktop\sampleFxCopReport.xml");
 
IEnumerable<XElement> query =
    from e in xml.Descendants("Issue")
    where e.Ancestors("Message").Attributes("TypeName").First().Value == "ReviewSqlQueriesForSecurityVulnerabilities"
    select new XElement("Vulnerability",
        new XElement("Module", e.Ancestors("Module").Attributes("Name").First().Value),
        new XElement("Namespace", e.Ancestors("Namespace").Attributes("Name").First().Value),
        new XElement("Type", e.Ancestors("Type").Attributes("Name").First().Value),
        new XElement("Method", e.Ancestors("Member").Attributes("Name").First().Value),
        new XElement("Issue", e.Value)
        );
 
XDocument xDoc = new XDocument(
    new XDeclaration("1.0", "UTF-8", "yes"),
    new XElement("Vulnerabilities", query)
    );

Nota que con solo 2 instrucciones—la instrucción de carga no cuenta—logré hacer la transformación (¡!) smile_omg.   Veamos los cambios significativos al query.

Primero, el resultado es almacenado en una variable que ya no es tipo var, sino IEnumerable<T> donde T es XElement.  Esto es porque en la proyección (el select) ya no estoy utilizando un tipo anónimo para construir un objeto, sino los constructores de la clase XElement para obtener objetos de este tipo.

Finalmente, a la hora de crear el nuevo documento XML, utilizo también un constructor alterno de XDocument, para primero pasarle la declaración XML, y agregarle un elemento <Vulnerabilities> que tendrá como contenido el resultado de mi query—la lista de nodos <Vulnerability> con sus respectivos elementos hijo.  ¿Así o más mágico? smile_teeth

[Pronunciarse en tono de vendedor de chuchulucos por T.V.:] ¡Pero aún hay más!

¿Quieres saber lo que se necesita para guardar el nuevo documento de XML a un archivo?

xDoc.Save(@"C:\Users\Carlos\Desktop\transformedReport.xml");

Así que con un total de 4 instrucciones logré cargar un archivo complejo de XML, transformarlo y escribir los resultados a otro más amigable.

[Pronunciarse en tono de vendedor de chuchulucos por T.V.:] ¡Pero aún hay más!

Sintetizándo el código aún más

¿En verdad quieres ponerte quisiquilloso con el query?  Puedes lograr la totalidad de la lógica con solo 3 instrucciones—una para cargar el archivo original, otra para transformarlo y una más para guardar el archivo resultante—si utilizas expresiones lambda.  Este ejemplo no lo explico porque igual y te acabo confundiendo más, pero creo que ilustra el poderío de LINQ y las nuevas clases para manipular XML.

XElement xml = XElement.Load(@"C:\Users\Carlos\Desktop\sampleFxCopReport.xml");
 
XDocument xDoc = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
    new XElement("Vulnerabilities",
      xml.Descendants("Issue")
      .Where(i => i.Ancestors("Message").Attributes("TypeName").First().Value == "ReviewSqlQueriesForSecurityVulnerabilities")
      .Select(e => new XElement("Vulnerability",
              new XElement("Module", e.Ancestors("Module").Attributes("Name").First().Value),
              new XElement("Namespace", e.Ancestors("Namespace").Attributes("Name").First().Value),
              new XElement("Type", e.Ancestors("Type").Attributes("Name").First().Value),
              new XElement("Method", e.Ancestors("Member").Attributes("Name").First().Value),
              new XElement("Issue", e.Value)
              )
          )
      )
  );
 
xDoc.Save(@"c:\users\Carlos\Desktop\transformedReport.xml");

¿Qué tal, eh? smile_nerd

Conclusión

Este artículo mostró un ejemplo de cómo transformar un documento XML utilizando LINQ to XML.  También mostró de manera práctica qué son los tipos anónimos (“clases sin nombres”), cómo cargar y escribir archivos XML con las clases XElement y XDocument incluidas en el .NET Framework 3.5, e ilustró brevemente el uso de expresiones lambda.  La mayoría de los conceptos—excepto quizá las expresiones lambda—aplican también si estás utilizando Visual Basic como tu lenguaje de desarrollo para .NET.

Enjoy. smile_shades

Dynamic Data se ve interesante (y con screencast se entiende mejor)

Dynamic Data es uno de los features de las ASP.NET 3.5 Extensions (aún en Preview) que me llamó la atención el otro día.  Te permite hacer una versión scaffolded—un armazón pues—de tu aplicación a partir de tu modelo de base de datos, algo al estilo de Rails.

(Ahí les encargo la traducción de “scaffolded” al español.  Hasta donde sé un scaffold es un andamio, como el que usan los maistros albañiles en la talacha, pero no creo que “andamiada” sea una palabra correcta. Y si lo es, qué fea.  A mi no me gusta nada “miado” o “miada”) smile_wink

Esto es bastante útil porque muchas veces necesitas arrancar con algo para poder meter datos aunque sea de prueba a tu base de datos.  Y muchas veces esto, con cambios muy menores, es suficiente para lo que los usuarios necesitan de la aplicación.  Si tienes una base de datos bien armada—y sí la tienes ¿verdad?—o al menos tu modelo de datos bien armado, esto ya te quitó esa talacha.

Ayer precisamente estaba intentando explicarle a un compañero del trabajo qué era Dynamic Data: “Pos ¿ya ves cómo está shida que puedes nomás arrastrar un SqlDataSource y un GridView a una página ASP.NET y sin mucho esfuerzo ya puedes editar tu tabla?  Bueno pues esto es como eso, pero para toda tu base de datos, si así lo quisieras. Y como está basado en templetes, pues puedes alterar de un sablazo todo el sitio...”

Pero al parecer no supe explicarlo muy bien, así que después de la cara de guatdefuc que me puso, me dió mucho gusto mostrarle este screencast (en inglés) que me encontré hoy en el blog de David Ebbo:

I made a screencast which walks through a basic scenario of using ASP.NET Dynamic Data in a simple site using Northwind.  It's about 17 minutes long.  Enjoy and feel free to give any feedback here or in the forum.

Enjoy. smile_shades

¡Visual Studio 2008 ya está disponible!

Ya está disponible Visual Studio 2008 para suscriptores de MSDN.  Y si no has gastado los miles de dólares en una suscripción, pues de perdis puedes jugar utilizando las versiones Express que son gratuitas.  Ya liberaron Visual C# 2008 Express Edition y Visual Basic 2008 Express Edition—ambas con soporte para desarollar WPF, naturalmente—y Visual Web Developer 2008 Express Edition que no solo trae las mejoras que había comentado previamente, sino que también trae soporte para Popfly—para aquellos que gustan de los mashups y cosas de esas.

Enjoy.

¿Ya estás listo para Visual Studio 2008?

Con el anuncio ayer que hizo Microsoft de que Visual Studio 2008 y el .NET Framework 3.5 estarán disponibles para suscriptores de MSDN a finales de este mes (can you say: wohoo! thumbs_up) es hora de comenzar a prepararse para varias novedades que trae.Tecnologías en las diferentes versiones del .NET Framework

En lo personal me tiene emocionado el lanzamiento porque creo que le agregaron cosas que ya urgían.  Comenzando por el soporte nativo a las tecnologías de .NET 3.0 y ASP.NET AJAX—no más andar instalando extensiones y adiciones por separado.  Por ejemplo, me acaban de dar una máquina nueva en el trabajo, y solo de Visual Studio tuve que instalar:  Visual Studio 2005, Visual Studio 2005 SP1, .NET 3.0 Framework Runtime, Windows SDK February 2007 update, Visual Studio Extensions for WCF & WPF November 2006 CTP (que no funciona muy bien), Visual Studio Extensions for WF, ASP.NET AJAX y ASP.NET AJAX Control Toolkit.  Cuéntalas: OCHO cosas que se reducirán a uno o dos.

Además, finalmente le agregaron soporte decente para CSS y un editor y debuggeador de JavaScript excelente, lo cual es música para los oídos de desarrolladores web como yo.

"¡Pero aún hay más!", como dirían los vendedores: LINQ—una tecnología que promete cambiar cómo lidean los programadores con el acceso a datos, sin tener que salirse del paradigma de C# o VB—, soporte para Silverlight y más (whew!).

En fin, como una imágen dice más que mil palabras, aquí está un poster (PDF) que pueden descargar e imprimir de los tipos y espacios de nombre más comúnes con .NET 3.5.

Además, el 14 de noviembre—¡osea en menos de dos semanas!—tendremos en Ciudad Juárez el Tour Tecnológico 2007 de Microsoft.  Este es un evento como el CodeCamp que tuvimos en abril de este año o el Community Dev Day del año pasado así que no te lo pierdas. 

Tour Tecnológico Microsoft 2007

La agenda es:
8:30  Registro
8:45 Bienvenida
9:00  Características principales de VS 2008 y el .Net framework 3.5
10:00 Construyendo Clientes inteligentes usando Visual Studio 2008, WPF y Expression Blend
11:00 Receso
11:30 Construyendo aplicaciones móviles usando Visual Studio 2008 y el .NET Compact Framework 3.5
12:30 Introducción al ciclo de desarrollo con Visual Studio Team System

Para registrarte ve a esta dirección: http://www.mslatam.com/spanish/msdn/mexico/tour2007/ luego selecciona Lista de Eventos > Chihuahua > Cd. Juárez.

OJO, que en esta ocasión el evento será en uno de los edificios de la UACJ (el que está por la Avenida del Charro), NO será en el Tec de Monterrey, donde normalmente tenemos las reuniones y eventos.

Espero verlos ahi. smile_wink