Mostrando postagens com marcador C#. Mostrar todas as postagens
Mostrando postagens com marcador C#. Mostrar todas as postagens

14 de fevereiro de 2012

Aplicando recursos de localização em Asp.Net

por Tiago Oliveira*

Embora seja referenciado por uma palavra um tanto quanto estigmatizada - globalização - o fato é que o mundo ficou mesmo menor e os fabricantes de aplicações também passaram a levar isso em consideração. Ao planejar a construção de programas e sites, eles têm agora que incluir o conceito de localização na arquitetura, de uma forma que a aplicação se adeque à linguagem preferida pelo usuário sem que seja necessária uma reengenharia nas linhas de código.

O ASP.NET possui recursos de localização para que possamos fazer aplicações globalizadas, ou seja, para que a aplicação se adeque à região onde ela está sendo executada. A configuração utilizada pelo ASP.NET é a informada no navegador (no caso do IE, está no seguinte caminho : Opções de Internet/Geral/Idiomas), sendo considerado tanto o Idioma quanto a Região estipulados. Segue abaixo um exemplo de configuração:
en-US – Inglês região dos EUA.
pt-BR – Português região do Brasil.
As duas primeiras letras no código representam o Idioma e as duas últimas a região.

Agora vamos aplicar os recursos de localização disponíveis no ASP.NET para modificar esse comportamento padrão e fornecer um mecanismo para que o usuário opte pelo idioma de sua preferência na própria aplicação. Adicione um WebForm contendo 2 botões que, no caso, irão representar os possíveis idiomas suportados para visualização da aplicação : português e inglês.

Adicione um outro WebForm contendo alguns componentes Label, TextBox, etc. Para aplicar a localização às páginas da aplicação, o ASP.NET utiliza arquivos de Resources; por isso, vamos adicionar um arquivo de resource em nossa aplicação: clique no WebForm e selecione na guia Tools do Visual Studio a opção “Generate Local Resource”. Ao escolher essa opção. um arquivo de extensão .RESX com o mesmo nome da página será gerado dentro de uma pasta chamada App_LocalResources que será automaticamente adicionada ao seu projeto.

Dentro desse arquivo .RESX existe a relação de todos os componentes que comportam o recurso de localização, juntamente com a descrição dos campos. No nosso exemplo, esse arquivo irá representar a versão em português da aplicação; para gerarmos o arquivo contendo a versão em inglês, faça uma cópia do arquivo NomeDaSuaPagina.aspx.resx gerado pelo Visual Studio e altere o nome para NomeDaSuaPagina.aspx.en-US.resx. Nesse novo arquivo, faça a tradução dos campos para o Inglês.

No WebForm onde colocamos os 2 botões para identificar os idiomas, iremos passar a localização através de Query String, conforme código abaixo associado ao clique de cada um dos botões:
protected void ImageButton1_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=pt-BR");
}

protected void ImageButton2_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=en-US");
}
Na página Conteudo.aspx (representando a página principal da aplicação), precisamos inicializar a cultura de exibição para considerar a que passamos por parâmetro e não aquela que está configurada no navegador. Para isso precisamos sobrescrever o método InitializeCulture da página e ajustar a variável interna UICulture, que é a responsável por controlar o idioma em que a aplicação está sendo exibida:
protected override void InitializeCulture()
{
UICulture = Request.QueryString["Language"];
base.InitializeCulture();
}
Ao exibir os textos da página, o ASP.Net passará a considerar automaticamente o conteúdo do arquivo .RESX referente ao idioma e região escolhidos.

* Tiago Oliveira é desenvolvedor na ABC71, onde trabalha com os módulos fiscais, de faturamento e de vendas.


20 de julho de 2011

Usando um Web Service no Visual Studio - parte II

Escrevi um post em 2009 explicando alguns detalhes sobre o funcionamento de Web Services e mostrando como usá-los em um projeto do Visual Studio. Aquele post descrevia como usar o IDE para gerar automaticamente uma classe proxy, necessária para fazermos a chamada das funções remotas disponibilizadas pelo serviço. Tal classe é chamada de "proxy" por ser projetada segundo o design pattern que leva esse nome.

Como se vê no post, gerar o proxy é um procedimento muito simples, assim como utilizá-lo para executar a função remota. Para a grande maioria das situações, usar o Web Service desse modo é suficiente. No entanto, há uma desvantagem na forma como isso é feito. Uma vez que o processo todo é automatizado e o trâmite da comunicação com o servidor fica escondido nas bibliotecas da plataforma .Net, não é possível customizar parte alguma da comunicação.

Mas, porque seria necessário interceptar a comunicação e alterar alguma coisa ? Uma razão óbvia é depuração. Isso é, se acontecer um erro, é interessante saber quais foram os comandos enviados e os respectivos retornos fornecidos pelo servidor. Um sniffer não conseguiria fazer esse serviço caso o web service esteja num canal seguro (HTTPS) pois as informações são criptografadas.

