C#:如何在调用事件时触发事件的方法上创建属性?
-
03-07-2019 - |
题
在C#或.NET中,是否有办法在调用方法时触发事件的方法上创建属性?理想情况下,我可以在调用方法之前和之后运行自定义操作。
我的意思是这样的:
[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}
我完全不知道怎么做或者根本不可能,但 System.Diagnostic.ConditionalAttribute 可能会在后台执行类似的操作。我不确定。
编辑:我忘了提及由于我的具体情况,表现并不是真正的问题。
解决方案
我知道如何执行此操作的唯一方法是使用 PostSharp 。它会对您的IL进行后期处理,并且可以执行您要求的操作。
其他提示
此概念用于 MVC 网络应用程序。
.NET Framework 4.x 提供了几个触发操作的属性,例如: ExceptionFilterAttribute
(处理异常), AuthorizeAttribute
(处理授权) )。两者都在 System.Web.Http.Filters
。
您可以按如下方式定义自己的授权属性:
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
}
}
然后,在 controller 类中,您将按照以下方式修饰应该使用您的授权的方法:
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
每当调用 Post
方法时,它将在 myAuthorization
属性中调用 IsAuthorized
方法之前 Post
方法中的代码被执行。
如果在 IsAuthorized
方法中返回 false
,则表示未授予授权,并且 Post
方法的执行将中止。
为了理解这是如何工作的,让我们看一个不同的例子: ExceptionFilter
,它允许使用属性过滤异常,其用法类似于上面显示的 AuthorizeAttribute
(您可以找到有关其用法的更详细说明这里)。
要使用它,从 ExceptionFilterAttribute
派生 DivideByZeroExceptionFilter
类,如图所示这里,并覆盖方法 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
};
}
}
}
然后使用以下演示代码触发它:
[DivideByZeroExceptionFilter]
public void Delete(int id)
{
// causes the DivideByZeroExceptionFilter attribute to be triggered:
throw new DivideByZeroException();
}
现在我们知道它是如何使用的,我们主要对实现感兴趣。以下代码来自.NET Framework。它在内部使用接口 IExceptionFilter
作为合同:
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);
}
}
ExceptionFilterAttribute
本身定义如下:
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();
}
}
}
在 ExecuteExceptionFilterAsync
中,调用方法 OnException
。因为您已经覆盖了它,如前所示,现在可以通过您自己的代码处理错误。
OwenP的答案中提到了一种商业产品, PostSharp ,它允许你做这很容易。 此处是如何使用PostSharp执行此操作的示例。请注意,有一个Express版本,即使是商业项目也可以免费使用。
PostSharp示例(有关完整说明,请参阅上面的链接):
public class CustomerService
{
[RetryOnException(MaxRetries = 5)]
public void Save(Customer customer)
{
// Database or web-service call.
}
}
此处该属性指定如果发生异常,则最多会调用 Save
方法5次。以下代码定义了此自定义属性:
[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);
}
}
}
}
您需要某种面向方面的框架。 PostSharp将会这样做, Windsor 也是如此。
基本上,他们将您的对象子类化并覆盖此方法...
然后它变成了://proxy
public override void DoSomeStuff()
{
if(MethodHasTriggerAttribute)
Trigger();
_innerClass.DoSomeStuff();
}
当然这一切对你来说都是隐藏的。您所要做的就是询问Windsor的类型,它将为您进行代理。该属性成为我在温莎的一个(自定义)设施。
您可以使用ContextBoundObject和IMessageSink。请参阅 http://msdn.microsoft.com/nb-没有/杂志/ cc301356(EN-US)的.aspx
请注意,与直接方法调用相比,此方法会产生严重的性能影响。
我认为没有办法只使用属性,但使用代理类和反射你可以有一个类知道拦截你已经归因于方法的类的实例化。
然后,无论何时调用属性方法,代理类都可以触发事件。
属性提供信息,它们是元数据。我不知道有办法这么做,有人可能。
您可以查看.NET中的部分方法,它们允许您执行一些轻量级事件处理。您提供了钩子并让其他人处理实现。如果没有实现该方法,编译器就会忽略它。
你可以看看这个穷人的解决方案:看看装饰模式。