Este artigo foi traduzido por máquina.

Recortar borda

Injeção de políticas no Unity

Dino Esposito

No os dois artigos anteriores , apresentei a programação orientada a aspecto (AOP), usando o Microsoft Unity 2. 0. Formalizada na década de 1990 como forma de complementar e aprimorar ainda mais orientada a objeto (OOP) de programação, AOP recentemente foi renovado e muitas bibliotecas de inversão de controle (IoC) oferecem suporte a ele. Unity não é exceção. O principal objetivo da AOP é permitir que os desenvolvedores que mais lidam eficazmente com transversais preocupações. Em essência, AOP lida com a seguinte pergunta: quando você cria um modelo de objeto para um aplicativo, como você lida com os aspectos do código, como segurança, armazenamento em cache ou efetuando? Esses aspectos são fundamentais para a implementação, mas não estritamente pertencem aos objetos no modelo que você está criando. Você deve estragam o seu design para incorporar aspectos não-comerciais? Ou você melhor decorando suas classes orientada a negócios com aspectos adicionais? Se você escolher a última opção, AOP basicamente fornece uma sintaxe para definir e anexar esses aspectos.

Um aspecto é a implementação de uma preocupação de crosscutting. A definição de um aspecto, você precisará especificar algumas coisas. Primeiro, você precisará fornecer o código para a preocupação de que implementar. No jargão da AOP, isso é conhecido como o conselho. Um conselho é aplicado a um ponto específico de código — se o corpo de um método, o setter/getter de uma propriedade ou talvez um manipulador de exceção. Isso é conhecido como o ponto de junção. Por fim, no jargão da AOP, encontrar pointcuts. Um pointcut representa uma coleção de pontos de junção. Geralmente, os pointcuts são definidas pelos critérios usando caracteres curinga e nomes de método. AOP, por fim, atua em tempo de execução para injetar o código dos conselhos antes, depois e em torno do ponto de junção. Um conselho é associado um pointcut.

Nos artigos anteriores, explorei a API de Unity interceptação. A API permite que você defina advices para anexar a classes. No jargão Unity, o conselho é um objeto de comportamento. Normalmente você anexa o comportamento para um tipo que é resolvido por meio do mecanismo de IoC de Unity, mesmo que o mecanismo de interceptação não requerem estritamente a funcionalidade de IoC. Na verdade, você pode configurar interceptação aplicar também às instâncias criadas por meio de código simples.

Um comportamento consiste em uma classe que implementa uma interface fixa — a interface IInterceptionBehavior. A interface possui um método chamado Invoke. Substituindo esse método, você realmente define as etapas que você deseja executar antes ou após a chamada do método regular, ou ambos. Você pode anexar um comportamento a um tipo usando código fluent, bem como um script de configuração. Dessa forma, tudo o que precisa fazer é definir um ponto de junção. Mas e quanto pointcuts?

Como vimos no mês passado, todos os métodos interceptados no objeto de destino serão executado de acordo com para a lógica expressa no método Invoke do objeto de comportamento. A API de interceptação básica não fornece a capacidade de distinguir entre métodos e não oferece suporte a regras específicas de correspondência. Para obter isso, você pode recorrer para a API de injeção de diretiva.

PIAB e injeção de diretiva

Se você usou versões do Microsoft Enterprise Library (EntLib) antes para a versão mais recente, 5. 0, você pode ter ouvido falar sobre política injeção Application Block (PIAB) e as chances são que você também aproveitá-lo em alguns de seus aplicativos. EntLib 5. 0 também apresenta um módulo do PIAB. O que é a diferença entre o PIAB EntLib e injeção de diretiva Unity?

No EntLib 5. 0, o PIAB existe principalmente por motivos de compatibilidade. O conteúdo do assembly PIAB alterada na nova versão. Em particular, todas as máquinas para interceptação agora faz parte do Unity e todos os manipuladores de chamada fornecido pelo sistema em versões anteriores do EntLib foram movidos para outros assemblies, como mostrado na a Figura 1 .

