Este artigo foi traduzido por máquina.

Cutting Edge

Filtros de ação dinâmicos no ASP.NET MVC

Dino Esposito

Dino EspositoNo mês passado, que abordei a função e a implementação de filtros ação em um aplicativo asp.net MVC. Para rever um pouco: Os filtros de ação são atributos que você pode usar para decorar o controlador de métodos e classes com a finalidade de ter que eles executam algumas ações opcionais. Por exemplo, você poderia escrever um atributo de compactação e tê-lo de forma transparente filtrar qualquer resposta gerada pelo método por meio de um fluxo compactado gzip. A principal vantagem é que a compactação de código permanece isolado em uma classe distinta e facilmente reutilizável, o que contribui para manter as responsabilidades do método mais baixo possível.

Atributos, no entanto, são uma coisa estática. Para aproveitar as vantagens de sua flexibilidade inerente, você precisa passar por uma etapa de compilação adicionais. É fácil alterar os aspectos adicionais de sua classe de controlador, mas que diz respeito ao custo de modificar o código-fonte. Em geral, isso não é uma grande desvantagem. Maior parte da manutenção do código de trabalho passa através de alterações físicas no código-fonte. Quanto mais você pode fazer essas alterações com eficiência e não há risco de introduzir regressão, melhor.

Para sites da Web (principalmente portais da Web) com os recursos e conteúdo altamente voláteis e software altamente personalizável, como aplicativos de serviço (SaaS), qualquer solução que evita que você tocar no código-fonte é mais bem-vindo. Portanto, a pergunta é, há algo que você possa fazer para carregar dinamicamente os filtros de ação? Como o restante deste artigo demonstra, a resposta é um ressonante Sim.

Explorando o asp.net MVC

O asp.net MVC framework expõe um número de interfaces e métodos substituíveis e você pode personalizar quase todos os aspectos do. Em resumo, toda a coleção de filtros ação de um método do controlador é carregada e mantida em uma lista na memória. Como desenvolvedor, você está tem permissão para acessar e inspecionar esta lista. Com alguns mais trabalho, você pode modificar a lista de filtros ação de e até preenchê-lo dinamicamente.

Let’s dê uma olhada mais de perto como isso funciona com uma visão geral das etapas realizadas pela estrutura para executar uma ação. Ao longo do processo, você irá atender o componente central que permite a manipulação de cuja filtros dinâmicos: o chamador de ação.

O chamador de ação será definitivamente responsável para a execução de quaisquer métodos de ação em uma classe de controlador. O chamador de ação implementa o ciclo de vida interno de cada solicitação asp.net MVC. O chamador é uma instância de uma classe que implementa a interface IActionInvoker. Cada classe de controlador possui seu próprio objeto chamador exposto ao mundo por meio de uma propriedade get/set simples chamado ActionInvoker. A propriedade é definida no tipo base System.Web.Mvc.Controller da seguinte maneira:

public IActionInvoker ActionInvoker {

  get {

    if (this._actionInvoker == null) {

      this._actionInvoker = this.CreateActionInvoker();

    }

    return this._actionInvoker;

  }

  set {

    this._actionInvoker = value;

  }

}

O método CreateActionInvoker é um método substituível protegido do tipo de controlador. Esta é sua implementação:

protected virtual IActionInvoker CreateActionInvoker() {

  // Creates an instance of the built-in invoker

  return new ControllerActionInvoker();

}

Acontece que o chamador de ação pode ser alterado à vontade para qualquer controlador. No entanto, porque o chamador está envolvido bastante um estágio inicial do ciclo de vida da solicitação, você provavelmente precisa um alocador de controlador para o seu próprio chamador para o chamador padrão do exchange. Juntamente com uma estrutura de inversão de controle (IoC) como Unity, essa abordagem seria permitem alterar a lógica de chamador diretamente a partir de configurações de contêiner IoC (off-line).

