C #: Come creare un attributo su un metodo che attiva un evento quando viene invocato?
-
03-07-2019 - |
Domanda
Esiste un modo in C # o .NET in generale per creare un attributo su un metodo che innesca un evento quando viene invocato il metodo? Idealmente, sarei in grado di eseguire azioni personalizzate prima e dopo l'invocazione del metodo.
Intendo qualcosa del genere:
[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}
Non ho idea di come farlo o se possibile, ma System.Diagnostic.ConditionalAttribute potrebbe fare una cosa simile in background. Non sono sicuro però.
EDIT : ho dimenticato di menzionare che a causa delle circostanze del mio caso specifico, le prestazioni non sono realmente un problema.
Soluzione
L'unico modo in cui so come farlo è con PostSharp . Post elabora il tuo IL e può fare cose come quello che hai chiesto.
Altri suggerimenti
Questo concetto viene utilizzato nelle MVC applicazioni web.
Il .NET Framework 4.x fornisce diversi attributi che attivano azioni, ad esempio: ExceptionFilterAttribute
(gestione delle eccezioni), AuthorizeAttribute
(gestione dell'autorizzazione ). Entrambi sono definiti in System.Web.Http.Filters
.
Ad esempio, è possibile definire il proprio attributo di autorizzazione come segue:
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
}
}
Quindi, nella tua classe controller decori i metodi che dovrebbero usare la tua autorizzazione come segue:
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
Ogni volta che viene invocato il metodo Post
, chiamerà il metodo IsAuthorized
all'interno dell'attributo myAuthorization
prima viene eseguito il codice all'interno del metodo Post
.
Se si restituisce false
nel metodo IsAuthorized
, si segnala che l'autorizzazione non viene concessa e l'esecuzione del metodo Post
si interrompe.
Per capire come funziona, esaminiamo un esempio diverso: ExceptionFilter
, che consente di filtrare le eccezioni utilizzando gli attributi, l'utilizzo è simile a quello mostrato sopra per AuthorizeAttribute
(puoi trovare una descrizione più dettagliata del suo utilizzo qui ).
Per usarlo, ricava la classe DivideByZeroExceptionFilter
dalla ExceptionFilterAttribute
come mostrato qui e sovrascrive il metodo 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
};
}
}
}
Quindi utilizzare il seguente codice demo per attivarlo:
[DivideByZeroExceptionFilter]
public void Delete(int id)
{
// causes the DivideByZeroExceptionFilter attribute to be triggered:
throw new DivideByZeroException();
}
Ora che sappiamo come viene utilizzato, siamo principalmente interessati all'implementazione. Il seguente codice proviene da .NET Framework. Utilizza l'interfaccia IExceptionFilter
internamente come contratto:
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);
}
}
Il ExceptionFilterAttribute
stesso è definito come segue:
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();
}
}
}
All'interno di ExecuteExceptionFilterAsync
, viene chiamato il metodo OnException
. Poiché lo hai sovrascritto come mostrato in precedenza, ora l'errore può essere gestito con il tuo codice.
Esiste anche un prodotto commerciale disponibile come indicato nella risposta di OwenP, PostSharp , che ti consente di fare così facilmente. Qui è un esempio di come puoi farlo con PostSharp. Tieni presente che è disponibile un'edizione Express che puoi utilizzare gratuitamente anche per progetti commerciali.
Esempio PostSharp (vedi il link sopra per la descrizione completa):
public class CustomerService
{
[RetryOnException(MaxRetries = 5)]
public void Save(Customer customer)
{
// Database or web-service call.
}
}
Qui l'attributo specifica che il metodo Salva
viene chiamato fino a 5 volte se si verifica un'eccezione. Il codice seguente definisce questo attributo personalizzato:
[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);
}
}
}
}
Hai bisogno di una sorta di framework orientato all'aspetto. PostSharp lo farà, così come Windsor .
Fondamentalmente, eseguono la sottoclasse del tuo oggetto e sovrascrivono questo metodo ...
quindi diventa:
//proxy
public override void DoSomeStuff()
{
if(MethodHasTriggerAttribute)
Trigger();
_innerClass.DoSomeStuff();
}
ovviamente tutto ciò ti è nascosto. Tutto quello che devi fare è chiedere a Windsor il tipo e farà il proxy per te. L'attributo diventa una struttura (personalizzata) che penso in Windsor.
Puoi usare ContextBoundObject e IMessageSink. Vedi http://msdn.microsoft.com/nb- no / magazine / cc301356 (it-it) aspx
Tieni presente che questo approccio ha un forte impatto sulle prestazioni rispetto a una chiamata diretta al metodo.
Non credo che ci sia un modo per farlo con solo un attributo, ma usando classi proxy e riflessione potresti avere una classe che sa intercettare le istanze delle classi in cui hai attribuito metodi.
Quindi la classe proxy può attivare un evento ogni volta che vengono chiamati i metodi attribuiti.
Un attributo fornisce informazioni, sono metadati. Non conosco un modo per farlo con disinvoltura, qualcuno potrebbe.
È possibile esaminare metodi parziali in .NET che consentono di gestire in modo leggero gli eventi. Fornisci gli hook e consenti a qualcun altro di gestire l'implementazione. Se il metodo non è implementato, il compilatore lo ignora.
Potresti dare un'occhiata alla soluzione del povero: vedi il motivo del decoratore.