Figura 1 refatoração de manipuladores de chamada na biblioteca do Microsoft Enterprise 5. 0

Chamar o manipulador Novo Assembly na Enterprise Library 5. 0
Manipulador de autorização Microsoft.Practices.EnterpriseLibrary.Security.dll
Manipulador de manipulação de cache Removido do PIAB
Manipulador de manipulação de exceção Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
Manipulador de log Microsoft.Practices.EnterpriseLibrary.Logging.dll
Manipulador de contador de desempenho Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll
Manipulador de validação Microsoft.Practices.EnterpriseLibrary.Validation.dll

Como você pode ver na a Figura 1 , cada manipulador de chamada foi movido para o assembly do bloco de aplicativo associado. Portanto, o manipulador de manipulação de exceção chamada movido para o tratamento de exceção application block e o manipulador de validação são movidos para o bloco de aplicativo de validação e assim por diante. A única exceção a essa regra foi o manipulador de contador de desempenho, movida para o assembly PolicyInjection. Embora os assemblies alterado, o namespace das classes permaneceu da mesma. Também vale a pena observar que, devido a preocupações com segurança, o manipulador chamada cache anteriormente incluído no PIAB foi removido do EntLib 5. 0 e disponibilizado somente por meio do site EntLib Contribuidor do CodePlex, em bit.ly/gIcP6H . O resultado dessas alterações é que PIAB é agora disponibilizado de componentes legados apenas para compatibilidade com versões anteriores que ainda exigem algumas alterações de código para compilar com a versão 5. 0. A menos que tenha dependências herdadas sérias, a abordagem recomendada é que você atualizar seu injeção de diretiva de camadas para aproveitar a API de injeção de diretiva novo (e amplamente semelhante) baked no Unity application block. Vamos descobrir mais sobre injeção de diretiva no Unity.

Injeção de diretiva em um piscar de olhos

Injeção de diretiva é uma camada de código que se estende a interceptação de Unity API básica para adicionar regras de mapeamento e chamar manipuladores em uma base por método. Implementado como um comportamento especial de interceptação, injeção de diretiva consiste em duas fases principais: inicialização e o tempo de execução.

Durante a fase de inicialização, a estrutura primeiro determina qual das diretivas disponíveis podem ser aplicadas para o método de destino sejam interceptado. Nesse contexto, uma diretiva é descrita como um conjunto de operações que podem ser injetado em uma ordem específica entre o objeto sendo interceptados e seu chamador real. Você só pode interceptar os métodos em objetos (instâncias existentes ou instâncias recém-criadas) que foram configurados explicitamente para injeção de diretiva.

Ter descoberto a lista de diretivas aplicáveis, a estrutura de injeção de diretiva prepara o pipeline de operações (uma operação é conhecida como um manipulador de chamada). O pipeline resulta da combinação de todos os manipuladores definidos para cada uma das diretivas correspondentes. Os manipuladores no pipeline são classificados com base na ordem de prioridade atribuída a cada manipulador na diretiva do pai e a diretiva. Quando um diretiva ativada método é invocado, o pipeline previamente criado é processado. Se o método, por sua vez, coloca a chamadas para outros métodos de diretiva ativada no mesmo objeto, as tubulações do manipulador desses métodos são mescladas no pipeline principal.

Chamar manipuladores

Um manipulador de chamada é mais específico do que um comportamento de"" e realmente se parece com um aviso, como foi originalmente definido AOP. Enquanto um comportamento aplica-se a um tipo e deixa a você a responsabilidade de executar ações diferentes para diferentes métodos, um manipulador de chamada é especificado em uma base por método.

Manipuladores de chamada são compostos em um pipeline e invocados em uma ordem predeterminada. Cada manipulador é capaz de acessar os detalhes da chamada, incluindo o nome do método, parâmetros, valores de retorno e tipo de retorno esperado. Um manipulador de chamada pode também modificar parâmetros e retornar valores, parar a propagação da chamada para o pipeline e elevar uma exceção.

