Autorização Simplista no ASP.NET

by Israel Aece 6. June 2011 10:29

O Forms Authentication é o mecanismo que temos desde a primeira versão do ASP.NET, e que hoje faz parte do seu core, e é responsável pelo gerenciamento do processo de autenticação de um usuário, utilizando cookies. É importante dizer que este recurso apenas se encarrega de gerenciar o cookie que é criado, se certificando de que ele existe, é válido e não está expirado, atuando como um "guarda" logo nos primeiros estágios da requisição.

Só que a verificação quanto a existência daquele usuário é importante, e temos algumas opções para isso. Uma delas é recorrer à um banco de dados customizado, um arquivo Xml, ou ainda, utilizar toda a infraestrutura que o ASP.NET fornece a partir da versão 2.0, conhecida como Membership. Além dessas alternativas, para alguns cenários extremamente simplistas, podemos utilizar o próprio arquivo Web.config para armazenar os usuários. Para isso, utilizamos o sub-elemento credentials do elemento forms, que é utilizado para configurar o Forms Authentication. Abaixo temos este exemplo:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="~/PaginaDeLogin.aspx">
        <credentials passwordFormat="Clear">
          <user name="Israel" password="123"/>
          <user name="Claudia" password="456"/>
        </credentials>
      </forms>
    </authentication>
  </system.web>
</configuration>

Com os usuários ali catalogados, para que seja possível verificar a existência do mesmo, recorremos ao método estático Authenticate da classe FormsAuthentication. Este método receberá duas strings, que correspondem ao login e senha, retornando um valor boleano indicando se o usuário existe ou não dentro do arquivo de configuração. Isso é o suficiente para manter os usuários da aplicação, sem necessitar de algum recurso extra para armazenar os usuários. Abaixo o código que faz essa checagem:

if (FormsAuthentication.Authenticate(this.Login.Text, this.Senha.Text))
    FormsAuthentication.RedirectFromLoginPage(this.Login.Text, false);

Apesar da Microsoft suportar um modelo de gerenciamento de usuários de forma simples, ela não disponibiliza um recurso nesta mesma linha para armazenar os papéis do usuário. Talvez isso se deva ao fato de isso já deve ser encarado como sendo algo mais rebuscado, ou seja, devendo recorrer à algo mais elegante para o gerenciamento de usuários e seus respectivos papéis. Mas para aqueles que ainda desejam manter a simplicidade, você pode ter isso com um pouco de trabalho. O primeiro passo consiste na criação de um arquivo para armazenar os papéis, e para isso vamos utilizar o formato Xml, relacionando os papéis através do nome do usuário. Abaixo temos o conteúdo do arquivo que chamei de userRoles.config, mas que não possui nenhuma relação com o arquivo de configuração da aplicação, o Web.config.

<?xml version="1.0"?>
<userRoles>
  <user name="Israel">
    <roles>
      <role name="Admin" />
      <role name="TI" />
    </roles>
  </user>
  <user name="Claudia">
    <roles>
      <role name="Gerencia" />
    </roles>
  </user>
</userRoles>

Com todos papéis ali definidos, precisamos criar uma classe que será responsável pela busca e extração dos papéis de um determinado usuário. Essa classe estática, chamada de ConfigurationRoles, é extremamente simples, que expõe um método chamado GetRolesByUser, e que recebe o nome do usuário, e efetua a busca no arquivo userRoles.config utilizando LINQ To Xml, e caso encontre-o, retornará um array de strings, onde cada elemento corresponde à um papel em que o usuário está contido. Abaixo temos a classe que acabou de ser descrita:

internal static class ConfigurationRoles
{
    private static readonly string RolesFilePath;

    static ConfigurationRoles()
    {
        RolesFilePath = HttpContext.Current.Server.MapPath("~/userRoles.config");
    }

    internal static string[] GetRolesByUser(string username)
    {
        XDocument doc = XDocument.Load(RolesFilePath);

        return
            (
                from u in doc.Descendants("user")
                where u.Attribute("name").Value == username
                from r in u.Descendants("role")
                select r.Attribute("name").Value
            ).ToArray();
    }
}

Só que ainda nos resta utilizar isso na aplicação. Para interceptar o processo de autenticação, podemos utilizar o evento Authenticate da classe FormsAuthenticationModule, que é o responsável por gerenciar o processo da autenticação através do Forms Authentication. Esse evento nos permitirá a customização do objeto que representa o usuário (IIdentity) e seus papéis (IPrincipal), que é exatamente o que precisamos fazer. Para tratarmos o evento, criamos no arquivo Global.asax um método nomeado como FormsAuthentication_OnAuthenticate, que automaticamente o ASP.NET o invocará quando o evento Authenticate acontecer. Abaixo temos a o código referente a este processo:

public class Global : HttpApplication
{
    public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e)
    {
        if (FormsAuthentication.CookiesSupported)
        {
            if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
            {
                FormsAuthenticationTicket ticket =
                    FormsAuthentication.Decrypt(
                        Request.Cookies[FormsAuthentication.FormsCookieName].Value);

                string username = ticket.Name;

                e.User =
                    new GenericPrincipal(
                        new GenericIdentity(username),
                        ConfigurationRoles.GetRolesByUser(username));
            }
        }
        else
        {
            throw new HttpException("Modelo de autenticação não suportado.");
        }
    }
}

Note que se houver um cookie de autenticação criado, decriptamos o mesmo extraindo o nome do usuário. Em seguida, construímos uma nova identidade a partir daquele nome de usuário, e recorremos a classe que criamos acima, extraindo todos os papéis daquele usuário, envolvendo tudo isso em um objeto chamado GenericPrincipal. Note que a instância é atribuída à propriedade User do parâmetro FormsAuthenticationEventArgs que é passado ao método, e que a partir de agora, será utilizado por toda aplicação.

Apesar de tudo isso funcionar, ele deve ser utilizado em casos realmente simples, onde a demanda por gerenciar usuários e papéis em tempo de execução não seja necessária. Do contrário, o melhor é recorrer à alguns recursos que já temos à disposição tanto para o gerenciamento de usuários como o de papéis.

Tags: , , ,

ASP.NET

Validando a postagem do WIF

