Detectando a desconexão - Parte 2

by Israel Aece 29. July 2010 09:16

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;
    }
}

Tags:

WCF

Tratando erros com jQuery e WCF

by Israel Aece 18. May 2010 22:03

Neste artigo eu mostrei como construir serviços WCF para serem consumidos através do jQuery. Nele falamos sobre os cuidados que devemos ter para criar e expor serviços WCF, mas não chegamos a falar sobre alguns detalhes, não menos importante, como é o caso do tratamento de erros, que é algo tão comum em qualquer tipo de aplicação.

Ao executar alguma operação, uma exceção pode ser disparada por algum motivo. Como já falamos, ao utilizar a biblioteca do jQuery para invocar algum serviço, podemos através de um callback, especificarmos um código para ser disparado quando algum problema ocorrer do lado do serviço. Para configurar isso, podemos utilizar o parâmetro error da função $.ajax. O código abaixo ilustra como podemos configurar o parâmetro error, exibindo uma mensagem informando ao usuário que algum problema ocorreu:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "", "email": "ia@israelaece.com" }', //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                alert('Algum Problema Ocorreu!');
            }
        });
    }

Esse tipo de código funciona normalmente, até que você precise capturar a mensagem exata do erro que ocorreu. Quando executamos uma operação que está disparando alguma exceção, o WCF acaba retornando a seguinte mensagem: "Request server encountered an error processing the request. See server logs for more details". O problema é que o erro não será retornado em um formato que é facilmente entendido/preferido pelo jQuery, que é o JSON.

Ao configurar o contrato para expor via AJAX, podemos definir através do atributo WebInvokeAttribute, que a requisição (RequestFormat) e a resposta (ResponseFormat) sejam formatadas em JSON, mas isso não inclui eventuais exceções que sejam disparadas pelas respectivas operações, mesmo que esse problema esteja definido como um contrato de fault (FaultContractAttribute).

Para tentar resolver isso, podemos recorrer a alguns pontos de estensibilidade do WCF, para conseguirmos interceptar o erro que ocorreu. Ao interceptar, deveremos transformar a mensagem em algum objeto que deverá ser entendido pelo jQuery, entregando para o cliente todas as informações necessárias para que o mesmo consiga tratar o erro ocorrido.

Para descrever o problema, vamos criar uma classe que possui características que detalham o erro ocorrido. Essa classe será chamada de GenericErrorDescription, que possuirá apenas uma única propriedade (para manter a simplicidade), chamada de Message, do tipo string. Como a finalidade será transformar a exceção em formato JSON para facilitar o consumo pelo jQuery, criaremos uma classe chamada de JsonErrorHandler, e nela implementaremos a interface IErrorHandler, que é uma espécie de interceptador (mais detalhes neste artigo). O principal método fornecido por esta interface, é chamado de ProvideFault, que passa como parâmetro a instância da exceção que ocorreu e uma instância da classe Message, que corresponde a mensagem que será devolvida para o cliente.

Na implementação deste método, utilizaremos o método estático CreateMessage da classe Message, que criará a mensagem de retorno, utilizando o serializador JSON para que a mesma será formatada neste padrão, e além disso, esse serializador deverá serializar a instância da classe que representa o erro, que no nosso exemplo é a GenericErrorDescription. Na sequência configuramos a instância da classe HttpResponseMessageProperty, para customizarmos a mensagem, informando que o tipo a ser retornado será application/json, para que o jQuery consiga, facilmente, entender e interpretar esse conteúdo. Note também que mantemos o StatusCode do HTTP como 500, ou seja, um erro interno, e como sua descrição, definimos a mesma mensagem gerada pela exceção disparada pela operação.

A classe JsonErrorHandler ainda implementa a interface IEndpointBehavior, que nos permite acoplar a instância desta classe em um endpoint específico, do lado do serviço (dispatcher), na coleção de tratadores de erros. O código abaixo ilustra a classe JsonErrorHandler já devidamente implementada:

public class JsonErrorHandler : IEndpointBehavior, IErrorHandler
{
    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        GenericErrorDescription desc = 
            new GenericErrorDescription() { Message = error.Message };

