Embora muitas pessoas possam torcer o nariz ao ouvirem termos como: Webservices, SOAP, XML, schema, XSD, WSDL, etc. São tecnologias que teremos que lidar em algum momento da nossa vida profissional.
Protocolos e padrões mais leves como REST, gRPC, GraphQL, etc, estão cada vez mais se popularizando, mas existem grandes projetos (sem previsão de migração), consolidados que utilizam o protocolo SOAP para troca de informações entre sistemas.
No Brasil, dois grandes exemplos são a Nota Fiscal Eletrônica - NF-e e o Web Service de Rastreamento dos Correios.
O que vamos aprender neste artigo? |
---|
1. Entender os fundamentos do protocolo SOAP |
2. Criar um serviço genérico para validar qualquer arquivo XML, com diferentes schemas |
3. Criar uma API REST em .Net para fazer a validação de arquivos XML |
1. O protocolo SOAP
Se existe um requisito comum, para a maioria das aplicações corporativas, é a integração entre diferentes sistemas. Nenhum software é (ou não deveria ser) uma ilha!
Se este for o seu caso, inevitavelmente em algum momento você precisará consumir algum serviço remoto, implementado por terceiros, durante o ciclo de vida operacional do seu sistema.
O protocolo SOAP - Simple Object Access Protocol (em português, Protocolo Simples de Acesso à Objeto) possibilita que esta tarefa seja executada de forma bastante eficiente.
SOAP utiliza o protocolo HTTP para transportar os dados. E devido ao fato de utilizar um protocolo padrão, permite que sistemas construídos com diferentes linguagens de programação e sendo executados em diferentes sistemas operacionais, troquem informações de forma transparente.
Além disso, permite que o produtor destes serviços remotos, defina o formato da entrada de dados que será aceito, utilizando para isto os XML Schema - XML Schema Definition (em português, Definição de Esquema XML).
São arquivos com a extensão .xsd, com os seguintes tipos de conteúdo:
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="TEndereco">
<xsd:sequence>
<xsd:element name="logradouro" type="xsd:string"/>
<xsd:element name="complemento" type="xsd:string"/>
<xsd:element name="bairro" type="xsd:string"/>
<xsd:element name="CEP">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value = "9"/>
<xsd:minLength value = "9"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
No exemplo acima, foi definido uma entrada de endereços, chamada de TEndereco, contendo os campos: Logradouro, Numero, Complemento, Bairro e CEP, cada um com a sua respectiva regra de preenchimento. Através deste modelo, poderemos utilizar o tipo TEndereco em outros tipos de dados:
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="TEndereco.xsd"/>
<xsd:element name="pessoa">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="nome">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value = "20"/>
<xsd:minLength value = "2"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="sobrenome">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value = "20"/>
<xsd:minLength value = "2"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="endereco" minOccurs="1" maxOccurs="1" type="TEndereco"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
No exemplo acima, o tipo TEndereco foi utilizado no tipo TPessoa. Desta forma, é possível definir todas as regras do tipo de entrada aceito pelo webservice SOAP.
Dica! |
---|
Para quem trabalha com orientação a objetos, os schemas seriam as classes, já os arquivos XML que transportam os dados, seriam uma instância desta classe, ou um objeto instanciado. |
Esta foi apenas uma pequena introdução ao protocolo SOAP, como não é objetivo deste artigo aprofundar neste tema, deixo aqui a fonte oficial de informações, para quem pretende entender de forma mais ampla, este vasto conteúdo:
https://www.w3.org/TR/soap/
2. Criando um serviço genérico para validar qualquer schema de diferentes tipos de XML
2.1. Ambiente necessário
Visual Studio Code (https://code.visualstudio.com/Download)
.Net 5.0 SDK (https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-5.0.400-windows-x64-installer)
Vamos criar uma API simples, que receberá uma string que representa um arquivo XML, validará o schema e caso econtre alguma inconsistência, retornará uma mensagem detalhando todos os problemas encontrados.
Para a criação da WEB API, após a instalação do .Net 5.0 SDK, temos disponível uma poderosa ferramenta de linha de comando que nos ajudará inclusive com a criação do projeto.
Abra um diretório qualquer do seu computador, clique na barra de endereços, digite o comando CMD
e tecle ENTER.
Ao fazer isto, o prompt de comando do Windows, abrirá o respectivo diretório automaticamente:
Digite o comando dotnet new webapi --name web.api.xml.schema.validation
e tecle ENTER.
Após alguns segundos, teremos um projeto do tipo WEB API, criado e totalmente funcional.
Para acessá-lo no Visual Studio Code, navegue para o diretório do projeto no prompt de comando, digite code .
e tecle e ENTER.
A tela do Visual Studio Code será aberta automaticamente, com o projeto previamente criado, já carregado:
Na primeira execução, o Visual Studio Code identificará a linguagem na qual o projeto está sendo desenvolvido e perguntará se deseja incluir a extensão para o C#, permita que ele faça a instalação, clicando no botão Install.
Para executar o projeto de teste, basta acessar o terminal do Visual Studio Code (CTRL + ‘) e digitar o comando: dotnet run
.
A partir deste momento, basta acessar o endereço: https://localhost:5001/swagger no browser para ver a API sendo executada.
É incrível como é fácil criar e executar uma API no .Net né!
A seguir, criaremos um serviço injetável (injeção de dependência) que será utilizando no controller responsável por receber e validar o arquivo xml.
Crie o diretório Services na raiz do projeto, com os seguintes subdiretórios: ServiceInterfaces e Services.
Crie o arquivo IXMLValidationService.cs no diretório \Services\XMLValidation\ServiceInterfaces, com o seguinte conteúdo:
namespace web.api.xml.schema.validation.Services.InterfacesServicos
{
public interface IXMLValidationService
{
string XMLValidate(string XML);
}
}
Agora vamos criar uma classe que implementará o método XMLValidate definido na interface IXMLValidationService:
Crie o arquivo XMLValidationService.cs no diretório \Services\XMLValidation\Services, com o seguinte conteúdo:
using web.api.xml.schema.validation.Services.InterfacesServicos;
namespace web.api.xml.schema.validation.Services.Servicos
{
public class XMLValidationService: IXMLValidationService
{
}
}
Neste momento, o Visual Studio Code exibirá um erro, pois não estamos ainda, implementando o XMLValidate definido na interface IXMLValidationService:
Para resolver este problema, posicione o mouse sobre o ícone da lâmpada amarela no início da linha 5 e clique na opção Implementar a interface:
A assinatura do método será criada automaticamente:
using web.api.xml.schema.validation.Services.InterfacesServicos;
namespace web.api.xml.schema.validation.Services.Servicos
{
public class XMLValidationService : IXMLValidationService
{
public string XMLValidate(string XML)
{
throw new System.NotImplementedException();
}
}
}
Agora vamos dar início ao processo de validação do schema de fato.
Para isto, crie o subdiretório Schemas dentro do diretório \Services\XMLValidation.
Crie no diretório \Schemas os seguintes arquivos com seus respectivos conteúdos:
Arquivo: TEndereco.xsd
Conteúdo:
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="TEndereco">
<xsd:sequence>
<xsd:element name="logradouro" type="xsd:string"/>
<xsd:element name="complemento" type="xsd:string"/>
<xsd:element name="bairro" type="xsd:string"/>
<xsd:element name="CEP">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="9"/>
<xsd:minLength value="9"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
Arquivo: TPessoa.xsd
Conteúdo:
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="TEndereco.xsd"/>
<xsd:element name="pessoa">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="nome">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value = "20"/>
<xsd:minLength value = "2"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="sobrenome">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value = "20"/>
<xsd:minLength value = "2"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="endereco" minOccurs="1" maxOccurs="1" type="TEndereco"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Estes arquivos são o schemas que validarão o conteúdo do arquivo xml recebido pela nossa WEB API.
O nosso método XMLValidate, contém um parâmetro do tipo string que será utilizada para criar um objeto do tipo System.Xml.XmlDocument.
Faça a seguinte modificação no método XMLValidate da classe XMLValidationService:
using System.Xml;
using web.api.xml.schema.validation.Services.InterfacesServicos;
namespace web.api.xml.schema.validation.Services.Servicos
{
public class XMLValidationService : IXMLValidationService
{
public string XMLValidate(string XML)
{
XmlDocument document = new XmlDocument();
try
{
document.Load(XML);
}
catch (System.Exception ex)
{
throw new System.Exception("Houve um erro ao gerar um documento XML com os dados recebidos. " + ex.Message);
}
return "";
}
}
}
Com isto, teremos um serviço que recebe uma string que representa um documento XML, tenta instanciar um XmlDocument e retorna uma exceção, caso o XML esteja com uma má formação.
Lembre-se, o que validaremos é um schema do XML e para isto, o conteúdo XML deverá no mínimo ser um XML bem formado.
Para criar o mecanismo de validação, utilizaremos o Validate(ValidationEventHandler validationEventHandler) do namespace System.Xml.XmlDocument.
Portanto, faça as seguintes modificações na classe XMLValidationService:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using web.api.xml.schema.validation.Services.InterfacesServicos;
namespace web.api.xml.schema.validation.Services.Servicos
{
public class XMLValidationService : IXMLValidationService
{
private static readonly ICollection<string> falhas = new List<String>();
public string XMLValidate(string XML)
{
XmlDocument document = new XmlDocument();
try
{
document.LoadXml(XML);
string falhas = ValidarXmlPessoa(document);
if (falhas.Count() > 0)
return falhas;
else
return "Arquivo validado com sucesso!";
}
catch (Exception ex)
{
throw new Exception("Houve um erro ao gerar um documento XML com os dados recebidos. " + ex.Message);
}
}
/// <summary>
/// Executa a validação de schema do XML do tipo "TPessoa", com base nos arquivos .xsd
/// </summary>
private string ValidarXmlPessoa(XmlDocument dados)
{
string retorno = "";
// Inclui os schemas XSD para validação do documento do tipo "TPessoa" e suas dependências
ICollection<string> XSDFiles = new List<String>();
try
{
XSDFiles.Add(@"Services\XMLValidation\Schemas\TPessoa.xsd");
XSDFiles.Add(@"Services\XMLValidation\Schemas\TEndereco.xsd");
}
catch (Exception ex)
{
throw ex;
}
// Aciona o método genérico de validações de schemas, mas que neste contexto, estará validando apenas os tipos "TEndereco" e "TPessoa"
List<string> validacao = ValidarDocumentoXML(dados, XSDFiles).ToList();
if (validacao.Count > 0)
{
retorno = "Ocorreram os seguintes erros na validação:\n";
foreach (var item in validacao)
{
retorno += item;
}
}
return retorno;
}
/// <summary>
/// Este é um método genérico, que serve para validar qualquer o schema de qualquer tipo de arquivo xml
/// </summary>
private static ICollection<string> ValidarDocumentoXML(XmlDocument doc, ICollection<string> XSDFiles)
{
// Limpa a lista de falhas de schema
falhas.Clear();
try
{
// Adiciona todos os arquivos .xsd ao fluxo de validação
foreach (var item in XSDFiles)
{
doc.Schemas.Add(null, item);
}
}
catch (System.Exception ex)
{
throw new Exception("Houve um erro ao incluir os arquivos XSD para validar o arquivo XML.\n" + ex.Message);
}
try
{
// Delegate responsável por manipular os erros ocorridos: ValidationCallBack()
doc.Validate(ValidationCallBack);
}
catch (XmlSchemaValidationException ex)
{
throw new Exception("Houve um erro executar a validação do documento XML. " + ex.Message);
}
return falhas;
}
/// <summary>
/// Manipulador de erros do xml
/// Sua finalidade é obter as mensagens de erro (disparadas pelo método "ValidarDocumentoXML") e as incluir a variável "falhas"
/// </summary>
private static void ValidationCallBack(object sender, ValidationEventArgs args)
{
// Podem ser gerados dois tipos de falhas ("XmlSeverityType"), portanto, a estrutura abaixo, separa os erros entre "Alerta (Warning)" ou "Erros (Error)"
if (args.Severity == XmlSeverityType.Warning)
{
falhas.Add("Alerta: " + TraduzMensagensDeErro(args.Message) + " (Caminho: " + ObtemCaminho(args) + ")");
}
else if (args.Severity == XmlSeverityType.Error)
{
falhas.Add("Erro: " + TraduzMensagensDeErro(args.Message) + " (Caminho: " + ObtemCaminho(args) + ")");
}
}
/// <summary>
/// Durante a validação do schema de um arquivo xml, este método auxilia na obtenção do caminho completo da tag que causou algum problema de validação
/// </summary>
private static string ObtemCaminho(ValidationEventArgs args)
{
// Captura a referência para a tag que causou o problema (falha de schema)
XmlSchemaValidationException ex = (XmlSchemaValidationException)args.Exception;
object sourceObject = ex.SourceObject;
if (sourceObject.GetType() == typeof(XmlElement))
{
XmlElement tagProblema = (XmlElement)(sourceObject);
return GetCaminhoTagXML(tagProblema.ParentNode) + "/" + tagProblema.Name;
}
else
{
return "";
}
}
/// <summary>
/// Devolve o caminho completo de um elemento de um documento XML, no padrão: "\elemento_raiz\elemento2\elemento3\..."
/// </summary>
private static string GetCaminhoTagXML(XmlNode args)
{
var node = args.ParentNode;
if (args.ParentNode == null)
{
return "";
}
else if (args.ParentNode.NodeType == XmlNodeType.Element)
{
// Elemento atual é um nó com mais itens
// Chama o próprio método recursivamente, para obter toda a árvore da tag atual
return GetCaminhoTagXML(node) + @"/" + args.Name;
}
return "";
}
/// <summary>
/// Altera o texto das mensagens de validação do schema, de inglês para português
/// </summary>
private static string TraduzMensagensDeErro(string mensagem)
{
mensagem = mensagem.Replace("The value of the 'Algorithm' attribute does not equal its fixed value.", "O valor do atributo 'Algorithm' não é igual ao seu valor fixo.");
mensagem = mensagem.Replace("The '", "O elemento '");
mensagem = mensagem.Replace("element is invalid", "é inválido");
mensagem = mensagem.Replace("The value", "O valor");
mensagem = mensagem.Replace("is invalid according to its datatype", "é inválido de acordo com o seu tipo de dados");
mensagem = mensagem.Replace("The Pattern constraint failed.", "");
mensagem = mensagem.Replace("The actual length is less than the MinLength value", "O comprimento real é menor que o valor MinLength");
mensagem = mensagem.Replace(" in namespace 'http://www.w3.org/2000/09/xmldsig#'.", "");
mensagem = mensagem.Replace("The element", "O elemento");
mensagem = mensagem.Replace("has invalid child element", "tem um elemento filho inválido");
mensagem = mensagem.Replace("List of possible elements expected:", "Lista de possíveis elementos esperados:");
mensagem = mensagem.Replace("The Enumeration constraint failed.", "");
mensagem = mensagem.Replace("http://www.w3.org/2000/09/xmldsig#:", "");
mensagem = mensagem.Replace("http://www.w3.org/2001/XMLSchema:", "");
mensagem = mensagem.Replace("The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.", "A entrada não é uma string Base-64 válida, pois contém um caractere não base 64, mais de dois caracteres de preenchimento ou um caractere ilegal entre os caracteres de preenchimento.");
mensagem = mensagem.Replace("The required attribute", "O atributo obrigatório");
mensagem = mensagem.Replace("is missing", "está ausente");
mensagem = mensagem.Replace("has incomplete content", "tem conteúdo incompleto");
mensagem = mensagem.Replace("as well as", "bem como");
return mensagem;
}
}
}
Como a classe ficou um pouco extensa, utilizei comentários em pontos específicos para explicar cada detalhe que está sendo executado.
Após estas modificações, temos o serviço para validação de XML completamente funcional, para utilizarmos, basta injetá-lo no controller, faremos isto através de uma configuração no método ConfigureServices da classe Startup.
Mas primeiro, vamos incluir uma referência em nosso arquivo web.api.xml.schema.validation.csproj para o pacote Microsoft.AspNetCore.Mvc.NewtonsoftJson, que nos auxiliará a trabalhar com arquivos XML nos controllers da API.
Edite o conteúdo do arquivo web.api.xml.schema.validation.csproj incluindo a linha:
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
Deixando-o da seguinte forma:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3"/>
</ItemGroup>
</Project>
Após adicionar a referência para a biblioteca de serialização NewtonSoft edite o conteúdo do arquivo Startup.cs, deixando-o da seguinte forma:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using web.api.xml.schema.validation.Services.InterfacesServicos;
using web.api.xml.schema.validation.Services.Servicos;
namespace web.api.xml.schema.validation
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Configurações da injeção de dependência do serviço validador de documentos XML
services.AddScoped(typeof(IXMLValidationService), typeof(XMLValidationService));
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "web.api.xml.schema.validation", Version = "v1" });
});
//Inclusão do framework de serialização NewtonSoft
services.AddControllers().AddNewtonsoftJson();
//Ajuste para permitir receber conteudos do tipo XML nos controllers da API
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddNewtonsoftJson
(options => { }).AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "web.api.xml.schema.validation v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
3. Criando o endpoint para receber os arquivos XML
A próxima e última etapa, será a criação do controller que receberá a string do XML e irá submetê-la à validação, por meio do serviço XMLValidationService.
Crie no diretório \Controllers o arquivo XMLValidationController.cs com o seguinte conteúdo:
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc;
using web.api.xml.schema.validation.Services.InterfacesServicos;
[Description("Validação de XML")]
public class XMLValidationController : Controller
{
//Interface do serviço que valida o arquivo XML e será injetado automaticamente em tempo de execução
private readonly IXMLValidationService _XMLValidationService;
public XMLValidationController(IXMLValidationService XMLValidationService)
{
_XMLValidationService = XMLValidationService;
}
[HttpPost("api/validarxml/")]
public string Validar([FromBody] string strDocumento)
{
return _XMLValidationService.XMLValidate(strDocumento);
}
}
Todo o código da aplicação está pronto. Agora, ao executar nossa WEB API e navegar para o endereço: https://localhost:5001/swagger, teremos acesso ao novo endpoint XMLValidation conforme mostra a imagem a seguir:
Ao clicar no botão Try it out podemos testar o nosso mecanismo de validação, usando o seguinte payload:
"<?xml version='1.0' encoding='UTF-8'?>
<pessoa>
<nome>Silvair</nome>
<sobrenome>Leite Soares</sobrenome>
<endereco>
<logradouro>Rua Goia</logradouro>
<complemento>Quadra e lote qualquer</complemento>
<bairro>Setor Gentil Meirelles</bairro>
<CEP>74575-200</CEP>
</endereco>
</pessoa>"
O schema será validado com sucesso, veja na imagem a seguir:
Mas se você fizer qualquer modificação no payload, de forma que o contrato (arquivos .xsd) seja quebrado, a API retornará todos os detalhes dos problemas ocorridos. Veja a seguir:
"<?xml version='1.0' encoding='UTF-8'?>
<pessoa>
<nome>Silvair</nome>
<sobrenome/>
<endereco>
<logradouro>Rua Goia</logradouro>
<complemento>Quadra e lote qualquer</complemento>
<bairro>Setor Gentil Meirelles</bairro>
<ElementoNaoPrevisto>Texto qualquer</ElementoNaoPrevisto>
<CEP>74575-200</CEP>
</endereco>
</pessoa>"
Omiti intencionalmente o conteúdo da tag sobrenome e inclui um elemento não previsto na tag endereco, veja o resultado:
Com este passo, concluímos o nosso projeto de teste. Lembrando que, por mais que estejamos validando os tipos "pessoa" e "endereco" durante este artigo, o serviço que construímos servirá para validar qualquer tipo de schema XML. Bastando para isso, incluir os respectivos arquivos .xsd no diretório \Schemas e fazer algumas pequenas modificações, como por exemplo, criar um endpoint específico para cada tipo de arquivo XML a ser validado.
Todo o código construído durante o projeto está disponível no seguinte repositório do GitHub: https://github.com/silvairsoares/web.api.xml.schema.validation
Referências:
MACORATTI, José Carlos. XML - Validando um Documento XML com um Schema (C#). Disponível em: http://www.macoratti.net/11/10/c_vxml1.htm. Acesso em: 13 de ago. de 2021.
Top comments (0)