18

初期化によって、制御できない理由で例外がスローされる可能性があるコンストラクターがあるとします。

FantasticApiController(IAwesomeGenerator awesome,
    IBusinessRepository repository, IIceCreamFactory factory)
{
       Awesome = awesome;
       Repository = repository;
       IceCream = factory.MakeIceCream();

       DoSomeInitialization(); // this can throw an exception
}

通常、WebAPIのControllerアクションが例外をスローすると、csutomを介してそれを処理できますExceptionFilterAttribute

public class CustomErrorHandler
{
    public override void OnException(HttpActionExecutedContext context)
    {
        // Critical error, this is real bad.
        if (context.Exception is BubonicPlagueException)
        {
            Log.Error(context.Exception, "CLOSE EVERYTHING!");
            Madagascar.ShutdownAllPorts();
        }

        // No big deal, just show something user friendly
        throw new HttpResponseException(new HttpResponseMessage
        {
            Content = new StringContent("Hey something bad happened. " +
                                        "Not closing the ports though"),
            StatusCode = HttpStatusCode.InternalServerError;
        });
    }

したがって、BoardPlaneをスローするAPIメソッドがある場合BubonicPlagueExceptionは、CustomerErrorHandlerマダガスカルへのポートをシャットダウンし、期待どおりにエラーとしてログに記録します。それがそれほど深刻ではない他の例では、私はいくつかのユーザーフレンドリーなメッセージを表示して500を返しますInternalServerError

DoSomeInitializationしかし、例外をスローする場合、これはまったく何もしません。WebAPIコントローラーコンストラクターで例外を処理するにはどうすればよいですか?

4

1 に答える 1

14

WebApi コントローラーが作成され、コンストラクターが HttpControllerActivators を介して呼び出されます。デフォルトのアクティベーターは System.Web.Http.Dispatcher.DefaultHttpControllerActivator です。

github のオプション 1 と 2 の非常に大まかな例https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src

非常にうまく機能するオプション 1には、DI コンテナーの使用が含まれます (既に使用している可能性があります)。この例では Ninject を使用し、「インターセプター」使用して、DefaultHttpControllerActivator の Create メソッドの呼び出しをインターセプトして試行/キャッチしました。少なくとも、次のようなことを実行できる AutoFac と Ninject を知っています。

インターセプターを作成する

あなたのマダガスカルとログ アイテムの有効期間はわかりませんが、インターセプターに注入される可能性は十分にあります

public class ControllerCreationInterceptor : Ninject.Extensions.Interception.IInterceptor
{
    private ILog _log;
    private IMadagascar _madagascar;

    public ControllerCreationInterceptor(ILog log, IMadagascar madagascar)
    {
        _log = log;
        _madagascar = madagascar;
    }

しかし、ログとマダガスカルがある種の静的グローバルであるあなたの質問の例を維持してください

public class ControllerCreationInterceptor : Ninject.Extensions.Interception.IInterceptor
{

    public void Intercept(Ninject.Extensions.Interception.IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        catch(InvalidOperationException e)
        {
            if (e.InnerException is BubonicPlagueException)
            {
                Log.Error(e.InnerException, "CLOSE EVERYTHING!");
                Madagascar.ShutdownAllPorts();
                //DO SOMETHING WITH THE ORIGIONAL ERROR!
            }
            //DO SOMETHING WITH THE ORIGIONAL ERROR!
        }
    }
}

最後にインターセプターをグローバル asax または App_Start (NinjectWebCommon)に登録します。

kernel.Bind<System.Web.Http.Dispatcher.IHttpControllerActivator>()
            .To<System.Web.Http.Dispatcher.DefaultHttpControllerActivator>().Intercept().With<ControllerCreationInterceptor>();

オプション 2は、IHttpControllerActivator インターフェイスを実装する独自の Controller Activator を実装し、Create メソッドで Controller の作成時のエラーを処理することです。デコレータ パターンを使用して、DefaultHttpControllerActivator をラップできます。

public class YourCustomControllerActivator : IHttpControllerActivator
{
    private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();

    public YourCustomControllerActivator()
    {

    }

     public System.Web.Http.Controllers.IHttpController Create(System.Net.Http.HttpRequestMessage request, System.Web.Http.Controllers.HttpControllerDescriptor controllerDescriptor, Type controllerType)
    {
        try
        {
            return _default.Create(request, controllerDescriptor, controllerType);
        }
        catch (InvalidOperationException e)
        {
            if (e.InnerException is BubonicPlagueException)
            {
                Log.Error(e.InnerException, "CLOSE EVERYTHING!");
                Madagascar.ShutdownAllPorts();
                //DO SOMETHING WITH THE ORIGIONAL ERROR!
            }
            //DO SOMETHING WITH THE ORIGIONAL ERROR!
            return null;
        }

    }
}

独自のカスタム アクティベーターを作成したら、グローバル asax でデフォルト アクティベーターを切り替えることができます。

  GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());

オプション3もちろん、コンストラクターでの初期化が実際のコントローラーメソッド、プロパティなどにアクセスする必要がない場合...つまり、コンストラクターから削除できると仮定すると...初期化を移動するだけの方がはるかに簡単ですフィルタなど

public class MadagascarFilter : AbstractActionFilter
{
    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
    try{
          DoSomeInitialization(); // this can throw an exception
        }
        catch(BubonicPlagueException e){
    Log.Error(e, "CLOSE EVERYTHING!");
        Madagascar.ShutdownAllPorts();
            //DO SOMETHING WITH THE ERROR                           
        }

        base.OnActionExecuting(actionContext);
    }

public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
    {
        base.OnActionExecuted(actionExecutedContext);
    }

    public override bool AllowMultiple
    {
        get { return false; }
    }


}
于 2012-07-24T14:31:37.967 に答える