        fault = Message.CreateMessage(version, "", desc, 
            new DataContractJsonSerializer(typeof(GenericErrorDescription)));
        fault.Properties.Add(WebBodyFormatMessageProperty.Name, 
            new WebBodyFormatMessageProperty(WebContentFormat.Json));

        var msgp = new HttpResponseMessageProperty();
        msgp.Headers[HttpResponseHeader.ContentType] = "application/json";
        msgp.StatusCode = HttpStatusCode.InternalServerError;
        msgp.StatusDescription = desc.Message;

        fault.Properties.Add(HttpResponseMessageProperty.Name, msgp);
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bp) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr) { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
    {
        ed.ChannelDispatcher.ErrorHandlers.Add(this);
    }

    public void Validate(ServiceEndpoint endpoint) { }
}

Só que essa classe por si só não funciona. Precisamos efetivamente acoplar ao pipeline do WCF, e para isso, podemos criar um extension element, que nos dará a oportunidade de efetuar essa configuração de modo declarativo, ou seja, através do arquivo Web.config. Basicamente, essa classe será responsável por criar instâncias da classe que criamos acima, ou seja, do tratador de erros:

public class JsonErrorElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(JsonErrorHandler);
        }
    }

    public override object CreateBehavior()
    {
        return new JsonErrorHandler();
    }
}

Depois das classes devidamente criadas, tudo o que precisamos fazer agora é configurarmos o arquivo Web.config do serviço, acoplando o extension element ao endpoint de acesso ao serviço. A configuração final do Web.config deve ficar da seguinte forma:

<system.serviceModel>
  <services>
    <service name="ServicoDeUsuarios" behaviorConfiguration="config">
      <endpoint
        binding="webHttpBinding"
        contract="IUsuarios"
        behaviorConfiguration="edpConfig" />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="config">
        <serviceDebug includeExceptionDetailInFaults="false" />
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="edpConfig">
        <jsonErrorHandler />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add
        name="jsonErrorHandler"
        type="JsonErrorElement, WCFExtensions, Version=1.0.0.0, ..." />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

Já do lado do cliente, as mudanças são bastante ligeiras. Tudo o que precisamos fazer é uma pequena mudança no código que é disparado quando o erro ocorre. Uma vez que o interceptador de erros está acoplado do WCF, a mensagem com o erro de retorno será formatada em JSON, e se analisarmos o retorno, veremos o seguinte resultado:

{"Message": "Informe o nome do usuario\u000d\u000aParameter name: nome"}

Como a mensagem de retorno está em formato JSON, utilizaremos o método parse da classe JSON, exposta pelo JSON2. Esse método é responsável por transformar o conteúdo JSON em um objeto, respeitando as mesmas propriedades serializadas pela classe GenericErrorDescription. Com isso, o nosso código de consumo ao serviço será alterado para:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "", "email": "ia@israelaece.com" }', //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                var erro = JSON.parse(xhr.responseText);
                alert(erro.Message);
            }
        });
    }

Se quiser levar mais detalhes do erro, você ainda tem algumas outras alternativas, mas não tão interessantes como essa. Você poderia optar por definir o atributo includeExceptionDetailInFaults do elemento serviceDebug para True, mas isso iria expor mais informações do que realmente deveria, como por exemplo a Stack Trace, algo que não deveria ultrapassar o serviço. Além dela, ainda poderia envolver toda a operação em um bloco try/catch, e caso algum problema ocorra, utilizamos a classe WebOperationContext para customizar o StatusCode e o ContentType através dela, mas isso torna a classe que representa o serviço dependente do protocolo HTTP, o que não é uma boa opção.

Conclusão: Graças a grande flexibilidade fornecida pelo WCF, podemos contornar alguma de suas "limitações" de uma forma bastante elegante, sem precisar misturar em minhas operações, códigos que estão relacionados puramente à infraestrutura.

Tags: , , , ,

WCF

Utilizando jQuery para invocar Actions

by Israel Aece 5. May 2010 16:51

Com jQuery podemos acessar serviços WCF para tornar a experiência do usuário muito melhor, evitando a atualização completa da página. Neste artigo vimos como proceder para preparar um serviço WCF para ser exposto e ser consumido via AJAX, e além disso, exploramos a API do jQuery para efetuar a comunicação com esse serviço.

