C#:呼び出されたときにイベントをトリガーするメソッドに属性を作成する方法
-
03-07-2019 - |
質問
C#または.NETには一般に、メソッドが呼び出されたときにイベントをトリガーするメソッドの属性を作成する方法はありますか?理想的には、メソッドの呼び出しの前後にカスタムアクションを実行できます。
次のようなものです:
[TriggersMyCustomAction()]
public void DoSomeStuff()
{
}
私はそれをどうやってやるか、まったく可能かどうかはまったくわかりませんが、 System.Diagnostic.ConditionalAttribute は、バックグラウンドで同様の処理を行う場合があります。よくわかりません。
編集:特定のケースの状況により、パフォーマンスは実際には問題ではないことを言及するのを忘れました。
解決
これを行う方法を知っている唯一の方法は、 PostSharp を使用することです。 ILを後処理し、要求したような処理を実行できます。
他のヒント
この概念は、 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
}
}
次に、 controller クラスで、次のように認証を使用することになっているメソッドを修飾します。
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
Post
メソッドが呼び出されるたびに、 myAuthorization
属性内の IsAuthorized
メソッドを before 呼び出します Post
メソッド内のコードが実行されます。
IsAuthorized
メソッドで false
を返す場合、許可が付与されていないことを通知し、メソッド Post
の実行が中止されます。
これがどのように機能するかを理解するために、別の例を見てみましょう。属性を使用して例外をフィルタリングできる ExceptionFilter
の使用法は、上記の AuthorizeAttribute
(その使用法に関するより詳細な説明を見つけることができますこちら)。
それを使用するには、こちら、メソッド 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にタイプを尋ねるだけです。この属性は、Windsorで考える(カスタム)機能になります。
ContextBoundObjectとIMessageSinkを使用できます。 http://msdn.microsoft.com/nb-を参照してください。 no / magazine / cc301356(en-us).aspx
このアプローチは、直接メソッド呼び出しと比較してパフォーマンスに重大な影響があることに注意してください。
属性だけでそれを行う方法はないと思うが、プロキシクラスとリフレクションを使用すると、属性付きメソッドを持っているクラスのインスタンス化をインターセプトすることがわかっているクラスを作成できます。
その後、プロキシクラスは、属性付きメソッドが呼び出されるたびにイベントをトリガーできます。
属性は情報を提供し、メタデータです。誰かがこれをオフハンドで行う方法を知りません。
.NETの部分的なメソッドを見ると、いくつかの軽量なイベント処理を実行できます。フックを提供し、他の誰かに実装を処理させます。メソッドが実装されていない場合、コンパイラはそれを無視します。
貧乏人の解決策を見てみましょう。デコレータパターンを参照してください。