Uma outra razão seria customizar a serialização das informações, oportunidade para enxugar o texto trafegado, removendo-se partes sobressalentes. Ou, ainda, para adequar a formatação dos parâmetros da função que são trafegados. Do ponto de vista do proxy, a serialização é o processo pelo qual o nome da função remota e os parâmetros que passamos para ela são transformados em texto e empacotados num XML antes de serem enviados para execução no servidor. Ao receber uma resposta do servidor, o proxy tem que aplicar o processo inverso no XML recebido, isto é, fazer a desserialização, convertendo o conteúdo do XML em tipos de dados nativos da linguagem e associando-o ao valor de retorno da função. Lembre-se que o protocolo usado para comunicação com web services é o SOAP, que é baseado em XML. Portanto, um parâmetro com tipo de dado complexo pode exigir uma formatação especial pra funcionar.

Já que o código gerado automaticamente pelo IDE do Visual Studio não permite interceptar a serialização, teremos que optar por um método alternativo. O SDK do .NET vem com uma ferramenta de linha de comando chamada WSDL.EXE. Basicamente, nela você informa o arquivo descritor do Web Service (WSDL) e ela gera a classe proxy apropriada. O quadro abaixo mostra um exemplo básico de como usar a ferramenta:
wsdl /language:CS /n:"WNameSpace" WMeuServico.wsdl

Nesse exemplo, o WSDL.EXE interpretará a descrição contida no arquivo WMeuServico.wsdl e gerará um uma classe proxy em C# inserindo-a no namespace WNameSpace. A diferença em relação ao proxy gerado diretamente pelo Visual Studio está na infraestrutura de classes envolvidas. No caso do WSDL.EXE, o proxy é uma herança de SoapHttpClientProtocol. A execução dos métodos dessa classe pode ser extendida usando-se atributos que implementem as funções contidas em SoapExtension. Ou seja, cria-se uma herança de SoapExtension que então é aplicada como um atributo da função existente no Web Service. Quando a função remota é chamada, a serialização passa a ser controlada pela nossa versão do SoapExtension.

O cerne dessa solução é o método ProcessMessage. Ele recebe uma instância com a mensagem SOAP atual, permitindo manipulá-la conforme a necessidade. O exemplo a seguir cria um log com as mensagens trafegadas no Web Service:
public class TWTraceExtension : SoapExtension
{
/* ... */
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
StreamWriter w = new StreamWriter(_LogFile);
w.WriteLine ("=> Serializado");
w.Flush();
message.Stream.Position = 0;
Copy(message.Stream, fs);
w.Close();
break;
case SoapMessageStage.BeforeDeserialize:
StreamWriter w = new StreamWriter(_LogFile);
w.WriteLine ("=> Desserializado");
w.Flush();
message.Stream.Position = 0;
Copy(message.Stream, fs);
w.Close();
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
/* ... */
}

Agora que temos a extensão do processo de serialização e desserialização, precisamos criar um atributo que seja aplicável ao método do Web Service. Fazemos isso criando uma extensão da classe de atributo SoapExtensionAttribute, cujo objetivo principal é determinar qual classe deve ser usada para serializar. Veja um exemplo:
[AttributeUsage(AttributeTargets.Method)]
public class TWTraceExtensionAttribute : SoapExtensionAttribute
{
/* ... */
public override Type ExtensionType
{
get { return typeof(TWTraceExtension); }
}
/* ... */
}

Veja que a propriedade ExtensionType é sobrescrita para retornar informações sobre nossa classe de serialização. Um outro detalhe é que a declaração de nossa classe de atributo é ela própria configurada por um atributo indicativo do tipo de uso esperado para ela : ser atributo de métodos de outras classes.

