web-dev-qa-db-fra.com

C #: Comment créer un attribut sur une méthode déclenchant un événement quand il est appelé?

Existe-t-il un moyen en C # ou .NET en général de créer un attribut sur une méthode qui déclenche un événement lorsque la méthode est appelée? Idéalement, je serais capable d'exécuter des actions personnalisées avant et après l'invocation de la méthode.

Je veux dire quelque chose comme ça:

[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}

Je ne sais absolument pas comment le faire ou si cela est possible, mais System.Diagnostic.ConditionalAttribute pourrait faire la même chose en arrière-plan. Je ne suis pas sûr cependant.

EDIT: J'ai oublié de mentionner qu'en raison des circonstances propres à mon cas, les performances ne sont pas vraiment un problème.

39
Tamas Czinege

La seule façon dont je sais faire cela est avec PostSharp . Il post-traite votre IL et peut faire des choses comme ce que vous avez demandé.

17
OwenP

Ce concept est utilisé dans MVC applications Web. 

.NET Framework 4.x fournit plusieurs attributs qui déclenchent des actions, par exemple: ExceptionFilterAttribute (gestion des exceptions), AuthorizeAttribute (autorisation de traitement). Les deux sont définis dans System.Web.Http.Filters.

Vous pouvez par exemple définir votre propre attribut d'autorisation comme suit:

public class myAuthorizationAttribute : AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        // do any stuff here
        // it will be invoked when the decorated method is called
        if (CheckAuthorization(actionContext)) 
           return true; // authorized
        else
           return false; // not authorized
    }

}

Ensuite, dans votre classe controller, vous décorez les méthodes censées utiliser votre autorisation de la manière suivante:

[myAuthorization]
public HttpResponseMessage Post(string id)
{
    // ... your code goes here
    response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
    return response;
}

Chaque fois que la méthode Post est appelée, elle appelle la méthode IsAuthorized à l'intérieur de l'attribut myAuthorizationavant que le code à l'intérieur de la méthode Post soit exécuté.

Si vous renvoyez false dans la méthode IsAuthorized, vous signalez que l'autorisation n'est pas accordée et que l'exécution de la méthode Post est annulée.


Pour comprendre comment cela fonctionne, examinons un exemple différent: Le ExceptionFilter, qui permet de filtrer les exceptions à l’aide d’attributs, son utilisation est similaire à celle présentée ci-dessus pour le paramètre AuthorizeAttribute une description plus détaillée de son utilisation ici ).

Pour l'utiliser, dérivez la classe DivideByZeroExceptionFilter à partir de ExceptionFilterAttribute comme indiqué ici et remplacez la méthode OnException

public class DivideByZeroExceptionFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext.Exception is DivideByZeroException)
        {
            actionExecutedContext.Response = new HttpResponseMessage() { 
                Content = new StringContent("An error occured within the application.",
                                System.Text.Encoding.UTF8, "text/plain"), 
                StatusCode = System.Net.HttpStatusCode.InternalServerError
                };
        }
    }
}

Ensuite, utilisez le code de démonstration suivant pour le déclencher:

[DivideByZeroExceptionFilter]
public void Delete(int id)
{
    // causes the DivideByZeroExceptionFilter attribute to be triggered:
    throw new DivideByZeroException(); 
}

Maintenant que nous savons comment il est utilisé, nous nous intéressons principalement à la mise en œuvre. Le code suivant provient du .NET Framework. Il utilise l'interface IExceptionFilter en interne sous forme de contrat:

namespace System.Web.Http.Filters
{
    public interface IExceptionFilter : IFilter
    {
        // Executes an asynchronous exception filter.
        // Returns: An asynchronous exception filter.
        Task ExecuteExceptionFilterAsync(
                    HttpActionExecutedContext actionExecutedContext, 
                    CancellationToken cancellationToken);
    }
}

Le ExceptionFilterAttribute lui-même est défini comme suit:

