Partager via


Cet article a fait l'objet d'une traduction automatique.

À la pointe

Injection de stratégie dans Unity

Dino Esposito

Julie Lerman prennent en charge. Unity ne fait pas exception. L'objectif principal de l'AOP est pour permettre aux développeurs que plus traitent efficacement crosscutting préoccupations. Par essence, AOP pose la question suivante : lorsque vous concevez un modèle d'objet pour une application, comment traiter les aspects du code tel que la sécurité, la mise en cache ou l'enregistrement ? Ces aspects sont essentiels pour la mise en oeuvre, mais ils n'appartiennent pas strictement aux objets dans le modèle que vous créez. Doit altérer votre conception pour intégrer les aspects non commerciaux ? Ou vous est préférable de décoration de vos classes orientée métier avec des aspects supplémentaires ? Si vous choisissez cette dernière, AOP fournit essentiellement une syntaxe pour définir et de joindre ces aspects.

Un aspect est l'implémentation d'un problème de crosscutting. Dans la définition d'un aspect, vous devez spécifier les choses. Tout d'abord, vous devez fournir le code de la préoccupation que vous implémentez. Dans le jargon de l'AOP, il s'agit en tant que l'avis. Un avis est appliqué à un point spécifique du code — si le corps d'une méthode, l'accesseur Get/Set d'une propriété ou peut-être un gestionnaire d'exceptions. Il s'agit comme point de jointure. Enfin, dans le jargon de l'AOP, trouver pointcuts. Un pointcut représente une collection de points de jointure. En règle générale, pointcuts sont définies par les critères à l'aide de caractères génériques et les noms de méthode. AOP agit finalement dans le runtime injecte le code des conseils avant, après et autour du point de jointure. Un Conseil est ensuite associée à un pointcut.

Dans les précédents articles, j'ai exploré l'API de l'unité d'interception. L'API vous permet de définir des avis à joindre aux classes. Dans le jargon de l'unité, le Conseil est un objet de comportement. En règle générale, vous associez le comportement à un type qui est résolu par le biais du mécanisme IoC d'unité, même si le mécanisme d'interception ne nécessite pas la fonctionnalité IoC strictement. En fait, vous pouvez configurer l'interception s'appliquent également aux instances créées via le code brut.

Un comportement se compose d'une classe qui implémente une interface fixe — l'interface IInterceptionBehavior. L'interface comporte une méthode nommée Invoke. En substituant cette méthode, vous définissez en fait les étapes que vous souhaitez être exécutées avant ou après l'appel de méthode régulière, ou les deux. Vous pouvez attacher un comportement d'un type à l'aide de code fluent ainsi qu'un script de configuration. De cette façon, il vous suffit est de définir un point de jointure. Mais qu'en est-il pointcuts ?

Comme nous l'avons vu le mois dernier, toutes les méthodes interceptés sur l'objet cible seront exécute en fonction de la logique exprimée dans la méthode Invoke de l'objet de comportement. L'API de base interception ne vous offre la possibilité de faire la distinction entre les méthodes et ne prend pas en charge les règles de correspondance spécifiques. Pour obtenir ceci, vous pouvez recourir à l'API d'injection de stratégie.

PIAB et Injection de stratégie

Si vous avez utilisé des versions de Microsoft Enterprise Library (EntLib) antérieures à la version la plus récente, 5.0, vous avez peut-être entendu sur la stratégie d'Injection Application Block (PIAB) et il est probable que vous avez également tiré parti de celui-ci dans certains de vos applications. EntLib 5.0 comprend également un module PIAB. Qu'est la différence entre l'unité d'injection de stratégie et EntLib PIAB ?

Dans EntLib 5.0, PIAB existe principalement pour des raisons de compatibilité. Le contenu de l'assembly PIAB modifié dans la nouvelle version. En particulier, toutes les machines pour l'interception fait maintenant partie de l'unité et tous les gestionnaires d'appel système fourni dans les versions antérieures de EntLib ont été déplacés vers les autres assemblys, comme indiqué dans Figure 1 .

Figure 1 refactorisation des gestionnaires d'appel dans la bibliothèque Microsoft Enterprise 5.0