Como alternativa, você pode definir uma classe de base do controlador personalizado para seu próprio aplicativo e substituir o método CreateActionInvoker para torná-lo a retornar apenas o objeto chamador que é necessário. Essa é a abordagem que o asp.net MVC framework emprega para oferecer suporte a execução assíncrona de ações do controlador.

O chamador de ação é criado em torno da interface de IActionInvoker, que é bastante simples, pois ele expõe apenas um método:

public interface IActionInvoker {

  bool InvokeAction(

    ControllerContext controllerContext, 

    String actionName);

}

Let’s mantendo um olho aberto no chamador da ação padrão, revise as principais tarefas para o qual um chamador de ação é responsável. O chamador primeiro obtém informações sobre o controlador de solicitação e a ação específica para executar. Informações vem por meio de um objeto descritor de ad hoc. O descritor que inclui o nome e o tipo do controlador, além da lista de atributos e as ações. Por motivos de desempenho, o chamador cria seu próprio cache de descritores de ação e o controlador.

É interessante observar um rápido o protótipo da classe ControllerDescriptor do Figura 1. A classe representa a classe base para o descritor de real.

Figura 1 da Classe ControllerDescriptor

public abstract class ControllerDescriptor : 

  ICustomAttributeProvider {



  // Properties

  public virtual string ControllerName { get; }

  public abstract Type ControllerType { get; }



  // Method

  public abstract ActionDescriptor[] GetCanonicalActions();

  public virtual object[] GetCustomAttributes(bool inherit);

  public abstract ActionDescriptor FindAction(

    ControllerContext controllerContext, 

    string actionName);

  public virtual object[] GetCustomAttributes(

    Type attributeType, bool inherit);

  public virtual bool IsDefined(

    Type attributeType, bool inherit);

}

O asp.net MVC framework utiliza duas classes de descritor de concreto intensamente usam a reflexão do Microsoft .NET Framework internamente. Uma é nomeada ReflectedControllerDescriptor;a outra é usada somente para controladores assíncronos e é denominada ReflectedAsyncControllerDescriptor.

Eu dificilmente imaginar um cenário realista onde talvez você precise criar seu próprio descritor. No entanto, para aqueles que estão curiosos, let’s dê uma olhada em como é feito.

Imagine que você crie uma classe derivada do descritor e substituir o método GetCanonicalActions para ler a lista de ações com suporte de um arquivo de configuração ou uma tabela de banco de dados. Dessa forma, você pode remover métodos de ação válido da lista com base em algum conteúdo configurável. Para fazer esse trabalho, no entanto, você precisa colocar em seu próprio chamador de ação e escrever seu método GetControllerDescriptor da mesma forma, para retornar uma instância do descritor de personalizado:

protected virtual ControllerDescriptor 

  GetControllerDescriptor(

  ControllerContext controllerContext);

Obter informações sobre o método do controlador e ação é apenas a primeira etapa realizada pelo chamador de ação. Em seguida e mais interessante é que para os fins deste artigo, o chamador de ação obtém a lista de filtros ação para o método que está sendo processado. Além disso, o chamador de ação, verifica as permissões de autorização do usuário valida a solicitação em relação aos dados postados potencialmente perigosos e, em seguida, chama o método.

Obtendo lista de filtros ação

Mesmo que o chamador de ação é identificado com a interface IActionInvoker, o asp.net MVC framework usa os serviços da classe interna ControllerActionInvoker. Esta classe oferece suporte a muitos métodos adicionais e recursos, incluindo os descritores de mencionada anteriormente e a ação de filtros.

A classe ControllerActionInvoker oferece dois pontos principais de intervenção para manipular a ação de filtros. Uma é o método GetFilters:

protected virtual ActionExecutedContext 

  InvokeActionMethodWithFilters(

  ControllerContext controllerContext, 

  IList<IActionFilter> filters, 

  ActionDescriptor actionDescriptor, 

  IDictionary<string, object> parameters);

O outro é o método InvokeActionMethodWithFilters:

