Há algum tempo eu mostrei aqui como podemos proceder para detectar a desconexão do cliente de um serviço WCF. Aquela solução tem a finalidade de detectar a desconexão do cliente ao tentar enviar um callback para ele, onde podemos analisar o estado do canal de comunicação com o mesmo, e se estiver em estado falho ou se já foi fechado, podemos desprezar a notificação.
O problema daquela técnica é que o serviço somente saberá que o cliente não está mais ativo, quando tentarmos acessar o canal para se comunicar com ele. Se o cliente já encerrou a aplicação, de forma normal ou não (matando o processo), a referência dele ainda existirá dentro do nosso serviço.
Para sermos notificados de que o cliente foi encerrado, novamente, de forma normal ou não, podemos recorrer à implementação da interface IInputSessionShutdown. Para bindings que suportam sessões, essa interface pode ser aplicada ao runtime, e sermos notificados quando o cliente encerrar a conexão com o serviço. Essa interface fornece dois métodos: DoneReceiving e ChannelFaulted. O primeiro método é disparado quando o cliente não puder mais receber as notificações, pois ele encerrou o canal de comunicação com o serviço; já o método ChannelFaulted é disparado quando o canal de comunicação do cliente entra em estado falho, ou seja, algum problema ocorreu na aplicação consumidora do serviço, que não foi capaz de encerrar o proxy de forma explicíta. Abaixo temos uma implementação simples deste recurso:
public class SessionShutdownTrigger : IInputSessionShutdown
{
public void ChannelFaulted(IDuplexContextChannel channel)
{
Console.WriteLine(“ChannelFaulted”);
}
public void DoneReceiving(IDuplexContextChannel channel)
{
Console.WriteLine(“DoneReceiving”);
}
}
Como podemos notar, ambos métodos recebem como parâmetro um objeto que implementa a interface IDuplexContextChannel, que corresponde ao canal de comunicação do cliente. Com esta classe criada, precisamos acoaplá-la ao runtime do WCF, e para isso recorremos aos pontos de estensibilidade que o WCF fornece, mais precisamente, ao uso da interface IContractBehavior, que nos permite modificar, examinar ou estender aspectos pertinentes à um contrato. Ao implementar essa interface, entre os vários métodos que ela fornece, temos o método ApplyDispatchBehavior, que nos permite interceptar e aplicar algum recurso customizado ao dispatcher do serviço. Justamente por isso, como parâmetro recebemos uma instância da classe DispatchRuntime, que por sua vez, fornece uma propriedade chamada InputSessionShutdownHandlers, que nos permite adicionar instâncias de classes que implementam a interface IInputSessionShutdown. O código abaixo ilustra essa classe:
public class SessionShutdownTriggerBehaviorAttribute : Attribute, IContractBehavior
{
public void ApplyDispatchBehavior(ContractDescription contractDescription,
ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InputSessionShutdownHandlers.Add(new SessionShutdownTrigger());
}
//Outros Métodos
}
Essa classe por si só não funciona. Note que ela herda da classe Attribute, que me permite decorar a classe que representa o serviço, e com isso, ao rodá-lo, o WCF será capaz de perceber a presença deste atributo, e executar o método ApplyDispatchBehavior, incluindo a instância da classe SessionShutdownTrigger que criamos acima e, finalmente, quando a cliente encerrar o canal de comunicação, seremos notificados que isso ocorreu, e podemos executar algum código pertinentes aquele cliente, sem a necessidade de somente detectarmos isso quando precisarmos efetivamente comunicar com ele. Abaixo temos o contrato do serviço com esse atributo decorado:
[SessionShutdownTriggerBehavior]
public class Servico : IContrato
{
public string Ping(string value)
{
return value;
}
}