web-dev-qa-db-ja.com

ロギングのためにNinjectインターセプトを使用してすべてのASP.NETWeb Apiコントローラーアクションメソッド呼び出しをインターセプトするにはどうすればよいですか?

当社では、ASP.NET WebApiコントローラーのアクションメソッドの1つが呼び出されるたびに、特定のものをログに記録する必要があります。現在、DIにNinjectを使用しているので、この目的にも使用したいと思います。これは私がこれまでに試したことです。

NuGetを介してNinject、Ninject.Extensions.Interception、およびNinject.Extensions.Interception.DynamicProxyがインストールされており、次のモジュールがあります。

public class InterceptAllModule : InterceptionModule
{
    public override void Load()
    {
        Kernel.Intercept(p => p.Request.Service.Name.EndsWith("Controller")).With(new TimingInterceptor());
    }
}

TimingInterceptorはどこにありますか

public class TimingInterceptor : SimpleInterceptor
{
    readonly Stopwatch _stopwatch = new Stopwatch();
    protected override void BeforeInvoke(IInvocation invocation)
    {
        _stopwatch.Start();
    }

    protected override void AfterInvoke(IInvocation invocation)
    {
        _stopwatch.Stop();
        string message = string.Format("[Execution of {0} took {1}.]",invocation.Request.Method,_stopwatch.Elapsed);
        Log.Info(message + "\n");
        _stopwatch.Reset();
    }
}

さて、モジュールをninjectカーネルに接続して、自分のサイトを実行しようとすると

var kernel = new StandardKernel(new InterceptAllModule());

ただし、アクションメソッドの1つに呼び出しが着信すると、エラーがスローされます。

Cannot instantiate proxy of class: MyApiController.

経験のある人が私が間違っていることを指摘してもらえますか?ありがとう。

20
Ray

更新

したがって、コードとRemoの優れた点を使用して、アクションメソッドを仮想にする必要があり、空のデフォルトコンストラクターを配置します(動的プロキシを配置するために、他のコンストラクターを静止させます)アクションフィルターとインターセプトアプローチの両方が機能しています。

現状では、コードはApiControllerの望ましくない可能性のあるメソッドをインターセプトするため、これらを除外するためにコードを配置する必要があります。 ExecuteAsyncとDispose。

私の他の唯一のポイントはパフォーマンスです。 巨大な免責事項これらは非常に基本的なテストです(統計をログに記録するために毎回アクションフィルターアプローチを使用します)、私はあなた自身を行うことを勧めます(!) ...しかし、DynamicProxyインターセプターを使用すると、getリクエストごとに約4ミリ秒の時間が得られました

[Execution of Get took 00:00:00.0046615.]
[Execution of Get took 00:00:00.0041988.]
[Execution of Get took 00:00:00.0039383.]

インターセプトコードをコメントアウトし、アクションフィルターを使用すると、ミリ秒未満のパフォーマンスが得られました。

[Execution of Get took 00:00:00.0001146.]
[Execution of Get took 00:00:00.0001116.]
[Execution of Get took 00:00:00.0001364.]

これが実際に問題なのか懸念なのかはあなた次第ですが、私はこれを指摘したいと思いました。

前の応答

ActionFiltersを使用して除外しましたか?これは、MVCアクションでのAOPの自然な拡張ポイントです。

コントローラでの実際のアクション以外の方法に興味がある場合は理解できますが、とにかく提案を投稿すると思いました。

ActionFilterAttributesはスレッド間で再利用されますか?それはどのように機能しますか? および ASP.NET MVCコントローラーアクションを呼び出す時間を測定する に触発されました。

メソッドがタグ付けされたときにタイマーが除外されることを示すように更新されました。特にコアWebApiフレームワークからのインスピレーション AllowAnonymousAttribute および AuthorizeAttribute

これをグローバルに登録して、すべてのアクションがこれによって監視されるようにします。

GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());

次に:

public class TimingActionFilter : ActionFilterAttribute
{
    private const string Key = "__action_duration__";

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (SkipLogging(actionContext))
        {
            return;
        }

        var stopWatch = new Stopwatch();
        actionContext.Request.Properties[Key] = stopWatch;
        stopWatch.Start();
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        if (!actionExecutedContext.Request.Properties.ContainsKey(Key))
        {
            return;
        }

        var stopWatch = actionExecutedContext.Request.Properties[Key] as Stopwatch;
        if(stopWatch != null)
        {
            stopWatch.Stop();
            var actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
            Debug.Print(string.Format("[Execution of {0} took {1}.]", actionName, stopWatch.Elapsed));
        }

    }

    private static bool SkipLogging(HttpActionContext actionContext)
    {
        return actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>().Any() ||
                actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<NoLogAttribute>().Any();
    }
}

そして

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoLogAttribute : Attribute
{

}

これで、次を使用してグローバルフィルターを除外できます。

public class ExampleController : ApiController
{
    // GET api/example
    [NoLog]
    public Example Get()
    {
       //
    }
}
29
Mark Jones

まだ潜んでいる人のために、私がNinjectを使用したかった理由は、ロガー(または他のもの)をインターセプターに注入できるようにするためでしたが、すべてのアクションをインターセプトしたかったのです。

マークの答えは完璧ですが、グローバルに登録する代わりに

GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());

を使用してフィルターをNinjectにバインドします

Kernal.BindHttpFilter<TimingActionFilter>(FilterScope.Action).

TimingActionFilterクラスに適切なコンストラクターを作成する必要があります。

2
Opender Singh