protected virtual FilterInfo GetFilters(

  ControllerContext controllerContext, 

  ActionDescriptor actionDescriptor)

Os dois métodos são marcados como protegido e virtual, como você pode ver.

O chamador chama GetFilters quando ele precisa acessar a lista de filtros definidos para uma determinada ação. Como você pode imaginar, isso ocorre bem no início do ciclo de vida de uma solicitação e qualquer invocação do método InvokeActionMethodWithFilters anterior.

Observe que depois de chamar GetFilters, o chamador tem disponível toda a lista de filtros para cada categoria possível, incluindo filtros de exceção, o filtros de resultado, filtros de autorização e, claro, a ação de filtros. Considere a seguinte classe de controlador:

[HandleError]

public class HomeController : Controller {

  public ActionResult About() {

    return View();

  }

}

Toda classe é decorada com o atributo HandleError, que é um filtro de exceção, e nenhum outro atributo está visível.

Agora adicione um chamador personalizado, substitua o método GetFilters let’s e colocar um ponto de interrupção na linha final do código, da seguinte forma:

protected override FilterInfo GetFilters(

  ControllerContext controllerContext, 

  ActionDescriptor actionDescriptor) {



  var filters = base.GetFilters(

    controllerContext, actionDescriptor);

  return filters;

}

A Figura 2 mostra o conteúdo real dos filtros de variáveis.

image: Intercepting the Content of the Filters Collection

A Figura 2 de interceptando o conteúdo da coleção Filters

A classe FilterInfo — uma classe pública nas coleções de System.Web.Mvc—offers específicas de filtros para cada categoria:

public class FilterInfo {

  public IList<IActionFilter> ActionFilters { get; }

  public IList<IAuthorizationFilter> AuthorizationFilters { get; }

  public IList<IExceptionFilter> ExceptionFilters { get; }

  public IList<IResultFilter> ResultFilters { get; }

  ...
}

Como em do Figura 2, para a classe simples mostrada anteriormente você contar uma ação filtro, a autorização de um filtro, filtro de resultado de um e dois filtros de exceção. Quem definidos os filtros de ação, o resultado e a autorização? A classe de controlador é um filtro de ação. Na verdade, a classe Controller base implementa todas as interfaces relacionadas do filtro:

public abstract class Controller : 

    ControllerBase, IDisposable,

    IActionFilter, IAuthorizationFilter, 

    IExceptionFilter, IResultFilter {

    ...
}

A implementação base do GetFilters reflete os atributos da classe de controlador usando a reflexão do .NET Framework. Na implementação do método GetFilters, é possível adicionar filtros de tantas quantas desejar, lê-los a partir de qualquer tipo de local. Você só precisa de um trecho de código como este:

protected override FilterInfo GetFilters(

  ControllerContext controllerContext, 

  ActionDescriptor actionDescriptor) {



  var filters = base.GetFilters(

    controllerContext, actionDescriptor);



  // Load additional filters

  var extraFilters = ReadFiltersFromConfig();

  filters.Add(extraFilters);



  return filters;

}

Essa abordagem lhe dá maior flexibilidade e funciona para qualquer objetivo que deseja alcançar ou com qualquer tipo de filtro que você deseja adicionar.

Chamando uma ação

InvokeActionMethodWithFilters é invocado durante o processo que leva o desempenho do método de ação. Nesse caso, o método recebe a lista de filtros ação de levar em conta. No entanto, ainda é permitido adicionar filtros adicionais neste momento. A Figura 3 mostra um exemplo de implementação InvokeActionMethodWithFilters dinamicamente adiciona um filtro de ação para a compactação de saída. Do Figura 3 o código primeiro verifica se o método que está sendo chamado é particular e, em seguida, ele instancia e adiciona um novo filtro. Nem é preciso dizer que você pode determinar os filtros de carga de qualquer forma adequada para você, inclusive a ler a partir de um arquivo de configuração, um banco de dados ou qualquer outro. Ao substituir o método InvokeActionMethodWithFilters, tudo o que fazer é verificar se o método que está sendo executado, anexar outra ação filtros e chamar o método base para que o chamador pode proceder como de costume. Para recuperar informações sobre o método que está sendo executado, você deve recorrer ao contexto do controlador e o descritor de ação.