Appeler le Gestionnaire Nouvel Assembly dans Enterprise Library 5.0
Gestionnaire d'autorisation Microsoft.Practices.EnterpriseLibrary.Security.dll
Gestionnaire de la gestion de la mise en cache Retirez le PIAB
Gestionnaire de la gestion des exceptions Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
Gestionnaire de journalisation Microsoft.Practices.EnterpriseLibrary.Logging.dll
Gestionnaire de compteur de performance Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll
Gestionnaire de validation Microsoft.Practices.EnterpriseLibrary.validation.dll

Comme vous pouvez le voir dans Figure 1 , chaque gestionnaire d'appel a été déplacé vers l'assembly du bloc d'application associée. Par conséquent, le Gestionnaire d'appel de la gestion des exceptions déplacé vers le bloc d'application de gestion des exceptions et le Gestionnaire de validation pour le bloc d'application de la validation et ainsi de suite. La seule exception à cette règle a été le Gestionnaire de compteur de performance, déplacée vers l'assembly PolicyInjection. Bien que les assemblys modifiés, l'espace de noms des classes est resté exactement la même chose. Il est également important de noter que, pour des raisons de sécurité, le Gestionnaire d'appel de mise en cache précédemment inclus dans le PIAB a été supprimé de EntLib 5.0 et rendue disponible uniquement via le site Web de CodePlex cotisation EntLib à bit.ly/gIcP6H . L'effet net de ces modifications est que PIAB est désormais de composants hérités disponible uniquement pour la compatibilité descendante qui continuent de nécessiter certaines modifications de code à compiler avec la version 5.0. À moins d'avoir des dépendances de hérités graves, l'approche recommandée est que vous mise à niveau des couches d'injection de votre stratégie pour tirer parti de l'API d'injection de stratégie nouvelle (et largement similaires) incorporées dans le bloc d'application de l'unité. Nous allons découvrir d'injection de stratégie dans l'unité.

Injection de stratégie en un coup de œil

Injection de stratégie est une couche de code qui étend l'interception Unity base API pour ajouter des règles de mappage et appeler des gestionnaires sur une base par méthode. Implémenté comme un comportement spécial d'interception, injection de stratégie se compose de deux phases principales : l'initialisation et l'heure d'exécution.

Pendant la phase d'initialisation, l'infrastructure détermine d'abord parmi les stratégies disponibles peut être appliqué à la méthode cible sont interceptée. Dans ce contexte, une stratégie est décrite comme un ensemble d'opérations qui peuvent être injectés dans un ordre particulier entre l'objet en cours a intercepté et son appelant réel. Vous pouvez intercepter uniquement des méthodes sur des objets (instances existantes ou instances nouvellement créées) qui ont été configurés explicitement pour l'injection de stratégie.

Avoir compris la liste des stratégies applicables, l'infrastructure d'injection de stratégie prépare le pipeline d'opérations (une opération s'appelle un gestionnaire d'appel). Le pipeline résulte de la combinaison de tous les gestionnaires définis pour chacune des stratégies correspondantes. Les gestionnaires dans le pipeline sont triés en fonction de l'ordre de la stratégie et la priorité attribuée à chaque gestionnaire dans la stratégie du parent. Lorsqu'une méthode de stratégie est appelée, le pipeline précédemment généré est traité. Si la méthode, place à son tour, les appels à d'autres méthodes de la stratégie activée sur le même objet, les pipelines du Gestionnaire de ces méthodes sont fusionnés dans le tuyau principal.

Appeler des gestionnaires

Un gestionnaire d'appel est plus spécifique que d'un « comportement » et ressemble vraiment à un avis, tel qu'il a été initialement défini dans AOP. Tandis qu'un comportement s'applique à un type laisse vous le fardeau de prendre des mesures différentes pour différentes méthodes, un gestionnaire d'appel est spécifié sur une base par méthode.

Gestionnaires d'appel sont composés dans un pipeline et appelés dans un ordre prédéterminé. Chaque gestionnaire est en mesure d'accéder aux détails de l'appel, y compris le nom de méthode, paramètres, valeurs de retour et type de retour attendu. Un gestionnaire d'appel peut également modifier les paramètres et renvoient des valeurs, arrêter la propagation de l'appel dans le pipeline et lève une exception.

