41

メソッドが呼び出されたときにイベントをトリガーするメソッドに属性を作成する一般的な方法は、C#または.NETにありますか? 理想的には、メソッドの呼び出しの前後にカスタム アクションを実行できるようにします。

私はこのようなことを意味します:

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

私はそれを行う方法、または可能であればまったく無知ですが、System.Diagnostic.ConditionalAttributeはバックグラウンドで同様のことを行う可能性があります。よくわかりませんが。

編集:私の特定のケースの状況により、パフォーマンスは実際には問題ではないことを忘れていました。

4

7 に答える 7

27

この概念は、MVC Web アプリケーションで使用されます。

.NET Framework 4.xExceptionFilterAttributeは、(例外の処理)、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メソッドが呼び出されます。IsAuthorizedmyAuthorizationPost

メソッドに戻るfalseと、IsAuthorized承認が付与されていないことを通知し、メソッドの実行がPost中止されます。


これがどのように機能するかを理解するために、別の例を見てみましょう:ExceptionFilter属性を使用して例外をフィルタリングできる の使用方法は、上記の の場合と同様AuthorizeAttributeです (使用方法の詳細については、こちらを参照してください)。

これを使用するには、ここに示すようにDivideByZeroExceptionFilterからクラスを派生させ、メソッドをオーバーライドします。ExceptionFilterAttributeOnException

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

ある種のアスペクト指向のフレームワークが必要です。Windsorと同様に、PostSharp がそれを行います。

基本的に、オブジェクトをサブクラス化し、このメソッドをオーバーライドします...

次に、次のようになります。

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

     _innerClass.DoSomeStuff();
}

もちろん、これはすべてあなたに隠されています。あなたがしなければならないことは、Windsor にタイプを尋ねるだけで、それがプロキシをしてくれます。属性は、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 に答える