De Adicionar um filtro de ação antes de executar a ação, a Figura 3

protected override ActionExecutedContext 

  InvokeActionMethodWithFilters(

  ControllerContext controllerContext, 

  IList<IActionFilter> filters, 

  ActionDescriptor actionDescriptor, 

  IDictionary<String, Object> parameters) {



  if (

    actionDescriptor.ControllerDescriptor.ControllerName == "Home" 

    && actionDescriptor.ActionName == "About") {



    var compressFilter = new CompressAttribute();

    filters.Add(compressFilter);

  }



  return base.InvokeActionMethodWithFilters(

    controllerContext, 

    filters, actionDescriptor, parameters);

}

Portanto, você tem duas abordagens possíveis para adicionar filtros dinamicamente a uma instância do controlador: substituindo GetFilters e substituindo InvokeActionMethodWithFilters. No entanto, não há nenhuma diferença?

O ciclo de vida de ação

Passar por GetFilters ou InvokeActionMethodWithFilters é quase a mesma coisa. Existem algumas diferenças, mesmo que ele esteja sem grande problema. Para entender a diferença, let’s obter mais informações sobre as etapas adotadas por um chamador de ação padrão, quando se trata de um método de ação em execução. Figura 4 resume o ciclo de vida.

image: The Lifecycle of an Action Method

Figura 4 do ciclo de vida de um método de ação

Depois de obter descritores, o chamador obtém a lista de filtros e entra em fase de autorização. Neste momento, o chamador lida com os filtros de autorização que você se registrou. Se a autorização falhar, qualquer resultado de ação é executado completamente, ignorando os filtros.

Em seguida, o chamador verifica se a solicitação requer a validação de dados publicados e, em seguida, prossegue para o método de ação de carregamento de todos os filtros registrados atualmente em execução.

No final, se você pretende adicionar dinamicamente os filtros de autorização, em seguida, ele só funcionará se fazê-lo por meio do método GetFilters. Se seu objetivo é apenas adicionar ação de filtros, filtros de resultado ou filtros de exceção, usar qualquer um dos métodos apenas produz o mesmo resultado.

Filtros dinâmicos

Carregamento dinâmico de filtros é um recurso opcional que na maioria das vezes serve ao propósito de aplicativos com volatilidade uma recurso extremamente alto nível. Um filtro, especificamente uma ação filtrar, permite que os recursos de orientada a aspecto de uma classe de controlador do asp.net MVC, ele permite que os desenvolvedores de ativar e desativar o comportamento de maneira declarativa.

Quando você escreve o código-fonte de uma classe de controlador, você pode optar por adicionar os atributos de ação para a classe ou o nível de método. Quando você lê sobre filtros de ação de uma fonte de dados externos, como organizar as informações para que fique claro quais filtros se aplicam a quais métodos pode não ser óbvio. Em um cenário de banco de dados, você pode criar uma tabela em que você usar o nome do método e o controlador como a chave. Em um cenário de configuração, você provavelmente precisará trabalhar check-out de uma seção de configuração personalizado que fornece apenas as informações que necessárias. Em qualquer caso, o asp.net MVC framework é flexível o suficiente para permitir que você decida quais filtros serão aplicadas em um método por e até mesmo em uma base por chamada.

Dino Esposito  é autor de “ Programming ASP.NET MVC ” da Microsoft Press (2010). Residente na Itália, Esposito é um palestrante sempre presente em eventos do setor no mundo inteiro. Você pode associar o seu blog em /despos de .

Meus agradecimentos aos seguinte especialista técnico para revisar este artigo: Scott Hanselman