Por fim, devemos usar o atributo criado acima para configurar o método do Web Service que vamos utilizar. Isso implica modificar o fonte gerado automaticamente pelo WSDL; então, tal configuração terá que ser refeita se precisarmos gerar o fonte de novo do Web Service. Veja um exemplo de configuração:
/* ... */
public partial class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol
{
/* ... */
[TWTraceExtensionAttribute(Filename = "c:\\log.txt")]
public string consultaNFe ([System.Xml.Serialization.XmlAnyElementAttribute()]
System.Xml.XmlElement Any)
{
/* ... */
}

A mesma solução pode ser adotada para a classe servidora do Web Service, situação em que estaremos interceptando as mensagens que um Web Service desenvolvido por nós está trocando.

Um exemplo completo de customização do SoapExtension pode ser encontrado na documentação dessa classe no MSDN. No exemplo, as mensagens trafegadas são armazenadas num arquivo de log.

23 de março de 2011

Problemas comuns ao consumir Web Services para NFe em C#

Ok, já venceu ou está prestes a vencer o prazo para que uma grande parte das empresas utilizem obrigatoriamente a NFe - Nota Fiscal Eletrônica. Mas, como o brasileiro no geral deixa tudo para a última hora, é bastante provável que este post seja útil pra muita gente. Aqui eu abordo alguns problemas comuns que aparecem quando se utiliza os Web Services necessários para implementar no Visual Studio a comunicação com o site da Receita Federal no âmbito da NFe. Estou usando Visual Studio 2010 com a versão 3.5 do .NET mas creio que a maioria das soluções apresentadas sejam aplicáveis também a outras versões.

Neste outro post, eu falei sobre como utilizar Web Services em um programa .NET. Usando as dicas contidas naquele post para adicionar os Web Services da Receita Federal a um projeto do Visual Studio é possível que você se depare com uma mensagem similar a esta:
The request failed with HTTP status 403: Forbidden.
Metadata contains a reference that cannot be resolved: 'endereço-do-web-service'.
The HTTP request was forbidden with client authentication scheme 'Anonymous'.

O servidor da Receita Federal está configurado para exigir a identificação de todos os usuários através de um certificado digital válido. O acesso através do Visual Studio para obter a descrição WSDL dos serviços não é diferente. O que a mensagem está dizendo é que você não informou um certificado que o autorize a acessar o site. No caso do Visual Studio, é feita automaticamente a seleção de um certificado digital que tenha sido inserido na área Personal do Certificate Store do Windows. Por isso, ter instalado tal certificado válido emitido por uma entidade autorizada (Correios, Serasa ou Certisign, por exemplo) é imprescindível já no momento de adicionar os Web Services ao projeto e não apenas para assinar os XMLs que serão enviados à Receita Federal.

Depois que você conseguiu importar os Web Services, você pode usá-los em seu projeto para enviar solicitações de uso de notas fiscais, dentre outros serviços disponíveis. Quando seu programa chama a função de um dos Web Services para enviar ou solicitar informações ao servidor da Receita, este erro é reportado pelo programa:
The HTTP request was forbidden with client authentication scheme 'Anonymous'.

É bastante parecida com a mensagem anterior mas, nesse ponto, você já deve ter instalado um certificado válido no Certificate Store. Então, a causa provável é que você não configurou a forma com que a mensagem SOAP (o XML de comunicação com o Web Service) será transportada via internet, usando o protocolo HTTPS. Quando você importa um Web Service, o Visual Studio automaticamente insere num arquivo chamado app.config toda a configuração associada ao serviço, incluindo aquelas relativas ao transporte das mensagens via HTTPS.

Há particularmente dois parâmetros neste arquivo cujos valores padrões diferem do exigido pelo servidor da Receita: authenticationScheme e requireClientCertificate. O primeiro indica como o usuário será validado, sendo que o valor padrão é "Anonymous" para indicar que nenhuma validação é esperada. Como eu disse antes, o servidor da Receita só libera o acesso se um certificado foi informado. Portanto, o valor desse parâmetro deve ser alterado para "Digest". O segundo parâmetro deve ter valor "true", confirmando que o servidor exige que sua aplicação envie os dados do certificado para garantir a autenticação. O quadro abaixo traz o trecho de um app.config como exemplo, mostrando como fica a configuração para o serviço de consulta de uma nota fiscal:
<binding name="NfeConsulta2Soap12">
...
<httpsTransport authenticationScheme="Digest" requireClientCertificate="true" ... />
</binding>

Por uma questão de clareza, foram omitidas outras configurações que aparecem neste mesmo contexto. Lembre-se ainda que as configurações são feitas por Web Service, isto é, cada serviço da Receita que você for suportar tem que ser configurado neste arquivo da mesma forma que foi feito no exemplo.

Após realizar as modificações do app.config citadas, você ainda pode ser brindado com o erro abaixo:
The client certificate is not provided. Specify a client certificate in ClientCredentials.

Mais uma vez, a culpa é a falta do certificado digital. Nós declaramos que o serviço exige que especifiquemos um certificado. Mas, ao contrário do ambiente do Visual Studio, um Web Service no seu programa não é capaz de detectar automaticamente qual certificado deve ser usado. Por isso, devemos atribuí-lo manualmente antes de fazer a chamada à função do Web Service:
NfeConsulta2Soap12Client lServ;

lServ = new NfeConsulta2Soap12Client ("NfeConsulta2Soap12", _UrlWebservice);
lServ.ClientCredentials.ClientCertificate.Certificate = GetCertificado;

XmlNode status = lServ.nfeConsultaNF2 (ref lCabec, XmlDoc);

O post neste link mostra como navegar no Certificate Store para encontrar um certificado específico. É o que faz a minha função GetCertificado, usada no código de exemplo acima. A variável _UrlWebservice também é minha e contem o endereço (URL) real do Web Service que será acessado.

A arquitetura montada pela Receita Federal permite que você opere num ambiente de homologação (para testes) ou direto no de produção (onde as notas fiscais entram efetivamente na base de dados da Receita). Além disso, cada unidade da Federação disponibiliza seu próprio conjunto de endereços dos Web Services destinados aos contribuintes dessa unidade. É normal, portanto, que os programas para atender a NFe sejam construídos de forma flexível, permitindo receber os diferentes conjuntos possíveis. Obviamente, essa flexibilidade traz consigo o risco de que um endereço errado seja especificado. Se isto ocorrer, a seguinte mensagem será reportada:
Unable to handle request. The action 'http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2/nfeConsultaNF2' was not recognized.

Isso significa apenas que você está tentando chamar uma função que não está implementada no endereço URL que você informou. A solução aqui é simples: verifique o endereço que está sendo passado à instância do Web Service - no exemplo anterior, seria descobrir o conteúdo da variável _UrlWebservice. Você pode até mesmo gerar um log imediatamente antes de fazer a chamada à função do Web Service, se for o caso.

Uma última recomendação. Se for criar um serviço Windows para monitorar as notas fiscais que entram para submetê-las à Receita, fique atento ao fato de que esse serviço pode ser executado com as credenciais de um usuário diferente daquele que está logado no Windows - normalmente, a conta Local System.

Neste cenário, o serviço não enxergará certificados digitais instalados na área Personal do Store de seu usuário Windows e você poderá receber uma porção das mensagens citadas neste post. Por isso, é recomendável que o certificado esteja instalado na área Personal do Store do Local Computer pois nessa área ele estará disponível a qualquer usuário do computador. Não se esqueça de restringir o acesso ao login desse computador por questões de segurança.


18 de maio de 2009

Incluindo a assinatura digital no XML

No último post, mostrei como criar a assinatura digital de um documento XML. Para poder enviá-lo à Receita Federal é preciso ainda incluir essa assinatura digital no corpo do XML e é isso que vou fazer neste post.

A assinatura tem que ser incluida como um nó filho do elemento NFe, no mesmo nível do nó infNFe, conforme mostrado no excerto de XML de Nota Fiscal abaixo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
<Signature>
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe3508059978"
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>4aXU7m8rl14ALy6X...=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>Igq7eI/Fy3PyjjSW...=</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIGxDCCBaygAwIBAgIIN6q4...=</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</NFe>
</enviNFe>

Considere a variável xmlNFe do tipo XmlElement criada de acordo com o descrito no post sobre criação de nós no XML. Considere também uma instância da classe SignedXml chamada signedXml e preparada da forma descrita no post sobre assinatura de um Xml. Vou usá-las no exemplo a seguir.

Primeiro, criarei um nó para conter o elemento Signature. Depois, vou usar as propriedades SignedInfo e KeyInfo do signedXml para obter nós XML contendo respectivamente dados sobre a informação que foi assinada e sobre a chave usada nessa assinatura:
XmlElement xmlSignature = doc.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");
XmlElement xmlSignedInfo = signedXml.SignedInfo.GetXml();
XmlElement xmlKeyInfo = signedXml.KeyInfo.GetXml();

Note que o namespace passado para a criação do nó Signature é diferente daquele usado para criar as informações da Receita Federal que usei no post sobre a criação de nós. O namespace "http://www.w3.org/2000/09/xmldsig#" indica que vamos usar a sintaxe de assinatura descrita no link do W3C, conforme especificado na documentação da Receita Federal.

Além dos dados que já obtivemos, é necessário ainda acrescentar a assinatura em si como nó filho do elemento Signature. De acordo com a documentação, a assinatura - que é uma sequência de bytes - deve ser representada como um texto utilizando a codificação Base 64. A criação do nó SignatureValue, sua conversão para Base 64 e a adição do nó ao elemento Signature estão no trecho de código abaixo:
XmlElement xmlSignatureValue = doc.CreateElement("SignatureValue", xmlSignature.NamespaceURI);
string signBase64 = Convert.ToBase64String(signedXml.Signature.SignatureValue);
XmlText text = doc.CreateTextNode(signBase64);
xmlSignatureValue.AppendChild(text);
xmlSignature.AppendChild(xmlSignatureValue);

O trecho signedXml.Signature.SignatureValue é onde se recupera o valor da assinatura computada.

Agora, só falta importar os dados contidos nos elementos xmlSignedInfo e xmlKeyInfo obtidos no primeiro passo, acrescentado-os ao elemento NFe:
xmlSignature.AppendChild(doc.ImportNode(xmlSignedInfo, true));
xmlSignature.AppendChild (doc.ImportNode(xmlKeyInfo, true));
xmlNfe.AppendChild(xmlSignature);

Depois de acrescentar o xmlNfe ao documento XML final, deve-se transformar o XmlDocument doc em texto para ser enviado à Receita Federal.

15 de maio de 2009

Assinando um documento XML

Agora que tenho assentada uma terminologia XML e métodos para criar um XML (incluindo o cabeçalho) posso mostrar como assinar digitalmente um documento XML e incluir nele essa assinatura.

Antes, uma consideração importante: a Receita Federal só aceita a Nota Fiscal Eletrônica assinada com um Certificado Digital autorizado, isto é, não vale criar um certificado de teste (com o Certificate Creation Tool, por exemplo) e tentar enviar a nota usando ele. Alguns emissores de certificados autorizados: Correios, Serasa e Certisign.

A Receita Federal usou um subconjunto do padrão do W3C para estipular as regras de assinatura digital da NFe. Devem ser assinados todos os elementos infNFe de um lote XML de Notas Fiscais segundo essas regras. Considerando uma variável do tipo XmlDocument de nome doc, já montada com a estrutura correta da NFe, segue um exemplo em C# de como chegar a esse nó:
XmlNodeList ListInfNFe = doc.GetElementsByTagName("infNFe");

Há uma classe no namespace System.Security.Cryptography.Xml do framework .NET chamada SignedXml, que implementa o padrão W3C para assinatura de documentos e verificação de documentos assinados. O trecho de código abaixo exemplifica a configuração básica desta classe:
foreach (XmlElement infNFe in ListInfNFe)
{
string id = infNFe.Attributes.GetNamedItem("Id").Value;
signedXml = new SignedXml(infNFe);
signedXml.SigningKey = ObtemCertificado().PrivateKey;

O id obtido é um código calculado segundo regras estipuladas pela Receita Federal e serve para identificar cada Nota. Esse id é inserido como um atributo no elemento infNFe. Ele será usado mais adiante, quando for preciso referenciar o elemento infNFe na assinatura. A função ObtemCertificado é minha: ela usa os conceitos descritos no post sobre Certificate Store do Windows para obter um certificado válido. A propriedade SigningKey é a chave que será usada para calcular a assinatura; por isso, é atribuido a ela a chave privada do certificado obtido.

Parte da assinatura é dedicada a descrever o nó a partir do qual a assinatura foi feita e quais transformações esse nó e seus filhos sofreram antes da assinatura ser efetivamente calculada. De acordo com a documentação da Receita Federal, as transformações a serem aplicadas são duas:
1) a que indica que a assinatura é "envelopada", ou seja, o trecho assinado é incluído na assinatura. Aqui cabe um esclarecimento: o elemento infNFe não é o elemento assinado; ele na verdade é usado para calcular um valor chamado de DigestValue, este sim inserido no trecho assinado, de forma que qualquer alteração no conteúdo de infNFe ou de seus filhos resultará num DigestValue diferente e, por fim, a assinatura também não será válida.
2) a que indica que o XML deve ser colocado na forma canônica antes do processamento.