Só que muitas vezes, para ter a riqueza necessário do lado do cliente, tudo o que precisamos fazer é consumir métodos que estão no código C#/VB.NET, dentro da própria aplicação. Se a aplicação está baseada no ASP.NET MVC, talvez tenhamos a necessidade de invocar uma ação (action) de um determinado controller. Felizmente o jQuery não está limitado à executar apenas métodos expostos por serviços WCF, mas também podemos acessar os métodos criados nos controllers, utilizando a mesma API. A finalidade deste artigo é mostrar como proceder para atingir esse objetivo.

Como todos sabemos, todas as actions de um controller sempre devem retornar a instância de uma das classes derivadas de ActionResult, e como o próprio nome diz, é responsável por receber, armazenar e devolver o resultado da respectiva ação. Uma das classes derivadas dela é a JsonResult, que representa o resultado em formato JSON, que é o formato mais conveniente para o jQuery trabalhar.

Sendo assim, para aquelas actions que desejamos consumir através do jQuery, precisamos retornar uma instância da classe JsonResult, abastecendo a propriedade Data com algum objeto que desejamos visualizar do lado do cliente. Essa propriedade recebe um System.Object, o que nos permite definirmos qualquer objeto, incluindo tipos anônimos. A partir da versão 2.0 do ASP.NET MVC, a Microsoft adicionou nesta classe uma nova propriedade chamada de JsonRequestBehavior, que recebe uma das duas opções fornecidas pelo enumerador com o mesmo nome. Basicamente, a ideia desta propriedade é controlar se o acesso via GET é permitido ou não. Por questões de segurança, essa propriedade é definida com a opção DenyGet, o que nega a possibilidade de conseguir invocar através de GET. Você pode trocar para a opção AllowGet, mas é importante que você conheça os eventuais problemas que podem acontecer se isso estiver habilitado. Com esse conhecimento da classe JsonResult, podemos ter a seguinte action:

public class UsuariosController : Controller
{
    public ActionResult RecuperarUsuario()
    {
        return new JsonResult()
        {
            Data = new Usuario() { Codigo = 123, Nome = "Israel" }
        };
    }
}

Depois do controller e da action criados, vamos recorrer ao jQuery para consumir este método. O jQuery fornece alguns atalhos, que evita a configuração total da função $.ajax. Um desses atalhos é a função $.getJSON, que dado uma URL e um callback, podemos referenciar o método criado anteriormente, e com a função de callback processar o resultado. O problema é que a função $.getJSON efetua a solicitação via GET, e como vimos acima, não estamos permitindo isso (JsonRequestBehavior). Sendo assim, vamos recorrer a função de baixo nível $.ajax, configurando apenas o que é necessário para executar o método:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "/Usuarios/RecuperarUsuario",
        contentType: "application/json",
        success:
            function (resultado) {
                alert(resultado.Codigo);
                alert(resultado.Nome);
            },
    }

Como percebemos no código acima, ele está invocando o método RecuperarUsuario no controller Usuarios, através de POST, e quando o resultado é devolvido, exibimos a mensagem na tela. Note que há uma barra (/) antes do nome do controller. Isso é necessário porque a view corrente, que possui o código jQuery, não faz parte deste controller, o que nos obriga a mencionar a partir no nível raiz. Se quisermos acessar uma action que está no controller da view, então tudo o que é necessário é informar o nome da mesma, sem mencionar o controller.

Já quando a action exigir parâmetros, é necessário informá-los durante a chamada. Se modificarmos a action para ela passar a receber dois parâmetros, sendo o código e o nome do usuário, precisamos informar isso na chamada para o método, ficando da seguinte forma:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        url: "/Usuarios/RecuperarUsuario",
        data: { "codigo": 123, "nome": "Israel Aece" },
        processData: true,
        success:
            function (resultado) {
                alert(resultado.Codigo);
                alert(resultado.Nome);
            },
    }

Como a opção processData está definida como True, ele converterá o JSON que informamos no parâmetro data em uma espécie de coleção de chave/valor, e como estamos efetuando a requisição através de POST, as parâmetros serão colocados no corpo da mensagem, separando cada par de chave com o caracter &. Note que na chamada acima, não definimos a propriedade contentType para application/json, já que o conteúdo a ser enviado trata-se de parâmetros em suas formas tradicionais.

