18

Unsere Firma muss bestimmte Dinge jedes Mal protokollieren, wenn eine unserer Aktionsmethoden unserer ASP.NET WebApi-Controller aufgerufen wird. Da wir jetzt Ninject für die DI verwenden, möchten wir es auch für diesen Zweck verwenden. Das habe ich bisher versucht.Wie werden alle Aufrufe von ASP.NET WebApi-Controlleraktionsmethoden mit Ninject-Interception für die Protokollierung abgefangen?

Ich habe Ninject, Ninject.Extensions.Interception und Ninject.Extensions.Interception.DynamicProxy durch NuGet installiert und ich habe folgendes Modul

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

Wo TimingInterceptor ist

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(); 
    } 
} 

Nun, wenn Ich versuche, das Modul mit Ninject-Kernel zu haken und meine Website zu starten

Jedes Mal, wenn ein Anruf in einem der Aktionsmethode kommt, wirft er eine Fehlermeldung,

Cannot instantiate proxy of class: MyApiController. 

Könnte jemand mit Erfahrung zeigen, was ich falsch mache bitte? Vielen Dank.

+0

Vielleicht ist dieser Vorschlag eine zu große Verschiebung im Design, aber wenn Sie alle Geschäftsvorgänge hinter einer generischen Abstraktion abstrahieren und diese in die Controller injizieren, könnten Sie diese Abstraktionen leicht schmücken oder abfangen, um übergreifende Bedenken hinzuzufügen wie dein Timing-Aspekt. Schauen Sie sich das [command/handler pattern] (http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91) an, um eine Idee zu bekommen, wovon ich spreche. – Steven

+1

Haben Sie einen zusätzlichen parameterlosen Konstruktor und sind alle Aktionsmethoden virtuell? –

+1

Danke Remo, ich habe die Aktionsmethode virtuell hinzugefügt, aber für den parameterlosen Konstruktor habe ich ein Problem, weil unser Dienstobjekt dort injiziert und im Controller verwendet wird. Mit einem parameterlosen Konstruktor ist das Serviceobjekt null. Wie kommst du um dieses Problem herum? Danke vielmals! –

Antwort

26

aktualisieren

So Ihre Code- und Remo ausgezeichneten Punkt mit über die Aktionsmethoden benötigen in einem leeren Standardkonstruktor zu virtuellen und setzen (nur dynamische Proxy zu besänftigen, halten Ihren anderen Konstruktor noch) I haben sowohl den Aktionsfilter als auch den Interception-Ansatz.

Ich würde sagen, dass, wie es aussieht, Ihr Code potenziell unerwünschte Methoden auf dem ApiController abfangen wird, so dass Sie wahrscheinlich auch etwas Code an Ort und Stelle setzen müssen, um diese herauszufiltern, z. ExecuteAsync und Dispose.

Mein einziger anderer Punkt ist die Leistung. Enormer Disclaimer Dies sind nur sehr grundlegende Tests (mit dem Action Filter Ansatz jedes Mal, um die Statistiken zu protokollieren), ich lade Sie zu Ihrer eigenen (!) ... aber mit dem DynamicProxy Interceptor bekam ich eine Zeit von etwa 4 Millisekunden pro Erhaltungs-Anforderungs

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

den Interception Code kommentierte und einen Aktionsfilter I Sub-Millisekunden-Leistung wurde immer:

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

Es liegt an Ihnen, ob dies tatsächlich ein Problem oder ein Anliegen ist, aber ich Ich dachte, ich würde darauf hinweisen.

Zurück Antwort

Haben rulled Sie ActionFilters mit aus? Dies ist der natürliche Erweiterungspunkt für AOP bei einer MVC-Aktion.

Wenn Sie andere Methoden als die eigentliche Aktion auf dem Controller interessiert waren, dann würde ich verstehen, aber ich dachte, ich würde einen Vorschlag trotzdem posten.

Inspiriert von Are ActionFilterAttributes reused across threads? How does that work? und Measure Time Invoking ASP.NET MVC Controller Actions.

Aktualisiert, um den Ausschluss des Timers anzuzeigen, wenn die Methode markiert wurde. Inspiration aus Kern WebAPI Rahmen speziell AllowAnonymousAttribute und AuthorizeAttribute

Register global dies so, dass alle Aktionen, die von dieser überwacht werden:

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

Dann:

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(); 
    } 
} 

Und

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

} 

Jetzt Sie können den globalen Filter ausschließen mit:

public class ExampleController : ApiController 
{ 
    // GET api/example 
    [NoLog] 
    public Example Get() 
    { 
     // 
    } 
} 
+0

+1 +1 Hallo Mark, ich schätze deine Antwort. Aber mein Manager möchte das Attribut Aktionsfilter nicht verwenden. Er möchte, dass alle Aktionsmethoden protokolliert werden, außer auf denen, bei denen wir ein Attribut [nolog] bezeichnen. –

+1

@ ray247, danke, dass ich den Vorschlag machen darf. Für einen Penny, in für ein Pfund habe ich aktualisiert, um die NoLog-Fähigkeit zu zeigen, die diesen Ansatz auch verwendet. Ich werde auch die Antwort mit einigen Ninject-Beispielen in Kürze aktualisieren. –

+2

Danke ein Haufen Mark. Ich habe Ihren Code noch nicht ausprobiert, aber ich stimme dem Code, den Sie oben gezeigt haben, absolut zu. Mit GlobalConfiguration.Configuration.Filters.Add wird es für jede Aktion möglich, und das NoLog-Attribut filtert diejenigen heraus, die wir nicht möchten. Ein Kollege hat eine Lösung mit HttpModule, die jede Anfrage abfängt. Aber für ein MVC-Projekt ist es natürlicher. Vielen Dank und ein frohes neues Jahr für Sie. Als Antwort markiert! –

2

Für alle noch, den Grund lauerte ich Ninject war, so konnte ich injizieren einen Logger (oder etwas anderes) in die Abfangjäger, aber ich wollte abfangen alle Aktionen verwenden wollte.

Mark Antwort ist perfekt, aber statt sie global Registrierung mit

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

binden Ihre Filter mit Ninject mit

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

Sie benötigen eine entsprechende contructor in der TimingActionFilter Klasse zu erstellen.