Quando hospedamos serviços WCF no IIS, podemos permitir ao serviço utilizar os recursos fornecidos pelo ASP.NET. Entre esses recursos, temos as variáveis de aplicação, de sessão, cookies, caching, etc. Para permitir isso, tudo o que precisamos fazer é habilitar através de um atributo na classe que representa o serviço, chamado de AspNetCompatibilityRequirementsAttribute. Ele fornece uma propriedade chamada RequirementsMode, que aceita uma das opções impostas pelo enumerador AspNetCompatibilityRequirementsMode. O código abaixo ilustra a sua configuração:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Servico01 : IServico01
{
public string Ping(string value)
{
if (HttpContext.Current.Session[“Teste”] == null)
HttpContext.Current.Session[“Teste”] = value;
return HttpContext.Current.Session[“Teste”] as string;
}
}
Note que na implementação estamos fazendo uso de variáveis de sessão. Além disso, precisamos ligar explicitamente essa compatibilidade com o ASP.NET no arquivo Web.config, que é utilizado pela aplicação que hospeda o serviço, e para isso, definimos o atributo aspNetCompatibilityEnabled do elemento serviceHostingEnvironment para True, conforme é mostrado abaixo:
<?xml version=”1.0″?>
<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
</system.serviceModel>
</configuration>
E com essas configurações podemos de dentro de serviços WCF, utilizar as funcionalidades expostas pelo ASP.NET. É importante dizer que variáveis de sessão são controladas através de cookies, e mesmo em serviços WCF, eles continuam sendo utilizados para relacionar um determinado cliente e as suas respectivas variáveis de sessão que estão armazenadas no servidor.
Quando estamos publicando o nosso serviço através de um binding HTTP (BasicHttpBinding ou WSHttpBinding), eles possuem uma propriedade boleana chamada AllowCookies. O nome desta propriedade pode parecer confuso, pois a finalidade dela não é permitir que o binding utilize ou não cookies, mas sim determinar quem deverá manipulá-los.
Quando esta propriedade estiver definida como True (o padrão é False), o próprio runtime do WCF irá gerenciar os cookies. Isso quer dizer que ele criará uma instância da classe CookieContainer e a associará ao serviço, utilizando-a para todas as requisições subsequentes, ou seja, todo e qualquer cookie retornardo pelo serviço, será automaticamente reenviado nas futuras requisições para este mesmo serviço, sem a necessidade de controlar como isso é realizado.
O problema aparece quando temos mais do que um serviço dentro da aplicação (WebSite). Serviços diferentes, exigem proxies diferentes no cliente, e os cookies estão vinculado à um proxy específico, ou seja, se você utiliza dois proxies, sendo um para cada serviço (*.svc), os cookies não serão compartilhados, e com isso, os cookies e, consequentemente, variáveis de sessão, não serão compartilhados entre eles (serviços), gerando assim um resultado inesperado. A imagem abaixo ilustra a estrutura do lado do servidor, e como sabemos, existirá um proxy para cada serviço.
A implementação do Servico02 para o teste é extremamente simples, ou seja, ele apenas lê a informação de uma variável de sessão que foi criada pelo Servico01. Apesar deste serviço também estar configurado para exigir o modo de compatibilidade, isso não resolverá, já que o problema reside do lado do cliente, pois como ele não consegue manter o cookie entre os proxies, quando tentarmos executar este segundo serviço, o WCF e o ASP.NET entendem que não foi criada a sessão e, consequentemente, retornará nulo.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Servico02 : IServico02
{
public string Ping(string value)
{
return HttpContext.Current.Session[“Teste”] as string;
}
}
Para começar a resolver o problema, o primeiro passo é desligar o controle automático de cookies pelo WCF, definido a propriedade AllowCookies para False (que já é o padrão), assim como é mostrado abaixo. É importante dizer que essa propriedade deve estar devidamente configurada no cliente, que com isso, será responsável por gerenciar manualmente os cookies gerados pelo serviço.
<bindings>
<basicHttpBinding>
<binding allowCookies=”false” />
</basicHttpBinding>
</bindings>
Depois dessa configuração, o resto será através do próprio código. Se notarmos no exemplo abaixo, podemos perceber que temos os dois proxies, sendo um para cada serviço. Temos também uma string que receberá o valor do cookie gerado, que corresponderá ao Id da sessão. Logo depois da criação do proxy para o primeiro serviço, instanciamos a classe OperationContextScope, que cria um bloco de acesso ao contexto da operação, tendo acesso a recursos como headers e properties do serviço. Já dentro deste bloco, utilizamos a classe HttpResponseMessageProperty para extrairmos os valores pertinentes a resposta da execução do método Ping. Note que ela está sendo acessada depois da chamada ao método Ping. Com a coleção de headers em mãos, utilizamos o header SetCookie para extrair o valor do cookie de sessão do ASP.NET (ASP.NET_SessionId) e guardamos na string sessionIdCookie.
Se ainda invocarmos o método Ping a partir do mesmo proxy, não teremos problema, ou seja, ele conseguirá manter a variável de sessão. Mas aqui a situação é outra, ou seja, temos um segundo proxy para o consumo de um outro serviço, mas que queremos utilizar a mesma variável de sessão, gerada anteriormente. Para o segundo proxy, nós fazemos o processo inverso, ou seja, criamos o escopo de acesso aos recursos da requisição, e construímos a instância da classe HttpRequestMessageProperty e configuramos o cookie com aquele valor que salvamos depois de invocar o método através do primeiro proxy.
static void InvocarServico()
{
string sessionIdCookie = null;
using (Servico01Client proxy1 = new Servico01Client())
{
using (new OperationContextScope(proxy1.InnerChannel))
{
Console.WriteLine(proxy1.Ping(“teste”));
HttpResponseMessageProperty response =
OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
sessionIdCookie = response.Headers[HttpResponseHeader.SetCookie];
}
}
using (Servico02Client proxy2 = new Servico02Client())
{
using (new OperationContextScope(proxy2.InnerChannel))
{
if (!string.IsNullOrWhiteSpace(sessionIdCookie))
{
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers[HttpRequestHeader.Cookie] = sessionIdCookie;
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request;
}
Console.WriteLine(proxy2.Ping(“outro valor”));
}
}
Com isso tudo criado, agora o método Ping do proxy2 será capaz de retornar o valor gerado e armazenado na variável de sessão através do proxy1. Caso você não utilize essa técnica, a requisição para os métodos do proxy2 serão enviadas sem o cookie, e com isso, o WCF e o ASP.NET não saberá que existe variáveis de sessão já criadas para aquele usuário, e com isso, não terá acesso as informações previamente criadas.