この概念は、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
メソッドが呼び出されます。IsAuthorized
myAuthorization
Post
メソッドに戻るfalse
と、IsAuthorized
承認が付与されていないことを通知し、メソッドの実行がPost
中止されます。
これがどのように機能するかを理解するために、別の例を見てみましょう: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);
}
}
}
}