É interessante observar que Unity não vem com quaisquer manipuladores de chamada. Somente você pode criar seu próprio, ou blocos de aplicativos de referência do EntLib 5. 0 e usar qualquer um dos manipuladores de chamada listados na a Figura 1 .

Um manipulador de chamada é uma classe que implementa a interface ICallHandler, como este:

public interface ICallHandler
{
  IMethodReturn Invoke(  
    IMethodInvocation input,     
    GetNextHandlerDelegate getNext);
  int Order { get; set; }
}

A propriedade Order indica a prioridade desse manipulador relacionada a todos os outros. O método Invoke retorna uma instância de uma classe que contém qualquer valor de retorno do método.

A implementação de um manipulador de chamada é muito simple no sentido de que ele tem apenas esperado para executar operações específicas em seus próprio e deixar que o pipeline de ir. Para gerar o controle para o próximo manipulador no pipeline, o manipulador chama o parâmetro getNext, que ele recebe de runtime Unity. O parâmetro getNext é um delegado definido como:

public delegate InvokeHandlerDelegate GetNextHandlerDelegate();

Por sua vez, o InvokeHandlerDelegate é definido como:

public delegate IMethodReturn InvokeHandlerDelegate(     
  IMethodInvocation input,           
  GetNextHandlerDelegate getNext);

A documentação de Unity fornece um claro diagrama que ilustra a interceptação. Em a Figura 2 , você vê um diagrama ligeiramente modificado que apresenta a arquitetura de injeção de diretiva.

Figura 2 O Pipeline de manipulador de chamada na injeção de diretiva Unity

Dentro dos limites de um comportamento de injeção de diretiva fornecido pelo sistema, você pode ver a cadeia de manipuladores para processar um determinado método invocado em um objeto de proxy ou classe derivada. Para concluir a visão geral de injeção de diretiva no Unity, precisamos dar uma olhada em regras de correspondência.

Regras de correspondência

Por meio de uma regra de correspondência, você pode especificar onde aplicar sua lógica de interceptação. Se você usar comportamentos, seu código se aplica ao objeto inteiro;com uma ou mais regras de correspondência, você pode definir um filtro. Uma regra de correspondência indica um critério para selecionar objetos e membros ao qual o Unity irá anexar um manipulador de pipeline. Usando a terminologia AOP, uma regra de correspondência é o critério que você usa para definir o pointcuts. Figura 3 lista as regras correspondentes suportadas nativamente pelo Unity.

Figura 3 lista de regras de correspondência com suporte no Unity 2. 0

Regra de correspondência Descrição
AssemblyMatchingRule Seleciona destino objetos com base em tipos no assembly especificado.
CustomAttributeMatchingRule Seleciona objetos com base em um atributo personalizado no nível do membro de destino.
MemberNameMatchingRule Seleciona objetos com base no nome do membro de destino.
MethodSignatureMatchingRule Seleciona objetos com base em assinatura de the de destino.
NamespaceMatchingRule Seleciona objetos com base em the namespace de destino.
ParameterTypeMatchingRule Seleciona objetos com base no nome do tipo de um parâmetro para um membro de destino.
PropertyMatchingRule Seleciona destino objetos com base no membro names, incluindo caracteres curinga.
ReturnTypeMatchingRule Seleciona objetos com base no tipo de retorno de destino.
TagMatchingRule Seleciona objetos com base no valor atribuído a um atributo Tag ad hoc de destino.
TypeMatchingRule Seleciona objetos com base no nome do tipo de destino.

Uma regra de correspondência é uma classe que implementa a interface IMatchingRule. Armado com esse conhecimento, vamos ver como trabalhar com injeção de diretiva. Há basicamente três maneiras em que você pode definir diretivas: usando atributos, usando código fluente e por meio da configuração.

Adicionando políticas por meio de atributos