O problema maior é quando a action recebe um parâmetro complexo, como por exemplo, a instância da classe Usuario. Neste caso, temos que recorrer à biblioteca JSON2, que fornecerá recursos para serializar a instância de um objeto em formato JSON, para que assim possa trafegar até a action correspondente. Com isso, o código para a chamada à ela será da seguinte forma:

function Adicionar() {
    var usuario = { "Codigo": 123, "Nome": "Israel" };

    $.ajax(
    {
        type: "POST",
        url: "/User/Adicionar",
        contentType: "application/json",
        data: JSON.stringify(usuario),
        processData: false,
        success:
            function (resultado) {
                alert(resultado);
            },
    });
}

Do lado do servidor, teremos um método chamado Adicionar, que recebe a instância da classe Usuario. O código abaixo ilustra como devemos configurar a action com este parâmetro, e note que a propriedade Data retorna uma mensagem, informando que o usuário foi cadastrado com sucesso.

public class UsuariosController : Controller
{
    public ActionResult Adicionar(Usuario usuario)
    {
        return new JsonResult()
        {
            Data = "Usuario cadastrado com sucesso."
        };
    }
}

Só que do lado da action também será necessário efetuar um código adicional. Como estamos mandando o conteúdo em formato JSON, o ASP.NET MVC não conseguirá extrair o objeto diretamente dele, o que nos obriga a criar um binder exclusivo para isso, onde podemos utilizar algum serializador JSON que faça esse trabalho.

Para atingir esse objetivo, podemos utilizar um ponto de estensibilidade do ASP.NET MVC, que nos permite criar um atributo herdando da classe CustomModelBinderAttribute, onde podemos definir como vamos efetuar a "tradução" do(s) parâmetro(s) que estão no corpo da requisição, em um objeto conhecido pela aplicação, e que no nosso exemplo será a classe Usuario. Esta classe possui um método chamado GetBinder, que retorna a instância de uma classe que implementa a interface IModelBinder, qual utilizaremos o serializador DataContractJsonSerializer, que é o mesmo serializador utilizado pelo WCF. O código abaixo ilustra como ele deve ficar:

public class JsonBinderAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new JsonModelBinder();
    }

    public class JsonModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext cc, ModelBindingContext bc)
        {
            return new DataContractJsonSerializer(bc.ModelType)
                .ReadObject(cc.HttpContext.Request.InputStream);
        }
    }
}

Agora, tudo o que precisamos fazer é decorar o parâmetro da action com o atributo que criamos acima. Isso dirá ao runtime do ASP.NET MVC que deverá utilizar este binder para preencher o conteúdo do parâmetro usuario. Abaixo temos a mesma action Adicionar, mas com a alteração necessária para que funcione devidamente, utilizando o binder recém criado:

public ActionResult Adicionar([JsonBinder]Usuario usuario)
{
    //...
}

Conclusão: Este artigo mostrou mais uma das possibilidades que temos ao utilizar o jQuery, onde conseguimos acessar métodos escritos em C#/VB.NET, sem a necessidade de uma atualização geral da página. Como já sabemos, a experiência do usuário aumenta, e nós como desenvolvedores, podemos fazer uso de uma API consistente, independente se estamos consumindo métodos locais ou remotos.

Tags: , , ,

ASP.NET

MEF - Managed Extensibility Framework

by Israel Aece 17. December 2009 09:37
Revista Mundo .NET - 018 Toda aplicação que deseja suportar “plugins”, terá que se preocupar, também, em criar toda a infraestrutura necessária para suportá-los. Como essa preocupação está cada vez mais popular nos dias de hoje, a Microsoft trabalha em um projeto chamado MEF – Managed Extensibility Framework. Ao invés de explicitamente referenciar os componentes na aplicação, o MEF permitirá efetuar o descobrimento desses componentes de forma implícita, através de composição, gerenciando tudo o que for preciso para manter essas extensões, possibilitando que sua aplicação fique dependente de uma abstração, e não de uma implementação.