Il est intéressant de noter que l'unité n'est pas fourni avec tous les gestionnaires d'appel. Vous pouvez uniquement créer vos propres, ou référencer des blocs d'application à partir de EntLib 5.0 et utiliser un des gestionnaires d'appel répertoriés dans Figure 1 .

Un gestionnaire d'appel est une classe qui implémente l'interface ICallHandler, comme suit :

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

La propriété Order indique la priorité de ce gestionnaire liée à tous les autres. La méthode Invoke retourne une instance d'une classe qui contient une valeur de retour de la méthode.

L'implémentation d'un gestionnaire d'appel est assez simple en ce sens qu'il a simplement prévu faire ses propres opérations spécifiques et permettent le pipeline de passer. Pour céder le contrôle le gestionnaire suivant dans le pipeline, le gestionnaire appelle le paramètre getNext qu'il reçoit du runtime Unity. Le paramètre getNext est un délégué défini comme :

public delegate InvokeHandlerDelegate GetNextHandlerDelegate();

À son tour, le InvokeHandlerDelegate est définie comme suit :

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

La documentation Unity fournit un schéma clair qui illustre l'interception. Dans Figure 2 , vous voyez un schéma légèrement modifié qui présente l'architecture d'injection de stratégie.

image: The Call Handler Pipeline in the Unity Policy Injection

Figure 2 le Pipeline de gestionnaire d'appel dans l'Injection de stratégie Unity

Dans les limites d'un comportement d'injection de stratégie fourni par le système, vous voyez la chaîne de gestionnaires pour traiter une méthode appelée sur un objet proxy ou la classe dérivée. Pour terminer la vue d'ensemble d'injection de stratégie dans l'unité, nous devons jeter un œil aux règles de correspondance.

Règles de correspondance

Par l'intermédiaire d'une règle de correspondance, vous spécifiez l'emplacement appliquer votre logique d'interception. Si vous utilisez des comportements, votre code s'applique à l'objet entier ;avec un ou plusieurs règles de correspondance, vous pouvez définir un filtre. Une règle de correspondance indique un critère pour sélectionner les objets et membres auxquels Unity s'attache un gestionnaire pipeline. À l'aide de la terminologie de l'AOP, une règle de correspondance est le critère que vous utilisez pour définir le pointcuts. Figure 3 répertorie les règles de correspondance pris en charge par unité.

Figure 3 la liste des règles de correspondance pris en charge dans Unity 2.0

Règle de correspondance Description
AssemblyMatchingRule Sélectionne cible des objets basés sur les types dans l'assembly spécifié.
CustomAttributeMatchingRule Sélectionne cible les objets basés sur un attribut personnalisé au niveau du membre.
MemberNameMatchingRule Sélectionne cible les objets basés sur le nom du membre.
MethodSignatureMatchingRule Sélectionne cible des objets en fonction de signature de the.
NamespaceMatchingRule Sélectionne cible des objets en fonction de l'espace de noms the.
ParameterTypeMatchingRule Sélectionne cible les objets basés sur le nom du type d'un paramètre pour un membre.
PropertyMatchingRule Sélectionne cible des objets en fonction de membre names, y compris les caractères génériques.
ReturnTypeMatchingRule Sélectionne les objets selon le type de retour de cible.
TagMatchingRule Sélectionne cible des objets basés sur la valeur affectée à un attribut Tag ad hoc.
TypeMatchingRule Sélectionne cible les objets basés sur le nom du type.

Une règle de correspondance est une classe qui implémente l'interface IMatchingRule. Grâce à ces connaissances, voyons comment travailler avec injection de stratégie. Il existe essentiellement trois façons dans laquelle vous pouvez définir des stratégies: à l'aide d'attributs, à l'aide de code fluent et via la configuration.

Ajout de stratégies via des attributs

Figure 4 montre un gestionnaire d'exemple d'appel qui lève une exception si le résultat d'une opération est négatif. J'utiliserai ce même gestionnaire dans divers scénarios.

Figure 4 La 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; }
}