Figura 4 mostra um manipulador de chamada de exemplo lança uma exceção se o resultado de uma operação é negativo. Utilizarei esse manipulador mesmo em vários cenários.

Figura 4 A classe NonNegativeCallHandler

public class NonNegativeCallHandler : ICallHandler
{
  public IMethodReturn Invoke(IMethodInvocation input,
                              GetNextHandlerDelegate getNext)
  {
    // Perform the operation
    var methodReturn = getNext().Invoke(input, getNext);
    // Method failed, go ahead
    if (methodReturn.Exception != null)
    return methodReturn;
    // If the result is negative, then throw an exception
    var result = (Int32) methodReturn.ReturnValue;
    if (result <0)
    {
      var exception = new ArgumentException("...");
      var response = input.CreateExceptionMethodReturn(exception);
      // Return exception instead of original return value
      return response;
    }
    return methodReturn;
  }
  public int Order { get; set; }
}

A maneira mais simples usar o manipulador é anexá-los a qualquer método no qual você acha que pode ser útil. Para isso, é necessário um atributo, como:

public class NonNegativeCallHandlerAttribute : HandlerAttribute
{
  public override ICallHandler CreateHandler(  
    IUnityContainer container)
  {
    return new NonNegativeCallHandler();
  }
}

Esta é uma classe de Calculadora de amostra que você decora com diretivas baseadas no atributo:

public class Calculator : ICalculator 
{
  public Int32 Sum(Int32 x, Int32 y)
  {
    return x + y;
  }

  [NonNegativeCallHandler]
  public Int32 Sub(Int32 x, Int32 y)
  {
    return x - y;
  }
}

O resultado é que chama o método Sum continue como de costume, independentemente do valor retornado, enquanto as chamadas ao método Sub lançará uma exceção se um número negativo é retornado.

Usando código Fluent

Se você não gostar atributos, você pode expressar a mesma lógica através de uma API fluent. Nesse caso, você deve fornecer muitos detalhes mais que diz respeito a regras de correspondência. Vamos ver como expressar a idéia de que queremos injetar código apenas em métodos que retornam Int32 e são nomeados Sub. Use a API fluente para configurar o recipiente Unity (consulte a Figura 5 ).

Figura 5 código fluente para definir um conjunto de regras de correspondência

public static UnityContainer Initialize()
{ // Creating the container
  var container = new UnityContainer();
  container.AddNewExtension<Interception>();

  // Adding type mappings
  container.RegisterType<ICalculator, Calculator>(
    new InterceptionBehavior<PolicyInjectionBehavior>(),
    new Interceptor<TransparentProxyInterceptor>());

  // Policy injection
  container.Configure<Interception>()
    .AddPolicy("non-negative")
    .AddMatchingRule<TypeMatchingRule>(
      new InjectionConstructor(
        new InjectionParameter(typeof(ICalculator))))
    .AddMatchingRule<MemberNameMatchingRule>(
      new InjectionConstructor(
        new InjectionParameter(new[] {"Sub", "Test"})))
    .AddMatchingRule<ReturnTypeMatchingRule>(
      new InjectionConstructor(
        new InjectionParameter(typeof(Int32))))
    .AddCallHandler<NonNegativeCallHandler>(
      new ContainerControlledLifetimeManager(),
        new InjectionConstructor());

  return container;
}

Observe que se você usar o Gerenciador de ContainerControlledLifetimeManager, garantimos que a mesma instância de manipulador de chamada é compartilhada por todos os métodos.

O efeito do código é que qualquer tipo concreto que implementa o ICalculator (isto é, está configurado para ser interceptadas e é resolvido por meio de Unity) irá selecionar dois possíveis candidatos para injeção: métodos Sub e teste. No entanto, somente os métodos com um tipo de retorno de Int32 sobreviverão a regra de correspondência ainda mais. Isso significa que, por exemplo, teste irá descartar se ele acontece retornar um valor duplo.

Adicionando políticas por meio da configuração

Finalmente, o mesmo conceito pode ser expresso usando o arquivo de configuração. Figura 6 mostra o conteúdo esperado <unity>seção.