Atualmente o MEF encontra-se em desenvolvimento, e a Microsoft tem disponibilizado versões CTPs (Community Technology Preview), para avaliações do produto. A versão atual é a CTP 8, que já vem incorporada no Beta 2 do Visual Studio .NET 2010 e .NET Framework 4.0 e, provavelmente, fará parte da versão final do .NET Framework 4.0.

Este artigo tem a finalidade de abordar a API do MEF, mostrando todas as suas funcionalidades e como podemos utilizá-las em nossas aplicações.

Tags: , ,

.NET Framework

Caching via Providers

by Israel Aece 16. November 2009 13:08

Assim como o membership, roles, profile, webparts ou health monitoring, a versão 4.0 do ASP.NET também permitirá trabalhar com o caching seguindo essa mesma arquitetura, ou seja, através de provider models. Com isso, teremos uma maior flexibilidade para elegermos o nosso repositório de caching, que nem sempre será a memória do próprio servidor onde a aplicação está hospedada. Provavelmente será através deste recurso que a Microsoft irá acoplar o "Velocity" ao pipeline do ASP.NET.

Para customizar, tudo o que precisamos fazer é criar uma classe que herde da classe abstrata OutputCacheProvider, e sobrescrever os seus métodos autoexplicativos: Add, Get, Remove e Set, assim como é mostrado abaixo.

public class TextCacheProvider : OutputCacheProvider
{
    private static readonly string PATH;

    static TextCacheProvider()
    {
        PATH = HttpContext.Current.Server.MapPath("~/CachedPages/");
    }

    public override object Add(string key, object entry, DateTime utcExpiry) { } 

    public override object Get(string key) { }

    public override void Remove(string key) { }

    public override void Set(string key, object entry, DateTime utcExpiry) { }
}

Depois desta classe criada, que seguirá as regras de caching customizadas, precisamos configurá-la para poder ser utilizada, e para isso recorremos ao arquivo Web.config. Tudo o que precisamos fazer para que essa nova classe funcione, é acoplá-la a execução, definindo-a como sendo o provider padrão de caching. O trecho de código abaixo ilustra essa configuração:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
    <caching>
      <outputCache defaultProvider="TextCacheProvider" enableOutputCache="true">
        <providers>
          <clear/>
          <add name="TextCacheProvider" type="TextCacheProvider"/>
        </providers>
      </outputCache>
    </caching>
</configuration>

E se desejar escolher, em tempo de execução, um dos providers de acordo com uma condição específica, você poderá fazer isso através do método GetOutputCacheProviderName, que é exposto pela classe HttpApplication (Global.asax), e retorna uma string que representá o provider a ser utilizado.

Tags: , ,

ASP.NET

Página de Acesso Negado

by Israel Aece 4. August 2009 12:55

Quando utilizamos FormsAuthentication para proteger uma determina página ou diretório, há um comportamento que talvez não agrade a maioria das pessoas. Suponhamos que você faça o login digitando o seu nome de usuário e senha. Ao validar esse usuário em algum repositório, o ASP.NET automaticamente redireciona o usuário para a página/diretório que ele tentou acessar inicialmente.

A partir daí, se você utilizar roles para restringir o acesso à alguma página/diretório dentro da aplicação e o usuário não esteja dentro dela, ele será redirecionado - novamente - para a página de login. Talvez isso não seja o ideal, já que o mais coerente seria redirecionar o usuário para uma página que contenha uma mensagem mais amigável e próxima ao que realmente aconteceu, algo como: "Você não tem permissão para acessar o recurso solicitado. Contate o Administrator.".

Quando utilizamos a UrlAuthorization (que já é o padrão), a cada requisição ela verifica se o usuário que acessa uma determinada página, possui a permissão para isso. Caso a configuração no arquivo Web.config diz que, para acessar aquela página, é necessário que o usuário esteja na role de "Administradores", e ele por sua vez não estiver, o UrlAuthorization define a propriedade StatusCode da resposta do HTTP para 401 (que significa "Acesso Negado"). O FormsAuthentication (mais precisamente, o FormsAuthenticationModule) se vincula ao evento EndRequest, e dentro dele analisa se a propriedade StatusCode foi definida como 401. Caso tenha sido, o FormsAuthenticationModule redireciona o usuário para a página de login, trocando o StatusCode para 302 ("Redirecionar").

