Mostrando entradas con la etiqueta Prog_dotNET. Mostrar todas las entradas
Mostrando entradas con la etiqueta Prog_dotNET. Mostrar todas las entradas

jueves, 20 de mayo de 2010

facturaE con .NET y C#

Si vas a implementar la facturación electrónica en tu aplicación .NET, y te has decidido por el formato facturaE, comprobarás que, a día de hoy, la documentación y las herramientas para trabajar con dicho lenguaje son muy escasas.

Si no sabes por donde empezar, estás leyendo las líneas que buscabas.

¿Por dónde empezamos? Bueno, la "facturaE" es un fichero XML firmado digitalmente. Por lo tanto lo primero que tenemos que hacer es generar ese XML. Como buen programador, lo primero que hay que hacer es leer la documentación oficial, así que en http://www.facturae.es/es-ES/Documentacion/EsquemaFormato/Paginas/Index.aspx encontramos la tabla de campos en PDF (guárdatela, te hará falta) y el esquema en formato XSD. A día de hoy existen 3 versiones de facturaE, la 3.0, 3.1 y 3.2. La más actual, 3.2, todavía no está lo suficientemente extendida, por lo que recomiendo trabajar (repito, a día de hoy, Mayo de 2010) con la versión 3.1.

El esquema XSD que encontramos nos facilitará mucho las cosas, ya que con él y con la herramienta xsd.exe vamos a generar una clase cuya instancia nos permitirá generar el XML de forma sencilla. Para generar el fichero *.cs necesitamos:
Con todo esto, podemos ejecutar el comando que nos dará el esperado fichero:

"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\xsd" Facturaev31.xsd xmldsig-core-schema.xsd /c /n:FacturaElectronica

Ahora podemos importar ese fichero a nuestro proyecto y generar facturas electrónicas así de fácil:
//Crear objeto facturaE (obtenido a partir del XSD)
Facturae facturasE = new Facturae();

//Rellenar objeto facturaE con datos de tu factura
....

//Sacar datos a fichero
string fichero = "c:\\temp\\pruebas.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Facturae));
FileStream fs = new FileStream(fichero, FileMode.Create);
serializer.Serialize(fs, facturasE);
fs.Close();

Para rellenar el objeto facturaE correctamente, debes leer el PDF que contiene la tabla de campos; éste te indica qué campos son obligatorios, qué son, y el formato que deben tener. Y aquí es donde nos encontramos con un problema: hay algunos campos que han de tener 2, 4 ó 6 decimales, pero el tipo de datos es un "Double", ¿cómo especificamos que cuando se genere el XML salgan exactamente ese número de decimales? Bien, hay varias soluciones, la mía es la siguiente:

Generaremos 3 clases, de forma que cada clase contiene un atributo "Double". Al serializar en XML un objeto de estas clases, la representación numérica aparecerá con un número fijo de decimales. En realidad haremos 4 clases: crearemos un clase padre para no repetir el atributo en las 3 clases:
public abstract class DoubleFixedDecimalType
    {
        protected double value;

        public DoubleFixedDecimalType()
        {
            value = 0;
        }

        public DoubleFixedDecimalType(double value)
        {
            this.value = value;
        }

        [System.Xml.Serialization.XmlText()]
        public abstract string Value
        {
            get;
            set;
        }
    }

    public class DoubleTwoDecimalType : DoubleFixedDecimalType
    {
        public DoubleTwoDecimalType() : base() { }

        public DoubleTwoDecimalType(double value) : base(value) { }

        public static implicit operator double(DoubleTwoDecimalType o)
        {
            return o.value;
        }

        public static implicit operator DoubleTwoDecimalType(double value)
        {
            return new DoubleTwoDecimalType(value);
        }

        [System.Xml.Serialization.XmlText()]
        public override string Value
        {
            get { return value.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); }
            set { this.value = System.Convert.ToDouble(value); }
        }
    }

    public class DoubleFourDecimalType : DoubleFixedDecimalType
    {
        public DoubleFourDecimalType() : base() { }

        public DoubleFourDecimalType(double value) : base(value) { }

        public static implicit operator double(DoubleFourDecimalType o)
        {
            return o.value;
        }

        public static implicit operator DoubleFourDecimalType(double value)
        {
            return new DoubleFourDecimalType(value);
        }

        [System.Xml.Serialization.XmlText()]
        public override string Value
        {
            get { return value.ToString("F4", System.Globalization.CultureInfo.InvariantCulture); }
            set { this.value = System.Convert.ToDouble(value); }
        }
    }

    public class DoubleSixDecimalType : DoubleFixedDecimalType
    {
        public DoubleSixDecimalType() : base() { }

        public DoubleSixDecimalType(double value) : base(value) { }

        public static implicit operator double(DoubleSixDecimalType o)
        {
            return o.value;
        }

        public static implicit operator DoubleSixDecimalType(double value)
        {
            return new DoubleSixDecimalType(value);
        }

        [System.Xml.Serialization.XmlText()]
        public override string Value
        {
            get { return value.ToString("F6", System.Globalization.CultureInfo.InvariantCulture); }
            set { this.value = System.Convert.ToDouble(value); }
        }
    }