Utiliser le gestionnaire le plus simple consiste en le joignant à n'importe quelle méthode où vous pensez qu'il peut être utile. Pour ce faire, vous devez un attribut, tel que :

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

Voici un classe d'exemple de calculatrice vous décorez avec les stratégies basées sur l'attribut :

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

Le résultat est que les appels méthode somme procéder de manière habituelle indépendamment de la valeur renvoyée, alors que les appels de méthode Sub lèvera une exception si un nombre négatif est renvoyée.

À l'aide de Code Fluent

Si vous n'aimez pas les attributs, vous pouvez exprimer la même logique via une API fluent. Dans ce cas, vous devez fournir beaucoup plus de détails en ce qui concerne les règles de correspondance. Voyons comment exprimer l'idée que nous voulons injecter du code uniquement dans les méthodes qui renvoient un Int32 et sont nommés Sub. Vous utilisez l'API fluent pour configurer le conteneur d'unité (voir Figure 5 ).

Figure 5 Code Fluent pour définir un ensemble de règles de correspondance

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

Notez que si vous utilisez le Gestionnaire de ContainerControlledLifetimeManager, vous êtes certain que la même instance de gestionnaire d'appel est partagée par toutes les méthodes.

L'effet du code est que n'importe quel type concret qui implémente ICalculator (c'est-à-dire est configuré pour être interceptés et est résolue par le biais de l'unité) sélectionnez deux candidats potentiels pour l'injection : méthodes Sub et Test. Toutefois, seules les méthodes avec un type de retour de Int32 survivre à la règle de correspondance supplémentaire. Cela signifie que, par exemple, Test va être exclu si cela se produit pour retourner une valeur Double.

Ajout de stratégies via la Configuration

Enfin, le même concept peut être exprimé à l'aide du fichier de configuration. Figure 6 indique le contenu prévu de la <unity>section.

Figure 6 Préparation de l'Injection de stratégie dans le fichier de Configuration

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="https://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>

En fait, lorsque vous avez plusieurs règles de correspondance dans une seule stratégie, le résultat final est l'opérateur booléen et s'applique à tous les éléments (ce qui signifie que chacun d'entre eux doit être true). Si vous avez défini plusieurs stratégies, puis chacune d'entre elles est évaluée pour la correspondance — et les gestionnaires appliqués — indépendamment. Par conséquent, vous pouvez obtenir des gestionnaires appliquées à partir de différentes stratégies.

Interception en un coup de œil

Pour résumer, interception est celle dans laquelle la plupart des infrastructures IoC dans Microsoft.NET Framework espace implémenter l'orientation de l'aspect. Par l'intermédiaire d'interception, vous êtes donné la possibilité d'exécuter votre propre code avant ou après n'importe quelle méthode donné dans n'importe quel type de donnée dans n'importe quel assembly donné. EntLib dans le passé fourni un bloc d'application spécifique, PIAB, pour parvenir. EntLib 5.0, le moteur sous-jacent de PIAB a été déplacé dans l'unité et implémenté comme un comportement spécial pour l'interception de bas niveau Unity API que j'ai abordées dans mes deux colonnes précédentes. Le comportement d'injection de stratégie requiert l'utilisation d'un conteneur d'unité et ne fonctionne pas uniquement par le biais de l'API de bas niveau interception.

L'interception de bas niveau API, cependant, ne vous permettre de sélectionner les membres de type que vous souhaitez intercepter ;Vous devez écrire le code que vous-même. Avec le comportement d'injection de stratégie, cependant, vous pouvez vous concentrer sur les détails du comportement souhaité, puis la bibliothèque vous permettent de prendre en charge les méthodes, elle s'applique à basée sur les règles que vous lui demandez.

Dino Esposito est l'auteur de « programmation Microsoft ASP.NET MVC » (Microsoft Press, 2010) et co-auteur de « Microsoft.NET: conception d'Applications pour l'entreprise » (Microsoft Press, 2008). Basé en Italie, Dino Esposito participe régulièrement aux différentes manifestations du secteur organisées aux quatre coins du monde. Vous trouverez son blog à l'adresse : weblogs.asp.net/despos.

Grâce à l'expert technique suivante pour la révision de cet article : Chris Tavares