Para mudar este comportamento, podemos criar um módulo e nos vincularmos ao evento EndRequest. Dentro deste evento, podemos analisar se a propriedade StatusCode foi definida como 401, e se estiver, redirecionar para uma página que informa exibe a mensagem correta ao invés da página de login, melhorando assim a navegabilidade da aplicação. O módulo é extremamente simples, assim como podemos notar abaixo:

public class AccessDeniedModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    private void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        if (app.Response.StatusCode == 401 && app.User.Identity.IsAuthenticated)
        {
            app.Response.Redirect("~/AreaRestrita/AcessoNegado.aspx");
        }
    }

    public void Dispose() { }
}

Como já sabemos, para que um módulo funcione, precisamos acoplá-lo na execução do ASP.NET, e fazemos isso através do elemento <httpModules />. Mas este módulo deve ter um cuidado especial para que funcione adequadamente. Já existem diversos módulos em execução pelo ASP.NET, e um deles é justamente o FormsAuthenticationModule. Se simplesmente adicionarmos o módulo que criamos na coleção de módulos da aplicação, o StatusCode vai chegar sempre como 302, pois a requisição primeiramente passará pelo módulo FormsAuthenticationModule, que como vimos acima, faz essa mudança. Com isso, a nossa condicional sempre irá falhar, fazendo com que o usuário seja redirecionado para a página de login. A dica aqui é ir até o arquivo Web.config que está em nível de servidor (%windir%\Microsoft .NET\Framework\VERSÃO\CONFIG\), copiar a coleção de módulos e colar no arquivo Web.config da aplicação, como podemos ver abaixo:

<httpModules>
  <clear />
  <add name="ScriptModule"
       type="System.Web.Handlers.ScriptModule, System.Web.Extensions"/>
  <add name="OutputCache"
       type="System.Web.Caching.OutputCacheModule"/>
  <add name="Session"
       type="System.Web.SessionState.SessionStateModule"/>
  <add name="AccessDeniedModule"
       type="AccessDeniedModule"/>

  <add name="FormsAuthentication"
       type="System.Web.Security.FormsAuthenticationModule"/>
  <add name="RoleManager"
       type="System.Web.Security.RoleManagerModule"/>
  <add name="UrlAuthorization"
       type="System.Web.Security.UrlAuthorizationModule"/>
  <add name="AnonymousIdentification"
       type="System.Web.Security.AnonymousIdentificationModule"/>
  <add name="Profile"
       type="System.Web.Profile.ProfileModule"/>
</httpModules>

Depois de colocar os módulos no arquivo Web.config da aplicação, precisamos colocar o elemento <clear /> para que não duplique a execução dos módulos. E para finalizar, o módulo que criamos deverá ser colocado antes do módulo do FormsAuthentication para que funcione como o esperado. Aproveitando a oportunidade, é importante que você deixe somente os módulos que a sua aplicação realmente utiliza, ganhando alguma performance, já que evitará passar por passos desnecessários. Para maiores detalhes sobre isso, consulte este artigo.

Tags: ,

ASP.NET | Security

A sua sessão expirou

by Israel Aece 28. July 2009 08:18

Um amigo me pediu uma ajuda para escrever a seguinte mensagem na página de Login de uma aplicação ASP.NET: "A sua sessão expirou. Efetue o login novamente.". Como ele está utilizando FormsAuthentication, pensei em analisar a existência da QueryString "ReturnUrl", que geralmente é adicionada como parte da URL, quando você tenta acessar algum recurso protegido e que você não esteja devidamente autenticado.

O problema desta técnica é que as vezes o usuário tem o endereço (nos "Favoritos", por exemplo) desta página restrita, e suponhamos que ele abra o navegador e tente acessá-la diretamente. O FormsAuthentication fará a parte dele, ou seja, redirecionará o usuário para a página de Login com a QueryString "ReturnUrl" contendo a página que ele tentou acessar. Se estiver analisando essa QueryString, a mensagem de "sessão expirada" será exibida, mas não faz sentido neste caso, já que é o primeiro acesso deste usuário.