A classe Reference cuida dessa parte do processo, incluindo a identificação do nó infNFe e as transformações exigidas:
// Transformações p/ DigestValue da Nota
Reference reference = new Reference("#" + id);
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigC14NTransform());
signedXml.AddReference(reference);

Antes de computar a assinatura, falta configurar a parte que trata das informações a respeito do certificado digital utilizado. É com base nesses dados que a Receita Federal consegue validar a assinatura e atestar que nenhuma informação da Nota foi modificada depois que o emissor da Nota Fiscal a assinou. Devemos incluir uma cláusula com os dados do certificado:
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(ObtemCertificado()));
signedXml.KeyInfo = keyInfo;

Agora, só falta computar a assinatura:
signedXml.ComputeSignature();

Neste ponto, a variável signedXml contém todos os dados para criar o elemento Signature que deve ser incluído na NFe antes do envio à Receita Federal. Mostro no próximo post como fazer isso.

14 de maio de 2009

Trabalhando com XML - parte 4

Para finalizar essa série sobre a criação de XML através da implementação DOM do C#, falo aqui da exportação desse DOM para o formato texto.

A chave para a exportação é uma classe chamada XmlTextWriter que fica no namespace System.Xml. Ela possui um construtor que aceita dois parâmetros : um stream onde a gravação deve ser feita e o Encoding responsável pela gravação. Stream é uma classe abstrata que apenas define uma interface para auxiliar a gravação e a leitura de bytes; por isso, devemos usar uma das heranças que faça efetivamente o trabalho.