Como se puede apreciar en las declaraciones, se puede convertir implícitamente un objeto "Double" a cualquier instancia de estas clases, lo cual nos facilita el trabajo ya que podemos asignar directamente un valor numérico.

Ahora queda reemplazar, en el fichero *.cs generado, el tipo de datos "Double" de las propiedades que deben tener un número fijo de decimales (ver tabla de campos) por alguno de estos 3.

Para ahorrarte algo de trabajo, aquí tienes para descargar dos ficheros, generados con xsd.exe y modificados para sacar correctamente el número de decimales, correspondientes a las versiones 3.1 y 3.2 de la facturaE: Haz click aquí para descargar.

Llegados a este punto ya puedes generar tu facturaE. Puedes comprobar la validez de tu XML en la web del ministerio de industria, turismo y comercio. Tu fichero debe pasar correctamente la validación de formato y, si has leído bien la tabla de campos y los importes de tu factura son correctos, también debe pasar con éxito la validación contable. Pero nos falta una cosa importante, la firma.

Para poder firmar el XML, necesitas un certificado digital expedido por una entidad certificadora reconocida. Puedes pedirlo en la web de la fábrica nacional de moneda y timbre.

Una vez tengas el certificado (fichero en formato *.cer, *.pfx...) te podrás hacer uso de la clase X509Certificate2 para cargarlo en memoria y realizar la firma. Llegados a este punto hay varias formas de cargar el certificado: a partir del fichero físico, buscando un certificado instalado en el equipo, presentando una lista de certificados y dando al usuario la posibilidad de elegir uno... En este ejemplo vamos a leer un certificado instalado en el equipo, usando asp.net. El usuario sobre el que se ejecuta asp.net no tiene suficientes permisos para leer la clave privada (necesaria para firmar) del certificado si lo importamos con el asistente de importación (importará la clave privada en el almacén del usuario con el que hemos iniciado sesión).

Para importar un certificado y poder usarlo con asp.net, lo haremos siguiendo estos pasos (repito que esto sólo es necesario para usar certificados con asp.net):

  1. Entra al sistema como administrador
  2. Haz click en Inicio -> Ejecutar. Escribe mmc y pulsa intro
  3. Pulsa en Archivo -> Agregar o quitar complemento, y haz click en "Agregar"
  4. Busca en la lista "Certificados" y haz doble click sobre él. Elige "cuenta de equipo" y pulsa siguiente
  5. Elige la opción en función de donde se encuentre el certificado (equipo local o remoto)
  6. Pulsa finalizar (aparece "Certificados" en la lista de complementos agregados)
  7. Pulsa "Aceptar"
  8. Despliega el nodo "Certificados"
  9. Haz click con el botón DERECHO en la carpeta "Personal" y elige "Todas las tareas -> Importar"
  10. Pulsa siguiente, busca y elige el certificado
  11. Elige el almacén "Personal" para guardar el certificado y finaliza
Ahora vamos a dar permisos al directorio MachineKeys:
  1. Navega hasta el directorio donde se almacenan las claves: C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys
  2. Asegúrate de NO estar usando el modo compartido simple de archivos (herramientas -> opciones de carpeta... -> Pestaña ver, al final del todo)
  3. Pulsa con el botón derecho en el directorio MachineKeys. Elige "Propiedades" y luego la pestaña "Seguridad"
  4. Añade el usuario IUSR_*** y dale permisos para todo (control total)
Una vez tenemos ya el certificado almacenado, podemos usarlo para firmar el XML generado. Vamos a firmarlo con XAdES. Pero , ¿cómo? Bien, todavía no he dado con la forma correcta. Pero puedo decirte cómo hacerlo para que veas cómo queda el fichero, pero sólo a modo de prueba. ¿Por qué? Porque lo haremos con una librería licenciada para usarla con factOffice (es un plugin para trabajar con facturas electrónicas desde MsOffice) y no se permite usarla fuera del complemento.