namespace System.Web.Http.Filters
{
    // Represents the attributes for the exception filter.
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, 
            Inherited = true, AllowMultiple = true)]
    public abstract class ExceptionFilterAttribute : FilterAttribute, 
            IExceptionFilter, IFilter
    {
        // Raises the exception event.
        // actionExecutedContext: The context for the action.</param>
        public virtual void OnException(
            HttpActionExecutedContext actionExecutedContext)
        {
        }
        // Asynchronously executes the exception filter.
        // Returns: The result of the execution.
        Task IExceptionFilter.ExecuteExceptionFilterAsync(
            HttpActionExecutedContext actionExecutedContext, 
            CancellationToken cancellationToken)
        {
            if (actionExecutedContext == null)
            {
                throw Error.ArgumentNull("actionExecutedContext");
            }
            this.OnException(actionExecutedContext);
            return TaskHelpers.Completed();
        }
    }
}

Dans ExecuteExceptionFilterAsync, la méthode OnException est appelée. Comme vous l'avez remplacé comme indiqué précédemment, l'erreur peut maintenant être traitée par votre propre code.


Il existe également un produit commercial disponible comme mentionné dans la réponse d'OwenP, PostSharp , qui vous permet de le faire facilement. _ { Here } est un exemple d'utilisation de PostSharp. Notez qu’il existe une édition Express disponible que vous pouvez utiliser gratuitement, même pour des projets commerciaux.

Exemple PostSharp (voir le lien ci-dessus pour une description complète):

public class CustomerService
{
    [RetryOnException(MaxRetries = 5)]
    public void Save(Customer customer)
    {
        // Database or web-service call.
    }
}

Ici, l'attribut spécifie que la méthode Save est appelée jusqu'à 5 fois si une exception se produit. Le code suivant définit cet attribut personnalisé:

[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
    public RetryOnExceptionAttribute()
    {
        this.MaxRetries = 3;
    }

    public int MaxRetries { get; set; }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        int retriesCounter = 0;

        while (true)
        {
            try
            {
                args.Proceed();
                return;
            }
            catch (Exception e)
            {
                retriesCounter++;
                if (retriesCounter > this.MaxRetries) throw;

                Console.WriteLine(
                    "Exception during attempt {0} of calling method {1}.{2}: {3}",
                    retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
            }
        }
    }
}
20
Matt

Vous avez besoin d'une sorte de cadre orienté Aspect. PostSharp le fera, tout comme Windsor .

En gros, ils sous-classent votre objet et surchargent cette méthode ...

alors il devient:

//proxy
public override void DoSomeStuff()
{
     if(MethodHasTriggerAttribute)
        Trigger();

     _innerClass.DoSomeStuff();
}

bien sûr, tout cela vous est caché. Tout ce que vous avez à faire est de demander le type à Windsor, qui le fera pour vous. L'attribut devient une installation (personnalisée) à Windsor, je pense.

10
Ben Scheirman

Vous pouvez utiliser ContextBoundObject et IMessageSink. Voir http://msdn.Microsoft.com/nb-no/magazine/cc301356(en-us).aspx

Soyez averti que cette approche a un impact important sur les performances par rapport à un appel direct à une méthode.

3
Hallgrim

Je ne pense pas qu'il soit possible de le faire avec un simple attribut, mais en utilisant classes proxy et en réfléchissant, vous pourriez avoir une classe qui sait intercepter les instanciations des classes dans lesquelles vous avez attribué des méthodes. 

Ensuite, la classe proxy peut déclencher un événement chaque fois que les méthodes attribuées sont appelées.

0
wprl

Un attribut donne des informations, ce sont des métadonnées. Je ne connais pas un moyen de faire ça tout seul, quelqu'un pourrait.

Vous pouvez examiner des méthodes partielles dans .NET qui vous permettent de gérer des événements légers. Vous fournissez les points d'ancrage et laissez quelqu'un d'autre gérer la mise en œuvre. Si la méthode n'est pas implémentée, le compilateur l'ignore.

http://msdn.Microsoft.com/en-us/library/wa80x488.aspx

0
Nick

Vous pouvez jeter un oeil à la solution du pauvre homme: voir le motif du décorateur.

0
balintn