Para tentar contornar esse problema, a solução foi a criação de um módulo, vinculando ao evento BeginRequest. Esse módulo será responsável por analisar a validade do cookie de autenticação, e se estiver expirado, redirecionará o usuário para a página de Login. Esse mesmo processo acaba sendo feito pelo próprio FormsAuthenticationModule, mas com este novo módulo, a ideia é nos antecipar ao FormsAuthentication e adicionar um novo item na coleção de QueryStrings, que especifica que a autenticação expirou. Esse valor será analisado pela página de Login, que baseando-se nele irá ou não exibir a mensagem em questão. Abaixo temos o módulo na íntegra:

using System;
using System.Web;
using System.Web.Security;

public class AuthenticationExpiredModule : IHttpModule
{
    private HttpApplication _app;

    public void Init(HttpApplication context)
    {
        this._app = context;
        this._app.BeginRequest += new EventHandler(this._app_BeginRequest);
    }

    private void _app_BeginRequest(object sender, EventArgs e)
    {
        if (this._app.Request.Path != FormsAuthentication.LoginUrl)
        {
            HttpCookie authCookie = 
                this._app.Request.Cookies[FormsAuthentication.FormsCookieName];

            if (authCookie != null)
            {
                FormsAuthenticationTicket ticket =
                    FormsAuthentication.Decrypt(authCookie.Value);

                if (ticket.Expired)
                {
                    this._app.CompleteRequest();
                    FormsAuthentication.RedirectToLoginPage("Authentication=Expired");
                }
            }
        }
    }

    public void Dispose() { }
}

Depois de criado, precisamos registrá-lo no arquivo de configuração (Web.config) para que ele comece a fazer parte da execução:

<httpModules>
    <add
        name="AuthenticationExpiredModule"
        type="AuthenticationExpiredModule"/>
</httpModules>

E, finalmente, já na página de Login, verificando a existência da QueryString:

this.AuthenticationExpiredMessage.Visible = Request.QueryString["Authentication"] == "Expired";

Tags: ,

ASP.NET | Security

Interfaces Explícitas e o WCF

by Israel Aece 19. May 2009 12:11

O C# e o VB.NET possibilitam a implementação explícita de interfaces. Esse tipo de implementação quer dizer que ao implementar a interface em uma classe, a assinatura do membro (propriedade, evento, método, etc.) levará o “nome completo”. Para exemplificar, note o exemplo abaixo, que exibe as duas implementações (implítica e explicíta):

class Data : ILog
{
    public void WriteMessage(ILogger logger, string msg) { }
}

class IO : ILog
{
    void ILog.WriteMessage(ILogger logger, string msg) { }
}

Note que na segunda implementação, o método WriteMessage está prefixado com o nome da interface onde ele foi definido, que neste caso é ILog. A finalidade deste tipo “diferenciado” de implementação é reduzir possíveis conflitos de nomenclatura que possa haver e, principalmente, esconder a implementação da “visão” pública do tipo onde ela foi implementada. É importante dizer que este tipo de implementação não proibe o cliente de acessar os métodos; basta apenas fazer um cast da instância da classe para a interface, que o método WriteMessage estará acessível.

Esse tipo de implementação também é útil ao trabalharmos com o WCF. Quando estamos utilizando algum recurso de extensibilidade, é muito comum implementarmos as interfaces predefinidas por ele, como por exemplo IServiceBehavior, IOperationBehavior ou IExtesion<T>, para acoplar um código customizado durante a execução do serviço. Além disso, quando desejamos customizar a serialização de um tipo, também devemos implementar algumas interfaces (ISerializable ou IXmlSerializable) fornecidas pelo .NET Framework. Quando implementadas, essas interfaces são utilizadas exclusivamente pelo runtime do WCF/.NET, e em um primeiro momento, os métodos que elas expõem não serão acessados diretamente pelo desenvolvedor.

Um exemplo mais concreto disso é a classe ServiceMetadataBehavior, que implementa de forma explítica a interface IServiceBehavior. Se estivermos trabalhando com a configuração imperativa,  ao instanciar essa classe, apenas iremos visualizar as propriedades que ela disponibiliza para configuração dos metadados do serviço, e não os métodos que a interface expõe, tornando o tipo bem menos poluído e de fácil entendimento, não misturando o que deve ser acessado pelo desenvolvedor e o que deve ser acessado pelo runtime.

Tags: ,

WCF | .NET Framework

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host