Figura 6 Preparando injeção de diretiva no arquivo de configuração

public class NonNegativeCallHandler : ICallHandler
{
  public IMethodReturn Invoke(IMethodInvocation input, 
                              GetNextHandlerDelegate getNext)
   {
     // Perform the operation  
     var methodReturn = getNext().Invoke(input, getNext);

     // Method failed, go ahead
     if (methodReturn.Exception != null)
       return methodReturn;

     // If the result is negative, then throw an exception
     var result = (Int32) methodReturn.ReturnValue;
     if (result <0)
     {
       var exception = new ArgumentException("...");
       var response = input.CreateExceptionMethodReturn(exception);

       // Return exception instead of original return value
       return response;   
     }

     return methodReturn;
   }

    public int Order { get; set; }
}
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <assembly name="PolicyInjectionConfig"/>
  <namespace name="PolicyInjectionConfig.Calc"/>
  <namespace name="PolicyInjectionConfig.Handlers"/>

  <sectionExtension  ...
/>

  <container>
    <extension type="Interception" />

    <register type="ICalculator" mapTo="Calculator">
      <interceptor type="TransparentProxyInterceptor" />
      <interceptionBehavior type="PolicyInjectionBehavior" />
    </register>

    <interception>
      <policy name="non-negative">
        <matchingRule name="rule1" 
          type="TypeMatchingRule">
          <constructor>
             <param name="typeName" value="ICalculator" />
          </constructor>
        </matchingRule>
        <matchingRule name="rule2" 
          type="MemberNameMatchingRule">
          <constructor>
            <param name="namesToMatch">
              <array type="string[]">
                <value value="Sub" />
              </array>
            </param>
          </constructor>
        </matchingRule>
        <callHandler name="handler1" 
          type="NonNegativeCallHandler">
          <lifetime type="singleton" />
        </callHandler>                    
      </policy>
    </interception>
            
  </container>
</unity>

Como acontece quando você tem várias regras de correspondência em uma única política, o resultado final é o operador booleano e se aplica a todos eles (que significa que todos eles deve ser verdadeira). Se você definiu várias diretivas, cada um deles é avaliado para correspondência — e manipuladores aplicadas — independentemente. Portanto, você pode obter manipuladores aplicados a partir de políticas diferentes.

Interceptação de relance

Para recapitular, interceptação é a maneira na quais a maioria dos frameworks de IoC no Microsoft.NET Framework espaço implementar a orientação de aspecto. Por meio da interceptação, você está tem a chance de executar seu próprio código antes ou depois de qualquer determinado método em qualquer tipo de dado em qualquer assembly fornecido. EntLib no passado fornecido um bloco de aplicativo específico, o PIAB, para vencer esse desafio. No EntLib 5. 0, o mecanismo subjacente do PIAB foi movido para o Unity e implementado como um comportamento especial para a interceptação de baixo nível Unity API que abordei em minhas duas colunas anteriores. O comportamento de injeção de diretiva requer o uso de um recipiente Unity e não funcionará apenas por meio da API de interceptação de baixo nível.

A interceptação de baixo nível API, no entanto, não permite que você selecione os membros de tipo para interceptar;Você precisa escrever o código para fazer esse sozinho. Com o comportamento de injeção de diretiva, no entanto, você pode se concentrar nos detalhes do comportamento desejado e permitir que a biblioteca cuidam de descobrir quais métodos que ele se aplica com base nas regras que você dê a ele.

Dino Esposito é o autor de "Programming Microsoft ASP.NET MVC"(Microsoft Press, 2010) e co-autor de"Microsoft.NET: arquitetura de aplicativos corporativos "(Microsoft Press, 2008). Residente na Itália, Esposito é um palestrante sempre presente em eventos do setor no mundo inteiro. Consulte seu blog em weblogs.asp.net/despos.

Graças ao especialista técnico seguinte pela revisão deste artigo: Chris Tavares