Desde la web de factOffice encontramos un enlace a dicha librería (Descargar) que podemos incluir en nuestro proyecto, repito, para pruebas sólamente. También necesitaremos el archivo BouncyCastle.Crypto.dll que podemos encontrar en el código fuente de factOffice. Una vez importadas estas dos librerías, firmar el xml es tan sencillo como esto:
XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = true;
    doc.Load("c:\\temp\\pruebas.xml");

    //Obtener certificado
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection certCollection = store.Certificates.Find(X509FindType.FindBySubjectName, "NOMBRE DE LA ENTIDAD", false);

    if (certCollection.Count == 1)
    {
        //Firmar con XAdES-EPES (formato básico con la mínima información)
        XadesSignature xadesSignature = new XadesSignature();
        xadesSignature.XadesEpes(doc, "Nombre Entidad", certCollection[0], "Nombre Aplicación");
        //Guardar versión firmada
        doc.Save("c:\\temp\\pruebas_firmado.xml");
    }

    //Cerrar almacén de certificados
    store.Close();

Llegados a este punto tenemos generado el fichero XML de forma que pasaría la validación de formato, la validación contable y la validación del certificado.

Pronto postearé la forma de realizar la firma sin usar la librería cedida por BackTrust y así poder incluir tranquilamente el firmado de XAdES en tu aplicación.

Actualización 20/05/2010
Lo prometido es deuda (aunque esto sólo se dice cuando se cumple la promesa) y aquí os ofrezco una forma de firmar facturas electrónicas con XAdES usando librerías que se pueden distribuir tranquilamente en vuestra aplicación.

Lo que he hecho es usar IKVM.NET para convertir las librerías Java, ofrecidas por el ministerio de industria, turismo y comercio, a librerías dll. Usando jar2ikvmc podemos crear un script para usar con ikvmc, de forma que tendrá en cuenta las dependencias de cada fichero jar.
Una vez convertidos los jar a dll, tal cual, intenté hacer una firma XAdES-EPES, tal y como se explica en los ejemplos del ministerio,  pero no funcionó debido a que el programa no reconocía la política de firma. El programa estaba intentando acceder a un fichero para cargar dinámicamente los distintos tipos de políticas de firma, para luego crear una instancia de la clase encargada de gestionar dicha política. Pero todo este proceso no funcionó con las librerías convertidas a dll. Así que tuve que modificar los fuentes java para permitir especificar las clases para la política de firma en tiempo de compilación (no es tan dinámico, pero ¡funciona en .net!), compilar y crear los ficheros jar, y volver a crear las dll's. Toda la información para usar/modificar/recompilar los fuentes se encuentra aquí.