by Israel Aece 8. October 2010 11:38

Quando estamos utilizando um STS para autenticar uma aplicação ASP.NET (ambiente passivo), somos redirecionados para a aplicação autenticadora, nos identificamos e caso seja um usuário válido, o token será criado e postado para a aplicação (relying party) que requisitou. Essa aplicação utilizará o WIF para extrair as informações do token, validá-las e deixá-las disponíveis para serem utilizadas.

O token é serializado em formato XML, contendo todas as informações de infraestrutura (envolvendo os artefatos de segurança) e as claims. Como o conteúdo da serialização é extenso, o STS envia isso através do body de uma requisição HTTP, que é realizada através do método POST, e é aqui que o problema reside.

Por questões de segurança, o ASP.NET não permite postar requisições que contenham em seu corpo tags, para evitar problemas de cross-site scripting (XSS). Como o WIF posta uma mensagem com conteúdo XML, o ASP.NET barra logo nos primeiros estágios da requisição. Uma solução que existe desde as primeiras versões do ASP.NET, é desligar essa verificação na página que recebe a requisição com o token do WIF. Para isso basta definir o atributo ValidateRequest da diretiva @Page para False.

Desligando essa verificação, iremos conseguir receber o token do WIF, mas abriremos uma porta para que pessoas maliciosas possam explorar e, consequentemente, danificar a nossa aplicação. Felizmente na versão 4.0 do ASP.NET a Microsoft permitiu que se customize essa validação, criando um validador próprio que podemos analisar a requisição, e mesmo que ela esteja postando tags, não quer dizer que ela seja maliciosa, assim como é o caso do token do WIF.

Para criar um validador customizado, devemos criar uma classe que herde da classe RequestValidator. Essa classe fornece um método virtual chamado IsValidRequestString, que deve ser sobrescrito na classe derivada, que determinará se a requisição é válida ou não. Esse método recebe vários parâmetros, que fornecem todas as informações necessárias para criarmos a regra de validação. Entre esses parâmetros temos o contexto da requisição (HttpContext), uma string que representa o valor a ser validado, que trabalha em conjunto com o um parâmetro do tipo RequestValidationSource, que especifica qual dado da requisição está sendo validado (Forms, QueryStrings, Headers, Cookies, etc.).

Como precisamos saber se a requisição trata-se de um token gerado pelo WIF, ele próprio traz métodos que permitem a criação de uma mensagem (RSTR) a partir do conteúdo de uma requisição HTTP. Sendo assim, se o conteúdo da postagem for válido e representa um token, então quer dizer que a requisição contém tags, mas são tags que correspondem a uma espécie de informação que sabemos como lidar, ou seja, não é maliciosa. O exemplo abaixo ilustra como efetuar esta implementação, recorrendo a tipos que o WIF fornece:

public class ValidadorDoToken : RequestValidator
{
    protected override bool IsValidRequestString(HttpContext context, string value,
        RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex)
    {
        validationFailureIndex = 0;

        if (requestValidationSource == RequestValidationSource.Form &&
            collectionKey.Equals(WSFederationConstants.Parameters.Result, StringComparison.Ordinal))
        {
            SignInResponseMessage message =
                WSFederationMessage.CreateFromFormPost(context.Request) as SignInResponseMessage;

            if (message != null)
            {
                return true;
            }
        }

        return base.IsValidRequestString(context, value, requestValidationSource,
            collectionKey, out validationFailureIndex);
    }
}

Como todas as informações da requisição são passadas para o validador, note que a condicional verifica se estamos recebendo o body da requisição, e como o body pode ter vários campos, estamos interessados apenas naquele campo que corresponde ao token serializado que é representado pelo campo wresult.

O ASP.NET MVC também impõe este mesmo tipo de validação, que felizmente recorre a este mesmo validador, ou seja, podemos criar um único e utilizar por qualquer tipo de projeto, WebForms ou MVC. Apenas é necessário tomar cuidado quando você decora a ação (método) com o atributo ValidateInputAttribute. A finalidade deste atributo é justamente permitir a validação da requisição, mas na versão 4.0 do ASP.NET, a adição deste validador customizado fez com aquele atributo seja executado depois do validador, que por sua vez, roda logo no evento BeginRequest.

Tags: , ,

ASP.NET | WIF

Autorização com WIF

by Israel Aece 28. September 2010 07:52

Dentro do desenvolvimento de software, o termo autorização descreve o processo de verificar se um determinado usuário possui ou não o acesso à um determinado recurso. Mas é importante dizer que a autorização acontece depois da autenticação, pois primeiramente é necessário saber quem o usuário é, para depois saber que direitos ele tem no sistema.

Recentemente eu comentei em alguns artigos sobre a utilização de um sistema de identidade para autenticar os usuários. Uma das principais finalidades é a terceirização do processo de autenticação, delegando isso à uma outra aplicação/serviço, e assim, todas as aplicações que confiarem naquele autenticador (STS), poderão fazer uso do mesmo para autenticar seus respectivos usuários, ficando essas aplicações totalmente isentas em como isso deve acontecer.

Como comentando nos artigos acima, o WIF (Windows Identity Foundation) abstrai grande parte da complexidade para fazer isso tudo funcionar, mas a autorização ainda continua sendo uma responsabilidade da própria aplicação. A finalidade deste artigo é apresentar algumas alternativas para efetuar a autorização nas aplicações, utilizando as claims emitidas por um determinado autenticador para refinar o acesso.

Grande parte das aplicações fazem uso do modelo baseado em papéis (roles), onde concedem ou negam acesso à algum recurso da aplicação se o usuário está ou não contido em um grupo específico. Tecnicamente falando, sempre recorremos a utilização de alguma implementação da interface IPrincipal, que fornece um método chamado IsInRole, que dado o papel retorna um valor boleano indicando se o usuário corrente está ou não contido nele.

Como já comentei anteriormente, a autorização baseada em claims é muito mais flexível do que isso, ou seja, não estamos condicionados a somente trabalhar com papéis (simples strings), mas também avaliar se o usuário poderá ou não acessar um determinado recurso a partir da sua idade, ou somente se o seu cartão de crédito estiver válido, e assim por diante.

