lunes, 25 de febrero de 2013

Exportar un StreamWriter a texto con acentos y ñ

Manda narices que a estas alturas y con los lustros de picar código que llevo aún me pegue con los acentos y letras como la ñ al ir a exportar datos de la pantalla a un fichero de texto.

Así tengo un StreamWriter que bebe de un Grid, y que quiero guardar todos sus datos como un CSV (fichero de texto separados los valores por punto y coma, para ser abierto directamente dese una Excel).

Me volví loco intentando codificar directamente los valores que estaba añadiendo al StreamWriter, cuando lo que realmente hay que hacer es codificar el objeto stream en sí mismo.

Primero voy a poner la solución y luego, por si le valiera a alguien, el ejemplo completo de exportar el contenido de un objeto DataGrid de WPF a un fichero csv que se pueda abrir desde Excel.

var sw = new StreamWriter(rutaFichero, false, Encoding.UTF8);

Y con esto consigo que los acentos y la letra ñ me salgan perfecto en Excel.

Ahora el código completo:

public void Exportar_a_csv(DataGrid pDgHistorial)
        {
           
if (pDgHistorial.IsNotNull())
            {
                var sw = new StreamWriter(rutaFichero, false, Encoding.UTF8);
               
var separador = ";";

                foreach (DataGridColumn columna in pDgHistorial.Columns)
                {
                    sw.Write(columna.Header);
                    sw.Write(separador);
                }
                sw.Write(sw.NewLine);


               
foreach (var registro in pDgHistorial.Items)
                {
                   
var historial = (ClaseAlmacenadaEnElGrid)registro;
                    sw.Write(historial.PrimerCampo
));
                    sw.Write(separador);
                    sw.Write(historial.SegundoCampo);
                    sw.Write(sw.NewLine);
                }
                sw.Flush();
                sw.Close();
            }
        }

Y ahora explico dos cosas que tienes que tener muy en cuenta:

  • El objeto de WPF DataGrid, te almacena objetos tipados. A diferencia del GridView de asp.net. Así lo que recupero son una colección de objetos de una clase específica que he tenido que definir en el momento de la carga de datos en el control. En este caso ClaseAlmacenadaEnElGrid.
  • El parámetro RutaDelFichero, puede ser un string o, lo ideal, que lo recuperes del app.config de la aplicación.

Espero que resulte útil.

ConfigurationSettings.AppSettings está obsoleto

Pequeña tontería que quiero compartir.

Un compañero en una aplicación ha utilizado el siguiente código para acceder a claves den el fichero de configuración app.config:

string NombreFichero = ConfigurationSettings.AppSettings.Get("FicheroExportar");
string TipoFichero = ConfigurationSettings.AppSettings.Get("TipoFicheroExportar");

Lo cual hace que salte una alarma en Visual Studio avisándote que este código es obsoleto y que lo cambies por el actual.

La duda viene a que no es tan directo como el llamar a un nuevo namespace y ya está, pero vamos a empezar por eso:

using System.Configuration;

Esto no nos dará error alguno, pero si intentas introducir ConfigurationManager en el Intellisense no te vá a salir. Para ello debes hacer referencia del ensamblado adecuado en el proyecto, para lo cual pulso con el botón derecho encima del proyecto principal y escojo “Add Reference”.

image

Se me abre la ventana para seleccionar el ensamblado adecuado: System.Configuration. Fíjate que estoy en .NET 4.0.

image

Pulso aceptar y ahora si que si puedo utilizar el código correcto:

var nombreFichero = ConfigurationManager.AppSettings["FicheroExportar"];
var tipoFichero = ConfigurationManager.AppSettings["TipoFicheroExportar"];

Espero que sea útil.

miércoles, 6 de febrero de 2013

Recuperar un valor de un elemento de un XDocument

Que XML no es santo de mi devoción, como tampoco lo es JavaScript, es algo que destila en todo el blog. Y no lo es ninguna de las dos tecnologías por la misma razón, no son nada amigables en su aprendizaje.

Así me encuentro con la siguiente respuesta de un Webservice, que es básicamente un XmlElement con el body del mensaje SOAP en el innerXML.

<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <reactiveSecureClientResponse xmlns="http://irdeto.com/pisys/secureclient">
    <reactiveSecureClientResult>
      <code>1028</code>
      <message>OK</message>

    </reactiveSecureClientResult>
  </reactiveSecureClientResponse>
</soap:Body>

Del cual quiero extraer el valor de los elementos hijos code y message, y que he resaltado en negrita.

Pues bién, llevo casi 6 horas probando mil métodos y la primera aproximación, antes de refactorizarlo – y que va a ser otro artículo – es un tanto extravagante:

Primero convierto el XmlElement en un XDocument (cuidadín con liarte y utilizar XmlDocument, que se montan unos líos de aupa si mezclamos Linq to SQL con XmlDocumentde .NET).

XDocument documento = XDocument.Parse(cuerpoDelMensajeSoap.InnerXml);

¿Porqué XDocument.Parse() en vez de el conocido documento.LoadXml()? Ni idea, pero creo que es para joder. Porque ya me contarás porqué algo tan lógico como lo segundo lo cambian por algo tan raro (un constructor estático con parámetros) como lo primero.

A continuación tiro de las capacidades de Linq to XML para hacer una búsqueda que, personalmente, me parece rara de narices:

XElement code = (from XElement xmlElemento in documento.Descendants()
                       
where xmlElemento.Name.LocalName.Equals("code")
                       
select xmlElemento).FirstOrDefault();

El primer problemón que me encontré es que, a pesar de lo que dicta el sentido común, los xmlElemento que obtengo de los descendientes del XDocument (documento.Descendants()) componen su nombre con dos partes diferenciadas: el namespace y el nombre del nodo en sí.

<{http://irdeto.com/pisys/secureclient}code>1028</code>

Osea, que no solo hay que buscar por el Name del elemento, si no también por su nombre Local.

where xmlElemento.Name.LocalName.Equals("code")

Y así, y entonces si, podemos cargar el valor del nodo de marras que quería recuperar. Por cierto, te aconsejo comprobar primero si el XElement no es nulo para evitar una excepción por objeto no instanciado al querer recuperar el Value.

string codigo = (code != null) ? code.Value : string.Empty;

 

Ahora a refactorizar, que me “huele mal” cuando me tengo que traer dos valores,

public string ExtraerRespuestaDelBody(XmlElement cuerpoDelMensajeSoap)
        {

           
XDocument documento = XDocument.Parse(cuerpoDelMensajeSoap.InnerXml);

           
XElement code = (from XElement xmlElemento in documento.Descendants()
                       
where xmlElemento.Name.LocalName.Equals("code")
                       
select xmlElemento).FirstOrDefault();


           
string codigo = (code != null) ? code.Value : string.Empty;
 
           
return codigo;
        }