Os ASP.NET Web Services (ASMX) são baseados na especificação WS-I Basic Profile, gerando as mensagens na versão 1.1 do SOAP. Eles foram que foram introduzidos desde a primeira versão do .NET Framework, e mais recentemente foram substituídos pelo WCF.
Para manter a interoperabilidade, a Microsoft criou um binding chamado BasicHttpBinding, que possui as mesmas características dos serviços ASMX. Com isso, podemos substituir tanto o serviço quanto o cliente utilizando a API do WCF. Ao optar pela substituição do lado do serviço, precisamos nos preocupar com a SOAPAction.
A SOAPAction é um header que é incluído na requisição HTTP, e determina intenção da requisição SOAP. Esse header é representado por uma URI que não aponta necessariamente para um local válido na internet. O problema reside justamente aqui. Imagine que temos um serviço (ASMX), que disponibiliza apenas uma única operação:
[WebService(Namespace = “http://tempuri.org/”)]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Servico : System.Web.Services.WebService
{
[WebMethod]
public string Ping()
{
return “ping!”;
}
}
Os clientes que o consomem devem enviar as requisições da seguinte forma:
POST /Servico.asmx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.4952)
Content-Type: text/xml; charset=utf-8
SOAPAction: “http://tempuri.org/Ping”
Host: 127.0.0.1:51982
Content-Length: 288
Expect: 100-continue
Connection: Keep-Alive
<?xml version=”1.0″ encoding=”utf-8″?>
<soap:Envelope …>
<soap:Body>
<Ping xmlns=”http://tempuri.org/” />
</soap:Body>
</soap:Envelope>
Agora queremos substituir o serviço para o WCF. Optamos então por criar o contrato e, consequentemente, o implementamos em uma classe que representará o serviço. A interface do contrato está definida abaixo:
[ServiceContract]
public interface IContrato
{
[OperationContract]
string Ping();
}
O ponto mais importante é expor o serviço através do binding BasicHttpBinding, que não será abordado aqui. Se analisarmos o WSDL gerado pelo serviço, veremos que a SOAPAction gerada é: http://tempuri.org/IContrato/Ping. Como podemos perceber, ela leva o nome do contrato. Com isso, os clientes existentes não conseguirão mais enviar mais mensagens para o serviço, já que o WCF utiliza a SOAPAction para identificar a operação a ser disparada, e como ela é diferente, não a encontrará. Abaixo temos a descrição do erro que ocorre:
Unhandled Exception: System.Web.Services.Protocols.SoapHeaderException: The message with Action ‘http://tempuri.org/Ping’ cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
Para resolvermos este problema, temos que recorrer a propriedade Action que é definida no atributo OperationContractAttribute, e lá colocarmos a mesma SOAPAction que os clientes estão enviando, mantendo assim a compatibilidade e não sendo necessária qualquer mudança por parte daqueles que o consomem.
[ServiceContract]
public interface IContrato
{
[OperationContract(Action = “http://tempuri.org/Ping”)]
string Ping();
}
Tomando este cuidado, podemos tranquilamente substituir serviços construídos em ASMX para WCF, sem afetar qualquer cliente que já consuma o mesmo. É importante dizer também que é possível consumir um serviço escrito em ASMX através da API do WCF do lado cliente, mas isso está fora do escopo deste artigo. E para finalizar, sempre se atente aos namespaces.
Muito bom esse artigo, pois se houver a necessidade de mudança do ASMX para o WCF não vai ser tão complicado assim.