A declaração a seguir é um exemplo que usa a herança chamada MemoryStream, que faz a leitura e gravação em memória:
Encoding enc = Encoding.UTF8;
MemoryStream ms = new MemoryStream();
XmlTextWriter writer = new XmlTextWriter (ms, enc);

No contexto que utilizei esse código na ABC71, usar o MemoryStream era a opção mais conveniente. No entanto, há um construtor do XmlTextWriter que aceita um nome de arquivo como parâmetro. Nesse caso, o texto resultante será enviado diretamente para o arquivo.

Quando criei o cabeçalho para o XML no post anterior, informei o Encoding UTF-8, conforme especifica a documentação da Receita Federal. Porque, então, tenho que informá-lo novamente na gravação ? A questão é que a classe XmlTextWriter usará o Encoding para "transformar" os textos contidos no DOM e, se não o informarmos, um encoding padrão será utilizado para tal "transformação". Isto pode gerar caracteres incorretos, além de fazer com que o cabeçalho exportado contenha o nome do Encoding padrão ao invés daquele que informamos na montagem do XML, tornando-o inválido para a Receita Federal.

Então, informando novamente o Encoding para forçar o XmlTextWriter a gravar o cabeçalho com o valor que precisamos. Simultaneamente, nenhuma transformação é aplicada no conteúdo, preservando a integridade do XML. A classe Encoding usada no exemplo fica no namespace System.Text.

O próximo passo é configurar o XmlTextWriter para que o resultado atenda nossas necessidades. Veja o exemplo:
writer.Formatting = Formatting.Indented;
writer.Indentation = 2;
writer.Namespaces = true;

A primeira linha indica que a gravação deve ser feita de forma estruturada, identando os elementos do XML. A segunda estabelece quantos espaços em branco devem ser usados para identar os elementos. Se não quiser usar identação para que o tamanho do texto exportado seja menor, use Formatting.None na propriedade Formatting: todo o XML será gerado sem quebra de linhas e sem espaços em branco extras. Por fim, indico que há namepaces na estrutura e que eles devem constar no texto exportado. Na verdade, sem essa configuração o .NET levantará uma exceção (Exception) e não executará a exportação pois, se há namespaces informados no DOM, eles devem mesmo constar no texto resultante.

