41

C# 或 .NET 通常有没有办法在方法上创建一个属性,该属性在调用该方法时触发一个事件?理想情况下,我将能够在调用方法之前和之后运行自定义操作。

我的意思是这样的:

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

我完全不知道如何做到这一点,或者是否可能,但System.Diagnostic.ConditionalAttribute可能会在后台做类似的事情。不过我不确定。

编辑:我忘了提到,由于我的具体情况,性能并不是真正的问题。

4

7 回答 7

27

这个概念用于MVC Web 应用程序。

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

}

然后,在您的控制器类中,您装饰应该使用您的授权的方法,如下所示:

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

每当Post调用方法时,都会在IsAuthorized方法内部的代码执行之前myAuthorization调用Attribute内部的方法。Post

如果您false在该IsAuthorized方法中返回,则表示未授予授权并且该方法的执行Post中止。


为了理解它是如何工作的,让我们看一个不同的例子: The ExceptionFilter,它允许通过使用属性过滤异常,用法与上面所示的类似(您可以在此处AuthorizeAttribute找到有关其用法的更详细说明)。

要使用它,请DivideByZeroExceptionFilter此处ExceptionFilterAttribute所示的类派生类,并覆盖该方法:OnException

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

然后使用下面的演示代码来触发它:

[DivideByZeroExceptionFilter]
public void Delete(int id)
{
    // Just for demonstration purpose, it
    // causes the DivideByZeroExceptionFilter attribute to be triggered:
    throw new DivideByZeroException(); 

    // (normally, you would have some code here that might throw 
    // this exception if something goes wrong, and you want to make
    // sure it aborts properly in this case)
}

现在我们知道它是如何使用的,我们主要对实现感兴趣。以下代码来自 .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.
        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);
            }
        }
    }
}
于 2014-01-30T16:20:27.257 回答
21

我知道如何做到这一点的唯一方法是使用PostSharp。它对您的 IL 进行后处理,并可以执行您要求的操作。

于 2008-10-22T15:57:35.080 回答
12

您需要某种面向方面的框架。PostSharp 会这样做,Windsor也会这样做。

基本上,它们是您的对象的子类并覆盖此方法...

然后它变成:

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

     _innerClass.DoSomeStuff();
}

当然,这一切对你来说都是隐藏的。您所要做的就是向 Windsor 询问类型,它会为您进行代理。我认为该属性成为温莎的(自定义)设施。

于 2008-10-22T16:02:36.320 回答
3

您可以使用 ContextBoundObject 和 IMessageSink。请参阅http://msdn.microsoft.com/nb-no/magazine/cc301356(en-us).aspx

请注意,与直接方法调用相比,此方法具有严重的性能影响。

于 2008-10-22T16:06:00.460 回答
0

我认为没有办法只使用一个属性,但是使用代理类和反射,您可以拥有一个知道拦截具有属性方法的类的实例化的类。

然后,代理类可以在调用属性方法时触发事件。

于 2008-10-22T15:57:35.487 回答
0

属性提供信息,它们是元数据。我不知道有什么方法可以临时做到这一点,有人可能会。

您可以查看 .NET 中的部分方法,这些方法允许您进行一些轻量级的事件处理。您提供挂钩并让其他人处理实现。如果该方法没有实现,编译器就会忽略它。

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

于 2008-10-22T15:58:28.247 回答
0

你可以看看穷人的解决方案:看看装饰器模式。

于 2018-05-07T20:34:13.560 回答