Olá pessoal.
Estou disponibilizando um sistema de exemplo para geração de XML, validação e assinatura digital.
Atenção: A classe disponibilizada foi bastante alterada daquela que eu utilizo nas empresas no dia a dia, visando torna-la ainda mais automatizada com Reflection, portanto, não testei todas as situações possíveis, mas já aviso antecipadamente que qualquer problema com a classe vocês podem perguntar que vou ajustando.
Antes de tudo, esta classe não é para ser um exemplo demonstrando a melhor forma de se gerar um XML para NFe, na verdade quando iniciei o desenvolvimento a intenção era uma geração de XML com manutenção fácil para que no futuro as alterações de Layout fossem o mais simples possível.
Portanto, explicando basicamente, a classe foi inteiramente escrita conforme o manual de Integração, utiliza Reflection para uma geração mais automatizada e atributos para facilitar a formatação e outras configurações do XML. Acredito que o meu objetivo inicial com ela, que era ter uma classe de fácil manutenção foi atingida.
Vou começar explicando como a classe funciona e no final deste posto você encontra um aplicativo exemplo com a classe em uso. No meu sistema original esta classe está mais organizada, mas para o exemplo, coloquei as classes um pouco bagunçadas dentro de arquivos, mas acredito que está bem fácil de entender.
Bom, a classe não tem muito segredo, foi inteiramente digitada, é necessário apenas explicar alguns dos recursos que implementei.
Atributos
Entre as várias propriedades da classe, em algumas você encontra atributos como no exemplo abaixo:
[Formato("#####0.0000", "en-US")]
public decimal qCom { get; set; }
O Atributo [Formato] define o formato que o valor precisa no XML, além de informar a cultura, como “en-US” ou “pt-br”.
[Obrigatorio]
public string IE { get; set; }
O Atributo [Obrigatorio] define se o campo deve sair no XML mesmo quando não tiver um valor definido, como o IE por exemplo, que mesmo que esteja em branco deve ser informado no XML ( ou ).
Além disso, várias propriedades podem ser nulas, como esta no exemplo abaixo:
public decimal? qTrib { get; set; }
Estas propriedades serão ignoradas pelo reflection se não tiverem um valor definido, utilize quando a TAG não deve ser informada no XML quando não possuir um valor.
Geração do XML
Pretendo explicar basicamente como desenvolvi a geração do XML através do Reflection. Vale lembrar que talvez algumas coisas devem ser arrumadas aqui ainda, visto que para este Post no Blog eu alterei a classe visando tornar ela ainda mais automatizada. Além disso, não me crucifiquem por algumas linhas de código, em algumas funções eu realmente não encontrei uma melhor de forma de fazer, mas uma das razões de postar esta classe é ter um feedback de vocês e melhorar ela ainda mais
Com a classe preenchida, a geração do XML é simples:
NFe teste = new NFe();
//Código para preencher a classe... no exemplo ele está disponível, aqui resolvi economizar espaço pulando esta parte
XmlDocument xmlGerado = teste.GerarXML();
//Salva uma cópia do XML não assinado - ATENÇÃO - se você está utilizando Windows Vista/7/Server, salvar na Unidade C pode não ser possível caso o VS2008 não esteja rodando como administrador
xmlGerado.Save("c:\\testeXMLNaoAssinado.xml");
//Seleciona o certificado
X509Certificate2 certificado = CertificadoDigital.SelecionarCertificado();
//assina o xml
XmlDocument xmlAssinado = CertificadoDigital.Assinar(xmlGerado, "infNFe", certificado);
//Valida o XML assinado
string resultado = ValidaXML.ValidarXML(xmlAssinado);
if (resultado.Trim().Length == 0)
resultado = "Xml gerado com sucesso, nenhum erro encontrado";
//Opcional - Função para gerar o Lote e deixar o arquivo pronto para ser enviado.
//teste.GerarLoteNfe(ref xmlAssinado);
//Importante:
//Salvar através do TextWriter evita que o XML saia formatado no arquivo, desta forma o mesmo
//pode ser rejeitado por alguns estados e/ou não validar nos programas teste
using (XmlTextWriter xmltw = new XmlTextWriter("C:\\testeXML.xml", new UTF8Encoding(false)))
{
xmlAssinado.WriteTo(xmltw);
xmltw.Close();
Eis a função GerarXML() – Veja os comentários no código para entender melhor
public XmlDocument GerarXML()
{
XmlWriterSettings configXML = new XmlWriterSettings();
configXML.Indent = true;
configXML.IndentChars = "";
configXML.NewLineOnAttributes = false;
configXML.OmitXmlDeclaration = false;
Stream xmlSaida = new MemoryStream();
XmlWriter oXmlGravar = XmlWriter.Create(xmlSaida, configXML);
oXmlGravar.WriteStartDocument();
oXmlGravar.WriteStartElement("NFe","http://www.portalfiscal.inf.br/nfe"); //abre nfe
oXmlGravar.WriteStartElement("infNFe");
oXmlGravar.WriteAttributeString("xmlns","xsi",null,"http://www.w3.org/2001/XMLSchema-instance");
oXmlGravar.WriteAttributeString("Id", "NFe" + Id.ToString());
oXmlGravar.WriteAttributeString("versao", versao.ToString());
Type tipoObjeto;
tipoObjeto = infNFE.Ide.GetType();
PropertyInfo[] propriedades;
propriedades = tipoObjeto.GetProperties();
//A Função objetoParaXML utiliza o reflection para ler as propriedades da classe e gerar o XML
objetoParaXML(oXmlGravar, infNFE.Ide,false);
objetoParaXML(oXmlGravar, infNFE.Emit,false);
objetoParaXML(oXmlGravar, infNFE.Dest, false);
foreach (infNFE.det detalhe in infNFE.Det)
{
oXmlGravar.WriteStartElement("det");
oXmlGravar.WriteAttributeString("nItem", detalhe.nItem.ToString());
objetoParaXML(oXmlGravar, detalhe.Prod, false);
oXmlGravar.WriteStartElement("imposto");
objetoParaXML(oXmlGravar, detalhe.Imposto.Icms, false);
objetoParaXML(oXmlGravar, detalhe.Imposto.Ii, false);
objetoParaXML(oXmlGravar, detalhe.Imposto.Ipi, false);
objetoParaXML(oXmlGravar, detalhe.Imposto.Pis, false);
objetoParaXML(oXmlGravar, detalhe.Imposto.Cofins, false);
oXmlGravar.WriteEndElement(); //fecha TAG imposto...
oXmlGravar.WriteEndElement(); //fecha TAG det...
}
objetoParaXML(oXmlGravar, infNFE.Total, false);
objetoParaXML(oXmlGravar, infNFE.Transp, false);
objetoParaXML(oXmlGravar, infNFE.Cobr, false);
objetoParaXML(oXmlGravar, infNFE.InfAdic, false);
oXmlGravar.WriteEndElement(); //fecha infNFe
oXmlGravar.WriteEndElement(); //fecha NFe
oXmlGravar.Flush();
xmlSaida.Flush();
xmlSaida.Position = 0;
XmlDocument documento = new XmlDocument();
documento.Load(xmlSaida);
oXmlGravar.Close();
return documento;
}
Função objetoParaXML, que gera o XML
private void objetoParaXML(XmlWriter xmlWriter, object objeto, bool ignorarDeclaracaoElemento)
{
if (objeto == null)
return;
Type tipoObjeto;
tipoObjeto = objeto.GetType();
PropertyInfo[] propriedades;
propriedades = tipoObjeto.GetProperties();
if (!ignorarDeclaracaoElemento)
xmlWriter.WriteStartElement(tipoObjeto.Name);
foreach (PropertyInfo propriedade in propriedades)
{
//A Função novaTag verifica se a propriedade em sí é um novo elemento, como o enderEmit
//no caso do emitente, e verifica tbm se esta propriedade foi declarada (!= null), se não ignora
if (Funcoes.novaTag(propriedade) && !(propriedade.GetValue(objeto, null) == null))
{
//utilizando recursão
objetoParaXML(xmlWriter, propriedade.GetValue(objeto, null), false);
continue;
}
object[] obj = propriedade.GetCustomAttributes(false);
Funcoes.gravarElemento(xmlWriter, propriedade.Name, propriedade.GetValue(objeto, null), obj);
}
if (!ignorarDeclaracaoElemento)
xmlWriter.WriteEndElement();
}
Bom, por enquanto é isso, não sei se consegui deixar claro sobre como funciona a classe, e agora estou com o tempo curto e vou postar assim mesmo, mas os comentários estão abertos para que todos possam perguntar, e assim vou melhorando este post e os códigos disponíveis.
Detalhe: A Classe está sendo disponibilizada gratuitamente, mas por favor, se você fizer alterações, notifique-me para que eu possa melhorar o código disponível.
Link para Download do Exemplo
Para usar, mude o código do button1, adicionando um CNPJ válido, IE, nome de empresa e etc.