Uma das grandes preocupações que a Microsoft teve, foi em manter compatibilidade com o modelo de objetos que utilizamos desde a primeira versão do .NET Framework, mas conseguindo já tirar proveito deste novo modelo. E foi justamente pensando nisso, que ela criou as interfaces IClaimsIdentity e IClaimsPrincipal. Elas já foram comentadas com mais detalhes neste artigo, e somente para recapitular, uma das principais propriedades é a Claims, exposta pela interface IClaimsIdentity, que fornece o conjunto de claims que foram emitidas pelo autenticador para o usuário atual.

A propriedade RoleClaimType também foi criada com propósito de compatibilidade. Como todas as informações emitidas por um STS chegam para uma relying party através de claims, é necessário saber qual ou quais as claims representam os papéis (IPrincipal.IsInRole). Com isso, podemos continuar utilizando a autorização baseada em papéis, mas nos bastidores, são claims que estão sendo utilizadas. Com isso, por mais que começamos a utilizar as técnicas de autenticação através do WIF, a autorização pode continuar da forma que está, sem a necessidade de sofrer qualquer alteração.

Como sabemos, entre as claims que existem predefinidas nas especificações, ainda podemos criar claims customizadas. Isso quer dizer que um STS pode emitir uma claim sem vincular ela ao tipo role, que por padrão, a aplicação utiliza quando chamamos o método IsInRole. Mas felizmente isso pode ser configurado de forma declarativa, onde cada aplicação determina qual a claim que ela vai quer utilizar/avaliar quando o método IsInRole for chamado. Para efetuar essa customização, podemos recorrer ao sub-elemento roleClaimType do elemento samlSecurityTokenRequirement, assim como é mostrado abaixo:

<samlSecurityTokenRequirement>
  <roleClaimType value= "http://MinhaAplicacao/departamento" />
</samlSecurityTokenRequirement>

Como comentado acima, podemos continuar trabalhando com a segurança baseada em papéis. E para isso, utilizamos o método IsInRole exposto pela interface IPrincipal ou através da classe PrincipalPermission, ou ainda, do atributo PrincipalPermissionAttribute, que nos permite trabalhar de forma imperativa ou declarativa, respectivamente. Todas deverão recorrer as claims do tipo role (http://schemas.microsoft.com/ws/2008/06/identity/claims/role) ou alguma outra, conforme configuramos acima. Mas o WIF também fornece um novo par de classes para trabalhar explicitamente com claims, a saber: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Ambas também servem para utilizar de forma imperativa ou declarativa, respectivamente, mas como já podemos perceber, vai além de somente utilizar claims como roles. Podemos aqui utilizar outras claims que foram emitidas pelo STS, e tornar o processo de autorização muito mais inteligente.

Como comentado, o uso pode ser de forma imperativa ou declarativa. Utilizar um modo ou outro tem suas vantagens e desvantagens. De qualquer forma, a utilização é extremamente fácil, pois é semelhante ao modelo que já estamos acostumados a trabalhar dentro da plataforma .NET. Para exemplificar, o exemplo abaixo mostra a utilização de ambas as formas:

[ Modelo Imperativo ]

public string AlgumaOperacao()
{
    new ClaimsPrincipalPermission("AlgumaOperacao", "Executar").Demand();

    //Processamento do Método
}

[ Modelo Declarativo ]

[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = "AlgumaOperacao", Action = "Executar")]
public string AlgumaOperacao()
{
    //Processamento do Método
}

Apesar de aparentemente funcionar como o modelo baseado em papéis, o WIF nos induz a não fixar o valor das claims pelo nosso código, que é algo que mais cedo ou mais tarde sempre acaba mudando, e justamente por isso que temos as propriedades Resource e Action, mas não devemos nos preocupar com o que significado delas agora, pois o seu uso está condicionado ao ambiente em que são utilizadas, e que serão detalhados mais abaixo. Um detalhe importante também é que as duas classes que representam a permissão não são responsáveis por efetivamente validar se o usuário poderá ou não acessar aquele recurso; isso é delegado à uma outra classe, chamada ClaimsAuthorizationManager, qual veremos a seguir.

O processo de autorização é altamente customizável, o que nos permite interceptar o momento em que o runtime avalia se o usuário possui ou não permissão, e com isso alterar o comportamento padrão, nos baseando em outras claims que foram emitidas para tomar a decisão se o usuário deverá ou não acessar o recurso.

Independentemente de qual ambiente estamos falando (ativo ou passivo), as classes que lidam com o processo de autorização são as mesmas. A diferença consiste nas informações que são colocadas dentro dessas classes, pois elas são contextualizadas, condicionados ao ambiente. A principal classe que irá gerenciar isso é a ClaimsAuthorizationManager, que fornece um local central para analisarmos a requisição e determinar se o usuário poderá ou não acessar o recurso. Essa classe possui um único método chamado CheckAccess, que é disparado antes de acessar o recurso em si, e dado o contexto da requisição, retorna um valor boleano indicando se o usuário terá acesso ao mesmo, e que por padrão sempre permite o acesso.

Como dito acima, o método CheckAccess recebe o contexto da requisição como parâmetro, fornecendo todas as informações necessárias para a tomada de decisão. Esse parâmetro é do tipo AuthorizationContext, e possui três principais propriedades: Principal, Resource e Action. A primeira propriedade retorna uma instância da classe ClaimsPrincipal, que representa o usuário atual. Já a propriedade Resource determina o recurso que você está tentando acessar, e isso varia de acordo com o ambiente. Finalmente, a propriedade Action determina a operação que está sendo aplicada ao recurso, e assim como a propriedade Resource, também varia de acordo com o ambiente.

Quando implementamos a classe ClaimsAuthorizationManager para o ambiente passivo, a propriedade Resource trará o caminho e nome da página ASPX que estamos tentando acessar e a Action deve retornar se está sendo acessada via GET ou via POST. Já no ambiente ativo, a propriedade Resource deverá representar o endereço do serviço que está sendo acessado, enquanto a propriedade Action deverá refletir a SOAPAction da operação. A propriedade Principal tem o mesmo significado para ambos ambientes, ou seja, representar o usuário que está tentando acessar o recurso. Como há algumas outras diferenças entre os dois ambientes, vamos analisar a sua implementação individualmente a partir de agora.