Para finalizar a exportação, falta ainda chamar o comando específico. Estou considerando que a variável doc é do tipo XmlDocument e contém a estrutura XML já montada, nos moldes descritos nos outros posts sobre XML (veja parte 2 e parte 3).
doc.WriteTo(writer);
writer.Flush();
writer.Close();

O resultado disso tudo é que o MemoryStream de nome ms agora contém todo o XML em formato texto, pronto para ser enviado, por exemplo, no corpo de um email ou exibido em tela.

13 de maio de 2009

Trabalhando com XML - parte 3

Quando fiz o post sobre criar um XML usando C#, ficou faltando mostrar a parte de como criar o cabeçalho do XML dentro da estrutura do DOM. Vou mostrar isso neste post e, para tanto, volto a reproduzir o mesmo XML dos outros dois posts sobre o assunto e vou usá-lo como exemplo :
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>

Há uma classe no namespace System.Xml do framework do .Net responsável pela criação do cabeçalho do XML. O nome dessa classe é XmlDeclaration. Considerando a variável doc do tipo XmlDocument criada no post anterior sobre XML, a inclusão do cabeçalho deve ser feita como no exemplo abaixo :
XmlDeclaration decl;
decl = doc.CreateXmlDeclaration("1.0", System.Text.Encoding.UTF8.HeaderName, "");
doc.InsertBefore(decl, doc.DocumentElement);

Os parâmetros para a função CreateXmlDeclaration são a versão do XML (normalmente, o valor "1.0"), em seguida o tipo de codificação a ser utilizado (no exemplo, obtive o nome do encoding UTF-8 definido pelo próprio framework) e, por fim, o valor para o atributo standalone. Como no nosso XML exemplo o standalone não aparece, passei um texto vazio, instruindo a criação do cabeçalho sem essa informação.

A última linha do código faz com que a declaração de cabeçalho seja acrescentada na estrutura DOM do XML imediamente antes do nó raiz. O nó raiz é aqui representado pela propriedade DocumentElement da variável doc (que é um XmlElement).

Nos próximos posts, mostro como exportar essa estrutura DOM para o formato texto de forma que possa ser usado para gravar um arquivo com o conteúdo XML; devo falar também sobre a navegação pelos nós de um XML dentro do DOM e, finalmente, como assinar o XML para enviá-lo à Receita Federal.

11 de maio de 2009

Trabalhando com XML - parte 2

Como eu havia informado no post anterior sobre XML, vou mostrar aqui como criar um documento XML usando C#. O XML a ser criado será um fragmento do layout exigido pela Receita Federal para o envio de Notas Fiscais Eletrônicas; para facilitar, reproduzo novamente aqui o documento a ser criado :
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>
Para iniciar, o pacote onde estão as classes para manipulação de XML através de DOM é o System.Xml. Portanto, é preciso informar isso no início do fonte.

using System.Xml;

O documento XML em si é representado por uma instância da classe XmlDocument. Também vamos usar bastante o namespace definido pela Receita Federal para suas estruturas, então, vou declarar uma constante para facilitar. Inclua as linhas abaixo na sua classe :
static string NFeNamespace = "http://www.portalfiscal.inf.br/nfe";
private XmlDocument doc = new XmlDocument();

O primeiro passo na criação é criar o nó raiz. No nosso exemplo, é como segue :
XmlElement raiz, no, noNFe, noInfNFe;
XmlAttribute att;
raiz = doc.CreateElement("enviNFe", NFeNamespace);
// Atributos do nó de enviNFe
att = doc.CreateAttribute("versao");
att.Value = "1.10";
raiz.Attributes.Append(att);

Note que criei manualmente apenas o atributo versao enquanto o xmlns (namespace) é colocado automaticamente pelo parâmetro passado ao CreateElement. Todos os "nós filhos" compartilharão essa informação de namespace.

Vamos criar agora o elemento idLote, que possui um valor associado :
XmlText noText;
no = doc.CreateElement("idLote", NFeNamespace);
noText = doc.CreateTextNode("71");
no.AppendChild(noText);
// "Nó" é filho de raiz :
raiz.AppendChild(no);

Observe que continua imprescindível passar o namespace na criação do nó idLote; se não for informado, subentende-se que um novo namespace (vazio) deve ser usado.

As mesmas regras vistas até agora valem para criar o nó NFe e o nó infNFe e seus filhos:
// infNFE
noInfNFe = doc.CreateElement("infNFe", NFeNamespace);
att = doc.CreateAttribute("Id");
att.Value = "NFe3508059978";
noInfNFe.Attributes.Append(att);

att = doc.CreateAttribute("versao");
att.Value = "1.10";
noInfNFe.Attributes.Append(att);

// Filhos do infNFE
no = doc.CreateElement("cUF", NFeNamespace);
noText = doc.CreateTextNode("35");
no.AppendChild(noText);
noInfNFe.AppendChild(no);

no = doc.CreateElement("cNF", NFeNamespace);
noText = doc.CreateTextNode("518005127");
no.AppendChild(noText);
noInfNFe.AppendChild(no);

// *** Inclua os demais campos aqui ....

// Hierarquia de nós

noNFe = doc.CreateElement("NFe", NFeNamespace);
noNFe.AppendChild(noInfNFe);
raiz.AppendChild(noNFe);