Una vez hecho esto he conseguido firmar facturas en formato facturaE, con XAdES-EPES, a partir de un certificado almacenado en un fichero pfx. Para hacerlo tú también sigue estos pasos:
    • Incluye las siguientes dll's en tu proyecto C# (las encuentras en la descarga anterior): 
      • commons-logging-1.1 
      • facturaE_adittional
      • IKVM.OpenJDK.Core
      • IKVM.OpenJDK.Security
      • IKVM.OpenJDK.Text
      • IKVM.OpenJDK.Util
      • IKVM.OpenJDK.XML.API
      • IKVM.OpenJDK.XML.Parse
      • IKVM.Runtime
      • MITyCLibAPI-1.0.4
      • MITyCLibCert-1.0.4
      • MITyCLibPolicy-1.0.4
      • MITyCLibTrust-1.0.4
      • MITyCLibTSA-1.0.4
      • MITyCLibXADES-1.0.4
      • xmlsec-1.4.2-ADSI-1.0
      Nota: Algunos usuarios me han informado de que la dll IKVM.OpenJDK.XML.Parse no se copia automáticamente al directorio del resultado de la solución. Llevadlo en cuenta por si es vuestro caso.
      • Incluye los siguientes "using" en el código:
      • using java.security;
        using java.io;
        using java.util;
        using java.security.cert;
        using javax.xml.parsers;
        using es.mityc.javasign.pkstore;
        using es.mityc.javasign.pkstore.keystore;
        using es.mityc.javasign.trust;
        using es.mityc.javasign.xml.xades.policy;
        using es.mityc.firmaJava.libreria.xades;
        using es.mityc.javasign.xml.refs;
        using es.mityc.firmaJava.libreria.utilidades;
        using org.w3c.dom;
        using sviudes.blogspot.com; 
      • Usa este código para firmar facturas:
      • private Document LoadXML(string path)
        {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         dbf.setNamespaceAware(true);
         return dbf.newDocumentBuilder().parse(new BufferedInputStream(new FileInputStream(path)));
        }
        
        private X509Certificate LoadCertificate(string path, string password, out PrivateKey privateKey, out Provider provider)
        {
         X509Certificate certificate = null;
         provider = null;
         privateKey = null;
        
         //Cargar certificado de fichero PFX
         KeyStore ks = KeyStore.getInstance("PKCS12");
         ks.load(new BufferedInputStream(new FileInputStream(path)), password.ToCharArray());
         IPKStoreManager storeManager = new KSStore(ks, new PassStoreKS(password));
         List certificates = storeManager.getSignCertificates();
        
         //Si encontramos el certificado...
         if (certificates.size() == 1)
         {
          certificate = (X509Certificate)certificates.get(0);
        
          // Obtención de la clave privada asociada al certificado
          privateKey = storeManager.getPrivateKey(certificate);
        
          // Obtención del provider encargado de las labores criptográficas
          provider = storeManager.getProvider(certificate);
         }
        
         return certificate;
        }
        
        private void btnFirmar_Click(object sender, EventArgs e)
        {
         PrivateKey privateKey;
         Provider provider;
         X509Certificate certificate = LoadCertificate("c:\\temp\\certificado.pfx", "contraseña", out privateKey, out provider);
        
         //Si encontramos el certificado...
         if (certificate != null)
         {
          //Política de firma (Con las librerías JAVA, esto se define en tiempo de ejecución)
          TrustFactory.instance = es.mityc.javasign.trust.TrustExtendFactory.newInstance();
          TrustFactory.truster = es.mityc.javasign.trust.MyPropsTruster.getInstance();
          PoliciesManager.POLICY_SIGN = new es.mityc.javasign.xml.xades.policy.facturae.Facturae31Manager();
          PoliciesManager.POLICY_VALIDATION = new es.mityc.javasign.xml.xades.policy.facturae.Facturae31Manager();
        
          //Crear datos a firmar
          DataToSign dataToSign = new DataToSign();
          dataToSign.setXadesFormat(EnumFormatoFirma.XAdES_BES); //XAdES-EPES
          dataToSign.setEsquema(XAdESSchemas.XAdES_132);
          dataToSign.setPolicyKey("facturae31"); //Da igual lo que pongamos aquí, la política de firma se define arriba
          dataToSign.setAddPolicy(true);
          dataToSign.setXMLEncoding("UTF-8");
          dataToSign.setEnveloped(true);
          dataToSign.addObject(new ObjectToSign(new AllXMLToSign(), "Descripcion del documento", null, "text/xml", null));
          dataToSign.setDocument(LoadXML("c:\\temp\\unsigned.xml"));
        
          //Firmar
          Object[] res = new FirmaXML().signFile(certificate, dataToSign, privateKey, provider);
        
          // Guardamos la firma a un fichero en el home del usuario
          UtilidadTratarNodo.saveDocumentToOutputStream((Document)res[0], new FileOutputStream("c:\\temp\\signed.xml"), true);
         }
        }
        

      Si ejecutas este código (cambiando las rutas del certificado, y los xml) se generará un xml que puedes validar aquí y comprobar que la firma es correcta. Este ejemplo usa la política de firma del formato 3.1 de facturaE, si quieres cambiar la política de firma tendrás que cambiar la asignación de las variables PoliciesManager.POLICY_SIGN y PoliciesManager.POLICY_VALIDATION.

      No he probado a firmar facturas con otro tipo de firma, igual funciona, igual no. En el fichero que puedes descargar más arriba incluyo todas las librerías del ministerio convertidas a dll, así como los fuentes modificados por si quieres hacer más modificaciones. Si necesitas más librerías de IKVM.NET las puedes descargar desde su web oficial.

      Espero que este post haya sido de ayuda.

      lunes, 14 de diciembre de 2009

      ASP.NET: Deshabilitar un botón durante un postback

      Los usuarios más desesperados tienen la manía de pulsar varias veces un botón cuando éste tarda en responder. O hay otros usuarios que suelen hacer doble click en todo tipo de botones. Esto puede hacer que tu web en Asp.net no funcione bien, ya que podría realizar varias veces la misma acción.

      Solucionar esto en una página con AJAX es muy sencillo mediante javascript usando este código:

      //Guardo el último elemento que ha causado un postback
        var postBackElementID;
      
        //Guardar estado del "PostbackElement"
        var peCursorStyle;
        var peDisabled;
        var peLinkTarget;
        var peLinkClick;
      
        //Obtener referencia a PageRequestManager
        var prm = Sys.WebForms.PageRequestManager.getInstance();
        
        //Añadir eventos de PageRequestManager
        prm.add_initializeRequest(InitializeRequest);
        prm.add_endRequest(EndRequest);
          
        function InitializeRequest(sender, args){
          //Obtengo el elemento que ha causado el postback
          postBackElementID = sender._postBackSettings.sourceElement != undefined ? sender._postBackSettings.sourceElement.id : "";
          
          //Deshabilito el botón que ha causado el postback
          if(postBackElementID != "" && $(postBackElementID)){
              var postBackElement = $(postBackElementID);
          
              peCursorStyle = postBackElement.style.cursor;
              peDisabled = postBackElement.disabled;
              postBackElement.style.cursor = 'wait';
              postBackElement.disabled = true;
              
              if(postBackElement.tagName == 'A'){
                  peLinkTarget = postBackElement.href;
                  peLinkClick = postBackElement.onclick;
                  postBackElement.href = 'javascript:void(0);';
                  postBackElement.onclick = new Function('return false;');
              }
          }
        }
        
        function EndRequest(sender, args){
          //Habilito el botón que ha causado el postback (si estaba habilitado)
          if(postBackElementID != "" && $(postBackElementID)){
              var postBackElement = $(postBackElementID);
          
              postBackElement.style.cursor = peCursorStyle;
              postBackElement.disabled = peDisabled;
      
              if(postBackElement.tagName == 'A'){
                  postBackElement.href = peLinkTarget;
                  postBackElement.onclick = peLinkClick ? peLinkClick : "javascript:void(0);";
              }
          }
        }
      

      InitializeRequest se ejecutará al inicio del postback, mientras que EndRequest lo hará al finalizar éste. En el primero deshabilitamos el control y guardamos su estado. En el segundo reestablecemos el estado de dicho control.

      Nota: En este ejemplo se usa la librería prototype. Si no la estás usando, cambia $ por document.getElementById.

      lunes, 2 de noviembre de 2009

      ResolveURL en ASP.NET sin Page

      Programando en Asp.net estoy acostumbrado a usar ResolveURL cada vez que quiero obtener una dirección completa a partir de una de la forma ~/carpeta/fichero.aspx. En ocasiones no disponemos del objeto Page para acceder a este método (por ejemplo en una librería de funciones). Para solucionar este problema usamos al método VirtualPathUtility.ToAbsolute(url).

      martes, 27 de octubre de 2009

      6 formas de obtener el directorio actual en C#

      Aquí tienes 6 formas distintas de obtener el directorio actual con C#:

      AppDomain.CurrentDomain.BaseDirectory: Esta es la mejor opción, te devuelve el directorio base donde se encuentran las librerías, incluyendo aplicaciones ASP.NET

      Directory.GetCurrentDirectory(): Realiza una llamada a GetCurrentDirectory de kernel32.dll. La documentación de MSDN afirma que no se garantiza que funcione en dispositivos móviles.

      Environment.CurrentDirectory: este llama a Directory.GetCurrentDirectory()

      Assembly.Location: se utiliza de la forma:

      this.GetType().Assembly.Location
      

      Devuelve la ruta completa al ensamblado, incluyendo el nombre del ensamblado en si.

      Application.StartupPath: Está en el espacio de nombres System.Windows.Forms, se suele usar en los Windows Forms únicamente.

      Application.ExecutablePath: Es lo mismo que Application.StartupPath, pero este incluye el nombre del ejecutable.

      lunes, 7 de septiembre de 2009

      ERROR en Asp.NET: El depurador no puede seguir ejecutando el proceso

      Si trabajas con asp.net, bajo windows vista e iis7, posiblemente te topes con este error durante la depuración del proyecto web. Estás depurando y, tras alrededor de un minuto y medio ó dos minutos compruebas que ya no funciona la depuración y obtienes este mensaje:


      Pues bien, la solución es muy sencilla. Simplemente entra al administrador de internet information services, abre las configuración avanzada del grupo de aplicaciones (appPool) de tu aplicación web y cambia el valor de la propiedad "Ping habilitado" a false.... y ¡ya está!, ¡solucionado!