Autorização no Ambiente Passivo - ASP.NET

A implementação da classe ClaimsAuthorizationManager para o ambiente passivo, consistirá em avaliar se a página (ASPX) que o usuário está acessado poderá ou não ser acessado, de acordo com uma regra determinada. Cada página que for acessada por aquele usuário, o runtime do WIF em conjunto com o ASP.NET invocará o método CheckAccess, que é onde terá toda a lógica de autorização.

Como exemplo, vamos somente permitir o acesso à uma determinada página se o usuário for maior que 21 anos de idade. Como o STS emite uma claim chamada dataDeNascimento, podemos efetuar o cálculo em cima dela, para avaliar se o usuário poderá ou não acessar a página. Abaixo temos a implementação:

public class AutorizadorDePaginas : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Resource.First().Value == "http://localhost:2721/PaginaRestrita.aspx")
        {
            var claimDeDataDeNascimento =
                (
                    from in context.Principal.Identities[0].Claims
                    where c.ClaimType == "http://MinhaAplicacao/dataDeNascimento"
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

Em primeiro lugar verificamos se a página que está sendo acessada deve ser verificada quanto a permissão. Como sabemos que devemos filtrar o acesso a partir da idade, então vamos em busca de uma claim que represente a data de nascimento do usuário, e se encontrada, verificamos se ele já possui mais que 21 anos. Se ele tiver, então ele pode acessar, do contrário, terá o acessado negado.

Mas essa implementação por si só não funciona. Temos que saber como acoplá-la ao pipeline de processamento do ASP.NET. Na verdade, o responsável por invocar o método CheckAccess será o módulo ClaimsAuthorizationModule, que por padrão, não está configurado. Esse módulo se vincula ao evento AuthorizeRequest do objeto HttpApplication, e quando é disparado, irá extrair o autorizador configurado na seção do WIF e, consequentemente, irá passar a instância da classe AuthorizationContext, onde o Resource representará o caminho até a página ASPX e a Action deve representar o verbo HTTP (GET ou POST). Abaixo temos a configuração no arquivo Web.config que correspondem ao módulo e ao autorizador:

<system.web>
  <!-- Outras Configurações -->
  <httpModules>
    <!-- Outros Módulos -->
    <add name="ClaimsAuthorizationModule"
         type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, ..."/>
  </httpModules>
</system.web>
<microsoft.identityModel>
  <service>
    <!-- Outras Configurações -->
    <claimsAuthorizationManager
        type="Servico.AutorizadorDePaginas, Servico, Version=1.0.0.0, ..."/>
  </service>
</microsoft.identityModel>

Autorização no Ambiente Ativo - WCF

Já no WCF vamos trabalhar de forma parecida ao que vimos acima, ao que diz respeito a forma de implementar a classe responsável pela autorização do usuário. As ligeiras diferenças estão nas informações que são colocadas na classe AuthorizationContext e como ela é acoplada à execução.

Neste ambiente, a propriedade Resource representará o serviço que está sendo acessado, mais precisamente, o endereço do mesmo. Já a propriedade Action determinará a SOAPAction que o usuário quer invocar. 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, e que indicará qual o método a ser invocado.

A ideia aqui é justamente analisar a SOAPAction, verificando se trata-se de uma operação que exige a validação da permissão do usuário. Caso seja, então vamos verificar se ele possui a idade suficiente para acessar a operação em questão. Abaixo temos a implementação, bem próxima a qual fizemos no ambiente passivo:

public class AutorizadorDeOperacoes : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Action.First().Value == "http://tempuri.org/IConsultasFinanceiras/RecuperarLimiteDeCredito")
        {
            var claimDeDataDeNascimento =
                (
                    from c in context.Principal.Identities[0].Claims
                    where c.ClaimType == "http://MinhaAplicacao/dataDeNascimento"
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

A principal diferença aqui é como o WIF acopla isso à execução no WCF, que deve executá-la sempre quando uma nova requisição chegar ao serviço. Para que o serviço faça uso do WIF, é necessário submetermos a instância da classe ServiceHost para o método estático ConfigureServiceHost da classe FederatedServiceCredentials, assim como já foi mostrado neste artigo. Assim como no ASP.NET, o que o WIF faz aqui é utilizar um ponto de estensibilidade do WCF para adicionar a implementação do ClaimsAuthorizationManager, e para isso, o WIF já possui implementado uma classe que deriva de ServiceAuthorizationManager, que é a IdentityModelServiceAuthorizationManager, e em seu método CheckAccessCore captura o autorizador implementado/configurado pelo WIF e invoca o seu método CheckAccess, abastecendo a instância da classe AuthorizationContext com informações que estão dentro do contexto da operação (OperationContext).

Sendo assim, tudo o que precisamos aqui é configurar o autorizador customizado, para que o runtime do WIF possa capturá-lo e, consequentemente, entregar ao WCF para efetuar a validação. O código abaixo ilustra essa configuração, que é realizada da mesma forma que no ambiente passivo:

<microsoft.identityModel>
  <service>
    <!-- Outras Configurações -->
    <claimsAuthorizationManager
        type="Servico.AutorizadorDeOperacoes, Servico, Version=1.0.0.0, ..."/>
  </service>
</microsoft.identityModel>

Claims Explícitas

Para ratificar o que disse acima, em ambos cenários não temos as claims espalhadas pela aplicação. Como isso pode mudar, o ideal é manter centralizado, para facilitar a alteração caso ela seja necessária.

O mesmo vale para as classes que falamos acima: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Quando as utilizamos, nós vimos que tudo o que precisamos colocar ali é o Resource e Action, que determinam informações sobre o acesso e não sobre a claim que deve ser utilizada/avaliada.

Essas classes não são responsáveis por fazerem a verificação. Intermamente elas recorrem a implementação da classe ClaimsAuthorizationManager, que deve estar configurada da forma que vimos acima. Caso não tenhamos um autorizador explicitamente definido, o WIF utilizará uma implementação padrão, que sempre concederá o acesso. O mais interessante aqui é que as informações que colocamos nestas classes também são enviadas para a classe que herda de ClaimsAuthorizationManager, pois elas invocam o método CheckAccess, abstecendo o AuthorizationContext com as respectivos informações.

Nativamente o WIF não traz suporte para vinculação de claims à um determinado recurso, mas podemos criar algo customizado para isso. A ideia é ter algum repositório para catalogar as políticas de acesso à determinados recursos, por exemplo, para acessar uma determinada página ASPX ou uma operação de um serviço, precisamos verificar se a quantidade de anos da claim dataDeNascimento é maior que 21. Esse repositório pode ser um arquivo Xml, uma base de dados ou até mesmo o próprio arquivo de configuração (*.config). Criando algo customizado, nos permite tornar a implementação do ClaimsAuthorizationManager mais simples, dinâmica e ainda mantendo a centralização.

Conclusão: Vimos neste artigo o processo de autorização de uma aplicação que opta por fazer o uso do WIF para terceirizar a autenticação do usuário. Apesar de autorização ainda ser responsabilidade da relying party, o WIF se preocupou em fornecer classes para tornar esse processo mais suave, e não menos importante, mantendo compatibilidade com o legado, que são aplicações que se baseiam em papéis para refinar o acesso aos seus respectivos recursos.

Tags: , ,

ASP.NET | WCF | WIF

Escolhendo o gerenciamento de estado no ASP.NET

by Israel Aece 29. July 2010 16:24

Uma das principais necessidades em uma aplicação Web é a manutenção de estado. O protocolo HTTP determina que todas as aplicações que rodam sobre ele, não mantém nenhuma espécie de informação do lado do servidor, ou seja, ao requisitar qualquer recurso, uma vez que o conteúdo é enviado ao cliente solicitante, tudo o que foi gerado do lado do servidor para atende-lo, será descartado. Caso uma nova requisição ocorrer, mesmo que seja para o mesmo recurso, tudo será reconstruído.

Mas o que é o "estado"? Trata-se de dados que representam certas informações para o usuário ou para a aplicação, e que precisam ser armazenadas em algum local para conseguirmos restaurá-las no momento em que precisarmos delas. Depois que a requisição foi atendida pelo servidor, se ele precisar armazenar informações para aquele usuário, precisamos fazer uso de alguma das alternativas que são disponibilizadas pela tecnologia que estamos utilizando, e com isso, sermos capazes de manter esses dados mesmo que o usuário esteja totalmente "desconectado" da aplicação, para que quando ele voltar ao servidor, os dados continuarão disponíveis, refazendo o estado da requisição antes dela ser enviada ao navegador.

As alternativas que temos depende da tecnologia que estamos utilizando. No ambiente Microsoft, a tecnologia para desenvolvimento de aplicações Web é o ASP.NET. Ele, por sua vez, traz uma porção de opções para utilizarmos, e atualmente temos: Application, Caching, Controles Hidden, ViewState, Cookie, Session, Profile, QueryString e Context.Items. Para você escolher o que melhor se encaixa a sua necessidade, é necessário avaliar detalhes como segurança e performance. São essas duas categorias que mais influenciam na escolha de qual opção utilizar para manter as informações.

Para tentar auxiliar na escolha de cada um, criei um fluxograma que tenta exibir a melhor opção de acordo com a necessidade, balanceando entre escopo, performance e segurança. Abaixo temos a imagem que representa esse fluxograma, e na sequência, uma descrição mais detalhada de cada condicional que vemos nele.

  • Compartilhar: O compartilhamento consiste em ter uma informação que pode estar acessível entre vários usuários que acessam a mesma aplicação. Isso quer dizer que qualquer alteração que um usuário faça, já refletirá para todos aqueles que a acessam.
  • Dependência ou Expiração: Algumas vezes, a informação que é compartilhada entre os usuários poderá ter critérios de invalidação. Se você tem algo na memória e quer determinar uma forma de removê-la de lá, você pode optar por dependência ou expiração. A primeira delas consiste em ser reativo, ou seja, seremos notificado quando alguma mudança ocorrer na origem da informação, enquanto a segunda opção, devemos especificar um valor para determinar quando ela deverá ser removida.
  • Acessível entre requisições (escopo de usuário): A ideia desta condição é determina se os dados precisam ou não sobreviver entre a navegação das páginas da aplicação.
  • Dados Complexos ou Sigilosos (escopo de usuário): Para as informações que devem sobreviver entre todas as páginas da aplicação, podemos dividir em dados complexos ou sigilosos. Informações simples, como por exemplo, o idioma selecionado pelo usuário, podemos recorrer aos Cookies para armazená-las. Já aqueles dados que vão muito além do que simples strings, como é o caso de um carrinho de compras, podemos recorrer a recursos mais poderosos, como Session ou Profile.
  • Persistência: Utilizar Session ou Profile dependerá, principalmente, se você quer ou não ter a capacidade de persistir as informações. A principal diferença entre armazenar o carrinho de compras na Session ou não Profile, é que na segunda opção, as informações sobreviverão entre reinicializações do navegador e do servidor, ao contrário do que acontece com a Session [1], que é descartada ao fechar o navegador.
  • Transferência: A transferência consiste em levar dados de uma página para a outra, que geralmente parametrizam as tarefas que esta segunda página irá desempenhar.
  • Transferência no Servidor: Caso você já esteja executando algum código no servidor, então você pode optar pelo Context.Items para enviar os dados para uma outra página, sem a necessidade de voltar ao navegador, e a partir dali, ir para a página solicitada. Utilizar o Context.Items garante que as informações sejam passadas de forma transparente, e o usuário ou até mesmo os interceptadores de requisições HTTP não conseguirão capturar esses dados. Agora, se tivermos a necessidade de parametrizar publicamente a chamada de uma página, então a QueryString é a melhor saída, mas apenas lembre-se de que ela possue limitação de tamanho e você não deve passar dados sensíveis ali.
  • Manter dados na própria página: Para casos onde precisamos manter dados para uso excluso da página que estamos utilizando, podemos recorrer aos controles Hidden [2]. Esse tipo de controle é capaz de guardar informações simples, e o seu uso serve apenas para manter informações que não sejam sensíveis, estando tão vulneráveis quanto as QueryStrings.

[1] - A configuração padrão da Session no ASP.NET faz com que as informações sejam armazenadas InProc, ou seja, na memória do próprio servidor Web. Você pode alterar esse comportamento, e eleger um servidor SQL Server para persistir essas informações. Mas para essa situação, o Profile pode ser melhor, já que possui várias outras características interessantes, como a possibilidade de tipar as informações que serão armazenadas, migração de usuários anônimos, etc.

[2] - No caso do ASP.NET Web Forms, o ViewState também pode ser utilizado para manter as informações em nível de página, mas que irá recorrer ao uso de controles Hidden para o armazenamento delas. É importante dizer que o ViewState é armazenado nestes controles de forma codificada, ou seja, é possível extrair o que armazenamos ali. Seguindo a mesma linha de alguns itens anteriores, você não deve colocar informações sigilosas dentro dele.

Todos as opções para armazenamento do estado são basicamente dicionários, ou seja, utilizam a chave como sendo uma string. Já o valor dependerá do que está utilizando, por exemplo, no caso de Caching, Application, Session, Profile ou Context.Items, você poderá armazenar qualquer objeto, desde que ele esteja decorado com o atributo SerializableAttribute. As outras opções apenas trabalham com simples strings como valor.

Tags:

ASP.NET

Acessando ações diretamente

by Israel Aece 29. June 2010 16:06

Em algumas situações dentro de uma aplicação ASP.NET MVC, há ações dentro dos controllers que não podem ser invocadas diretamente a partir da URL, ou seja, somente estarão acessíveis através de métodos que geram o resultado (HTML) "in-line", ou seja, em algum ponto da página. Um exemplo disso é o uso dos métodos Action e RenderAction.

Como exemplo, há métodos que recebem como parâmetro o nome de uma ação e, eventualmente, o nome do controller, e o resultado irá variar de acordo com o método que você utiliza. Um desses métodos, chamado de Action, retornará um objeto do tipo MvcHtmlString, que colocará o resultado diretamente no local especificado dentro do HTML da página; enquanto o método RenderAction adiciona o conteúdo diretamente no objeto HttpResponse, que em alguns cenários, pode ser mais performático.

Muitas vezes, estas ações devem ser acessadas somente através destes métodos, e com isso um usuário não deveria acessá-la diretamente através do navegador. Para evitar que isso aconteça, você pode recorrer a um novo atributo, incluído na versão 2.0 do ASP.NET MVC, chamado ChildActionOnlyAttribute. Este atributo pode ser aplicado tanto em classes quanto em métodos, e quando o runtime do ASP.NET encontrá-lo, irá negar o acesso à mesma caso esteja sendo acessada diretamente através navegador. Para exemplificar, considere o controller abaixo, que gerencia os usuários do sistema:

public class UsuariosController : Controller
{
    public ActionResult Cadastrar()
    {
        return View();
    }

    [ChildActionOnly]
    public ActionResult VerificarExistenciaDoUsuario(string nome)
    {
        return PartialView();
    }
}

Agora, no código HTML da página ASPX da ação Cadastrar, podemos utilizar um dos métodos que falamos acima, especificando o nome da ação marcada com o atributo tema deste artigo, e com isso, o resultado gerado será colocado no mesmo local onde as tags <% %> estão localizadas. Abaixo podemos visualizar a sua utilização:

<div>
    <% Html.RenderAction("VerificarExistenciaDoUsuario"); %>
</div>

Note que ao navegar pela aplicação através do endereço Usuarios/Cadastrar, visualizamos o conteúdo normalmente, mas se tentarmos acessar o endereço Usuarios/VerificarExistenciaDoUsuario, a seguinte mensagem é exibida: The action 'VerificarExistenciaDoUsuario' is accessible only by child request. Mensagem essa, que não aconteceria se você omitir o atributo ChildActionOnlyAttribute.

Além da forma declarativa que vimos acima, você pode controlar se a chamada está ou não sendo realizada diretamente através do navegador. Para isso, basta recorrermos à propriedade IsChildAction da classe ControllerContext, que retorna um valor boleano indicando se ela está ou não sendo acessada diretamente. Agora, podemos tomar alguma decisão em cima desta propriedade, algo que é impossível de realizar quando utilizamos o modo declarativo.

Tags:

ASP.NET

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

Consumindo serviços WCF com jQuery

by Israel Aece 4. May 2010 12:41

O jQuery é uma biblioteca com várias funcionalidades que tornam o desenvolvimento de código JavaScript muito mais simples. Tarefas que demandavam várias linhas de código para chegar a um determinado resultado, atualmente com o jQuery o código necessário para atingir esse mesmo objetivo é muito menos complexo. Inclusive a Microsoft reconheceu a facilidade e a larga adoção pelos desenvolvedores, e decidiu incorporá-lo às templates de projeto do ASP.NET.

Entre as mais variadas funcionalidades que o jQuery oferece, uma delas é o suporte ao AJAX, que nos permite invocar métodos que estão do lado do servidor. Mas além disso, uma das possibilidades que temos é a requisição para serviços, incluindo serviços construídos com WCF. A finalidade deste artigo é mostrar como proceder para criar serviços WCF que possam ser consumidos pelo AJAX, e depois disso, vamos analisar o que o jQuery oferece para o consumo do mesmo.

Primeiramente vamos nos atentar a construção de um serviço para que possamos consumí-lo através do AJAX/jQuery. O que precisamos nos atentar é que para expor esse serviço para clientes AJAX, é necessário decorarmos as operações não somente com o atributo OperationContractAttribute, mas também com os atributos WebGetAttribute ou WebInvokeAttribute, que são disponibilizados a partir da versão 3.5 do .NET Framework, e que estão contidos debaixo do namespace System.ServiceModel.Web (assembly System.ServiceModel.Web.dll).

Como exemplo, o contrato irá manipular instância da classe Usuario, que nada mais é do que um objeto simples, que possui três propriedades autoexplicativas: Codigo, Nome e Email. O contrato fornecerá três operações: RecuperarUsuario, que retorna a instância da classe Usuario devidamente configurada com os parâmetros de entrada, uma outra operação chamada RecuperarUsuarios que retorna uma coleção de usuários e, finalmente, a operação Adicionar, que dado a instância do usuário, irá adicioná-lo em algum repositório. Abaixo podemos visualizar a interface que servirá como o contrato para o serviço:

[ServiceContract]
public interface IUsuarios
{
    [OperationContract]
    [WebInvoke(
        BodyStyle = WebMessageBodyStyle.Wrapped,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    Usuario RecuperarUsuario(string nome, string email);

    [OperationContract]
    [WebGet(
        BodyStyle = WebMessageBodyStyle.Bare,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    Usuario[] RecuperarUsuarios();

    [OperationContract]
    [WebInvoke(
        BodyStyle = WebMessageBodyStyle.Bare,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    void Adicionar(Usuario usuario);
}

Note que as operações que serão expostas também determinam algumas configurações específicas para quando forem consumidas a partir do AJAX. É importante perceber também que em todas elas, estamos optando por trabalhar com JSON ao invés do XML para formular a mensagem de requisição e de resposta, já que o JSON é mais simples de se trabalhar com Javascript. A propriedade BodyStyle controla como o corpo da requisição/resposta deve ser formatado, determinando se os parâmetros ou o resultado dos métodos devem ou não estar dentro de elementos adicionais. Para maiores detalhes sobre esses parâmetros, consulte este artigo.

Ao interceptar a requisição/resposta que é feita para a primeira operação, então teremos as informações em JSON sendo trafegadas, e as duas pontas (WCF e o jQuery) se encarregam de fazer toda a tradução necessária, aliviando assim nosso trabalho. Antes de analisarmos o código necessário para consumir serviços WCF no jQuery, vamos analisar o conteúdo que está sendo trafegado para as mensagens (requisição e resposta respectivamente) do contrato que criamos acima:

[ Operação RecuperarUsuario ]

POST http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario HTTP/1.1
Content-Type: application/json
-- OUTROS PARAMETROS OMITIDOS --

{ "nome": "Israel Aece", "email": "ia@israelaece.com" }

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
-- OUTROS PARAMETROS OMITIDOS --

{"RecuperarUsuarioResult":{"Codigo":123,"Email":"ia@israelaece.com","Nome":"Israel Aece"}}

[ Operação RecuperarUsuarios ]

GET http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuarios HTTP/1.1
-- OUTROS PARAMETROS OMITIDOS --

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
-- OUTROS PARAMETROS OMITIDOS --

[{"Codigo":1,"Email":"usuario1@servidor.com.br","Nome":"Nome do Usuario 1"},{"Codigo":2,"Email":"usuario2@servidor.com.br","Nome":"Nome do Usuario 2"},{"Codigo":3,"Email":"usuario3@servidor.com.br","Nome":"Nome do Usuario 3"},{"Codigo":4,"Email":"usuario4@servidor.com.br","Nome":"Nome do Usuario 4"}]

[ Operação Adicionar ]

POST http://localhost/Services/ServicoDeUsuarios.svc/Adicionar HTTP/1.1
Content-Type: application/json
-- OUTROS PARAMETROS OMITIDOS --

{"Codigo":123,"Nome":"Israel","Email":"ia@israelaece.com"}

Note que a URL de requisição também contempla o método a ser disparado do lado do serviço. Os parâmetros de entrada são serializados em JSON, e o resultado idem. Na operação RecuperarUsuario, configuramos ela para que o resultado seja Wrapped, e com isso, o corpo da mensagem de retorno veio envolvida em um objeto com o nome de RecuperarUsuarioResult, ao contrário daquelas definidas como Bare, onde a requisição/resposta não estão envolvidas neste elemento adicional.

Além dos detalhes que são necessários em nível de contrato, precisamos nos atentar em como hospedar esse serviço. Você precisa configurar o endpoint do WCF para utilizar um binding exclusivo para este cenário, que é o WebHttpBinding. Se você estiver hospedando no IIS (como é o caso do exemplo acima), então provavelmente haverá um arquivo com extensão *.svc que representa o serviço. É dentro deste arquivo que precisamos trocar o "fábrica" de hosts, que deve apontar para o WebServiceHostFactory, assim como já mostrei neste artigo.

Depois de todo o serviço WCF já devidamente criado e rodando, precisamos entender a API do jQuery para consumir esse serviço de exemplo. Nos dois tipos de projetos ASP.NET (WebForms e MVC), ambos já trazem o JQuery referenciado, o que nos permite acessar o que ele fornece para consumir serviços, escritos em qualquer tecnologia. Sendo assim, o primeiro passo importante para acessar qualquer um dos recursos fornecidos pelo jQuery, devemos adicionar uma referência para o arquivo *.js correspondente ao mesmo, e para isso, podemos utilizar o seguinte elemento:

<script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>

Depois deste arquivo referenciado, então já podemos utilizar a API do jQuery para consumir serviços WCF. Vamos utilizar neste exemplo a função $.ajax, que é uma função de baixo nível do jQuery, e que nos permite customizar, através de uma série de parâmetros, as configurações para controlar como a mensagem será enviada e como a resposta será processada.

Entre os vários parâmetros, temos o type, que determina com a requisição será enviada ao serviço, podendo ser através de GET ou POST. A propriedade url determina o endereço até o serviço, incluindo o nome da operação a ser invocada. contentType é onde definimos como o corpo da mensagem está formatada, e apesar do jQuery utilizar um tipo genérico, que pode ser aplicado em grande parte dos casos, é sempre melhor deixar isso explícito com o tipo que você está operando. A propriedade data determina o que vai ser enviado para a operação. Essa propriedade trabalha em conjunto com uma outra propriedade chamada processData. Quando você define os dados a serem enviados para o serviço através do método GET, então os dados serão convertidos em query strings, e a propriedade processData previne esse comportamento.

Além dessas propriedades, temos algumas opções para a configuração de callbacks para alguns cenários. beforeSend permite efetuar algum processamento momentos antes da requisição ser enviada ao serviço. A opção success permite especificarmos um callback apontando para um método que deverá ser disparado quando o resultado voltar com sucesso. Já error também permite especificarmos um método a ser disparado quando algum problema acontecer no serviço. É importante dizer aqui que o tratamento de erro não será tão simples, pois o WCF não traduz automaticamente a exceção em um erro no formato JSON, o que dificulta para mostrar a mensagem de erro. Finalmente, temos um callback chamado de complete, que como o próprio nome diz, é disparado quando a requisição é finalizada, independemtente se ocorreu ou não alguma exceção durante o seu processamento.

Abaixo temos como podemos configurar a função $.ajax para executarmos a primeira operação fornecida pelo serviço WCF que criamos anteriormente, que é a RecuperarUsuario. Como o acesso deve ser feito através do método POST, então os parâmetros serão enviados no corpo da mensagem, em formato JSON. Do lado do serviço, o WCF consegue interpretar o objeto JSON que foi enviado, extrair cada propriedade e preencher cada um dos parâmetros exigidos pela operação.

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

É importante perceber que configuramos (no contrato) o corpo da mensagem para o método RecuperarUsuario como Wrapped, e que isso nos obrigará a acessar o resultado através do objeto que envolve as propriedades do usuário, e que é criada automaticamente pelo JSON, que neste caso é chamada de RecuperarUsuarioResult.

Já a segunda operação exposta pelo serviço WCF, que retorna uma coleção de usuários, vamos acessá-la através do método GET, e ao receber o resultado, vamos iterar pela coleção, acessando elemento a elemento mostrando as propriedades Nome e Email de cada usuário retornado pelo serviço. É importante perceber aqui que estamos acessando diretamente as propriedades, sem passar por aquele elemento que é gerado pelo JSON. Isso se deve ao fato de termos configurado a respectiva operação como Bare, que evita envolver o resultado neste membro extra.

function RecuperarUsuarios() {
    $.ajax(
    {
        type: "GET",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuarios",
        success:
            function (usuarios) {
                $.each(usuarios, function (indice, usuario) {
                    alert(usuario.Nome + ": " + usuario.Email);
                })
            }
        });
    }

Finalmente temos o método AdicionarUsuario, que recebe como parâmetro a instância da classe Usuario e o adiciona em algum repositório. O usuário é criado utilizando a sintaxe JSON, onde configuramos cada propriedade e seu respectivo valor através de uma espécie de dicionário. Só que o jQuery não traz nativamente funções para representar o objeto em formato de string, algo que é necessário para enviá-lo até o serviço. Ao invés de fazer todo o trabalho árduo para essa transformação, podemos recorrer à uma biblioteca fornecida através do site oficial do JSON, chamada de JSON2.js. Essa biblioteca fornece dois métodos para a manipulação do JSON, sendo eles: parse e stringify. O método parse retorna o objeto devidamente reconstruído a partir de uma estrutura JSON, enquanto o método stringify retorna uma string contendo representação JSON de um determinado objeto. É justamente o resultado deste segundo método que estamos enviando ao serviço:

function AdicionarUsuario() {
    var usuario = { "Codigo": 123, "Nome": "Israel", "Email": "ia@israelaece.com" };

    $.ajax(
    {
        type: "POST",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/Adicionar",
        contentType: "application/json",
        data: JSON.stringify(usuario),
        processData: false,
        success:
            function (resultado) {
                alert('Usuário adicionado com sucesso.');
            }
        });
    }

Apenas atente-se que para este código funcionar, precisamos fazer o download do arquivo json2.js e referenciá-lo na página, como vemos abaixo:

<script src="Scripts/json2.js" type="text/javascript"></script>

Conclusão: Consumir serviços a partir de AJAX pode tornar a experiência do usuário muito melhor, já que evita a necessidade de ter efetuar a atualização completa da página. Isso já era uma necessidade, mas o jQuery torna isso muito mais simples, onde mesmo utilizando funções de baixo nível como vimos aqui, a tarefa acaba sendo muito mais simples de se realizar.

Tags: , ,

ASP.NET | WCF

Web.config e a reinicialização da aplicação

by Israel Aece 12. April 2010 10:29

Recentemente fui questionado sobre a possibilidade de alteração de um arquivo de configuração para a adição de uma nova string de conexão com uma determinada base de dados. Como sabemos, podemos utilizar a seção <connectionStrings /> para isso, onde elencamos todas as conexões que possam ser utilizadas pela aplicação.

O grande problema disso, é que ao modificar o arquivo Web.config, a aplicação é reinicializada, e com isso todas as informações que são armazenadas na memória são perdidas. Isso quer dizer que membros estáticos, variáveis de aplicação, de sessão e caching são descartadas, causando problemas dentro da aplicação, e trazendo péssimas experiências aos usuários.

Para contornar esse problema, podemos recorrer à uma técnica que nos permite isolar as configurações de uma seção inteira em um outro arquivo de configuração. No nosso exemplo, iremos criar um arquivo chamado connectionStrings.config contendo apenas a seção <connectionStrings />, assim como poder notar abaixo:

<connectionStrings>
  <add name="SqlConnectionString" connectionString="..."/>
  <add name="OracleConnectionString" connectionString="..."/>
</connectionStrings>

Já no arquivo de configuração da aplicação, Web.config, referenciamos este arquivo recém criado, e para isso utilizamos o atributo configSource, apontando fisicamente para ele:

<connectionStrings configSource="connectionStrings.config" />

Só que isso somente não resolverá. Como estamos querendo evitar a reinicialização caso alguma alteração na seção <connectionStrings /> aconteça, precisamos definir o atributo restartOnExternalChanges como False, que por padrão é True. Quando é definido como False e temos essa seção em um segundo arquivo, a aplicação não será reinicializada. Essa configuração deve ser aplicada durante a criação da seção, no arquivo machine.config que está localizado dentro do diretório de instalação do .NET Framework. Abaixo temos a seção já configurada com este atributo:

<section
    name="connectionStrings"
    type="System.Configuration.ConnectionStringsSection, ..."
    restartOnExternalChanges="false"
    requirePermission="false" />

Com isso, como já era de se esperar, qualquer mudança que ocorra no arquivo connectionStrings.config não provocará a reinicialização da aplicação, mas as alterações já serão detectadas por ela. O único cuidado que você precisa ter aqui é quando há alguma espécie de caching sendo utilizada pela aplicação ao pelo runtime do ASP.NET, pois as mudanças não irão refletir para a mesma até que ela seja reinicializada. Essa técnica também é válida para seções customizadas.

Tags:

.NET Framework | ASP.NET

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