Para finalizar, é preciso ainda informar ao XmlDocument quem é seu nó raiz:
doc.AppendChild (raiz);

Para não me estender muito, publico no próximo post como incluir o cabeçalho para o XML e como transformar a estrutura DOM num texto que possa ser salvo em arquivo ou transmitido como parâmetro num WebService.

28 de abril de 2009

Ligando .NET e C++ Win32 numa mesma solução

Não é tão comum, mas pode acontecer ter que misturar codificação .NET com codificação Win32. No caso da ABC71, onde temos um legado grande de regras de negócio - para quem não conhece, o 71 do nome é o ano de fundação da empresa - mudar de tecnologia não é um processo simples.
No momento, estamos trabalhando nosso ERP para que ele seja acessível via Web de forma nativa, isto é, sem ter que apelar para gambiarras do tipo "distribuir a aplicação Windows dentro de um VNC ou WTS". O desafio foi fazer isso sem perder todo o conjunto de regras de negócio que já temos validadas e funcionando na versão Windows.

A solução foi incluir um Web Service que recebe solicitações de um applet Java e as distribui para nossas bibliotecas de regras de negócio. Mas, por questão de facilidade no desenvolvimento, o Web Service é um código gerenciado .NET enquanto nossas bibliotecas são em C++ Win32 - portanto, não-gerenciadas. Foi preciso, então, criar uma camada capaz de permitir que esses dois tipos de código se comunicassem.

Pesquisando na Internet, encontrei uma ferramenta que atendeu plenamente esse requisito. Ela se chama SWIG ou Simplified Wrapper and Interface Generator. Apesar de ser uma aplicação simples, sem interface gráfica, é bastante poderosa, permitindo encapsular chamadas de rotinas C++ em diversas linguagens (C#, Java) e em scripts (PERL, Python, Ruby).

No caso da ABC71, montamos uma classe C++ que gerencia chamadas às nossas bibliotecas com as regras legadas e encapsulamos essa classe com SWIG para podermos direcionar a ela as chamadas geradas pelo usuário no applet Java.

Na prática, uma declaração simplificada da classe é assim :


class TWTransactionManager : public BTransaction
{
public:
TWTransactionManager (void);
virtual __fastcall ~TWTransactionManager (void);

virtual TMessageContent getReply (void);
virtual TdomDocument *getDomReply (void);

TWSession *getSession (void);
};

É necessário informar ao SWIG um arquivo contendo as regras para o encapsulamento. Esse arquivo se parece com o reproduzido abaixo :


%module trManagerWEB
%{
#include "WTransactionManager.h"
%}

%include "WTransactionManager.h"

Onde trManagerWeb é o nome da DLL em que a classe está incluída e "WTransactionManager.h" é o header onde está a declaração textual da classe.

Após a execução do SWIG, serão gerados dois conjuntos de arquivos :
1) fontes C++ que preparam a nossa classe para ser chamada externamente. No exemplo, eles têm que ser incluídos no projeto da biblioteca trManagerWeb.
2) fontes C# para serem incluídos no projeto do Web Service, permitindo trabalhar no C# com as classes C++ declaradas na DLL.

Colaborou Arnaldo Marinho.

20 de abril de 2009

Trabalhando com o Certificate Store do Windows

Vem se tornando popular ultimamente o uso de Certificados Digitais para autenticar documentos eletronicamente; a introdução da Nota Fiscal Eletrônica e da Escrituração Digital devem dar um impulso definitivo, já que praticamente todas as empresas terão que adotá-lo para poder assinar as informações enviadas para a Receita Federal.

Mas, o que é na prática um Certificado Digital ? Como é que o Windows trabalha com isso ? O Certificado em si, a grosso modo, é apenas um arquivo onde são armazenadas informações sobre quem outorgou o certificado (uma CA, ou Certificate Authority) e a quem é que foi outorgado o certificado (uma pessoa física ou jurídica ou ainda um computador) bem como um código público (a chave). O Certificado, portanto, associa uma chave pública a uma entidade (empresa, pessoa ou computador). Há um artigo no Wikipedia mais detalhado sobre esse assunto.

Se você (ou sua empresa ou seu computador) é quem recebeu o certificado, você também possui a chave privada, isto é, o código que só você conhece e que será usado para assinar um documento digital, garantindo-lhe a integridade, a autenticidade e conferindo validade jurídica ao documento.

No Windows, o acesso aos Certificados disponíveis se dá de forma centralizada, através do Armazém de Certificados Digitais (ou Certificate Store) . Isto é, uma vez que o certificado foi registrado no computador, não importa onde ele está armazenado : o acesso deve ser feito a partir do store. Para acessá-lo a partir do Windows, vá em Iniciar -> Executar e digite
mmc \windows\system32\certmgr.msc


Esse programa mostra de forma estruturada quais são os certificados presentes em seu computador: aqueles de uso exclusivo de seu usuário (Personal), as Autoridadades Certificadoras (tanto as de nível mais alto na hierarquia certificadora - as root ou raíz - quanto as intermediárias, que dependem da root), etc.

Via programação, o framework do .NET também fornece uma forma estruturada de acessar o Store, através do namespace System.Security.Cryptography.X509Certificates. São basicamente 3 classes para acessar os certificados :
  • X509Store : Representa o acesso ao Store. O construtor dela exige que se estipule qual parte da estrutura deve ser acessada. Use as definições na classe StoreName para identificar, por exemplo, os certificados particulares ("My") ou as Autoridades Certificadoras ("CA"). Pode optar também por informar apenas a localização, através da classe StoreLocation (somente os meus certicados ou todos os certificados presentes no computador).
  • X509Certificate2Collection : Uma lista dos certificados armazenados no Store aberto com a classe anterior.
  • X509Certificate2: Representa um certificado na coleção.
O exemplo abaixo em C# percorre a lista de certificados na Store "My", procurando os válidos e que possuam chave privada.

X509Certificate2Collection lcerts;
X509Store lStore = new X509Store (StoreName.My, StoreLocation.CurrentUser);

// Abre o Store
lStore.Open(OpenFlags.ReadOnly);

// Lista os certificados
lcerts = lStore.Certificates;

foreach (X509Certificate2 cert in lcerts)
{
if (cert.HasPrivateKey && cert.NotAfter > DateTime.Now && cert.NotBefore < DateTime.Now)
{
// Faz o uso do certificado....
// Por exemplo, assinar um docto.
}
}
lStore.Close();

14 de abril de 2009

Validando um XML contra seu esquema XSD

No dia a dia do profissional de TI, cada vez mais precisamos trabalhar com dados no formato XML. Vira e mexe, é preciso ainda saber se um XML está bem formado, isto é, se ele atende os requisitos dispostos num arquivo de esquema XSD. Um exemplo bem recente disso são os XMLs para envio à Receita Federal no âmbito da Escrituração Digital (SPED) e da Nota Fiscal Eletrônica, onde cada XML tem que seguir regras de formação bem definidas pela Receita, através de arquivos XSD. Os esquemas para NFe podem ser encontrados aqui.

Pesquisando no Google, encontrei uma porção de sites capazes de fazer a validação on-line e também ferramentas - algumas gratuitas, outras pagas. No caso da NFe, a própria Receita Federal faz essa validação mas é preciso submeter ao Web Service deles e aguardar a resposta assíncrona.

Como não encontrei nada que eu achasse prático, resolvi montar um validador por minha conta, para ter à mão sempre que necessário. Pra minha surpresa, descobri que o framework .Net já possui classes para validar um XML e que o uso delas é bastante simples.

Mãos à obra ! Montei uma aplicação simples em C# no Visual Studio 2008 : tem 3 caixas de edição e um botão, como mostra o print screen abaixo:

Ao clicar o botão, o arquivo indicado na caixa Caminho do XML é carregado através de um Reader previamente configurado para realizar a validação enquanto faz a carga. Abaixo, o código comentado do evento de clique no botão :
/* XmlReaderSettings é a configuração de como o XML deve ser lido */
XmlReaderSettings settings = new XmlReaderSettings();
FileStream fs = null;

/* Informa qual é o namespace (se houver) e o caminho do esquema XSD que será usado p/ validar o XML durante a leitura */
settings.Schemas.Add (edNamespace.Text, edXSD.Text);

/* Se dentro do XSD houver referência a outros XSDs externos, pode ser necessário ter certas permissões para localizá-lo. Usa um "Resolver" com as credencias-padrão para obter esse recurso externo. */
XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = CredentialCache.DefaultCredentials;

/* Informa à configuração de leitura do XML que deve usar o "Resolver" criado acima e que a validação deve respeitar o esquema informado no início. */
settings.XmlResolver = resolver;
settings.ValidationType = ValidationType.Schema;

try {
/* Abre o arquivo XML usando o caminho que foi informado na tela. */
fs = new FileStream(edXML.Text, FileMode.Open,
FileAccess.Read);

/* Constrói o leitor de XML, passando o arquivo XML que foi aberto e as configurações que sinalizam como o leitor deve trabalhar. */
XmlReader reader = XmlReader.Create (fs, settings);

/* Constrói um documento XML vazio */
XmlDocument xmlDoc = new XmlDocument();

/* Manda carregar as informações nesse XML através do leitor montado acima. */
xmlDoc.Load(reader);

/* Só chega aqui se carregou tudo OK. */
MessageBox.Show(this, "Validação concluída com sucesso.",
"Aviso", MessageBoxButtons.OK,
MessageBoxIcon.Information);

} catch (Exception exc)
{
/* Se houver erro na validação do XML, uma exceção é levantada e o fluxo vem para esse ponto, exibindo a mensagem de erro.*/
MessageBox.Show(this, exc.Message, "Erro",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

/* Fecha o stream para liberar o arquivo. */
if (fs != null)
{
fs.Close();
fs.Dispose();
}
Ao final, se foi tudo OK com a carga, tem-se na instância xmlDoc a árvore do XML montada, validada e pronta para ser usada pelo programa.

Obs: No lugar de ler o XML de um arquivo, pode-se usar qualquer tipo de Stream - até mesmo lido da memória (MemoryStream).

Clique aqui para fazer o download do programa. Inclui aqui no blog também alguns posts sobre a construção de arquivos XSD para facilitar o entendimento de como isso funciona.