【问题标题】:How to intercept all the ASP.NET WebApi controller action methods calls with Ninject interception for logging?如何使用 Ninject 拦截来拦截所有 ASP.NET WebApi 控制器操作方法调用以进行日志记录?
【发布时间】:2023-03-23 20:03:01
【问题描述】:

每次调用我们的 ASP.NET WebApi 控制器的一种操作方法时,我们公司都需要记录某些事情。由于我们现在将 Ninject 用于 DI,因此我们也希望将它用于此目的。这是我迄今为止尝试过的。

我通过 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());

然而,每当有一个动作方法的调用进入时,它就会抛出一个错误,说

Cannot instantiate proxy of class: MyApiController.

请有经验的人指出我做错了什么?谢谢。

【问题讨论】:

  • 也许这个建议在设计上是一个太大的转变,但是当您将所有业务操作抽象到一个通用抽象背后并将其注入控制器时,您将能够轻松装饰或拦截这些抽象以添加横切关注点,例如您的时间方面。查看command/handler pattern 了解我在说什么。
  • 你有没有额外的无参数构造函数,所有的动作方法都是虚拟的吗?
  • 感谢 Remo,我在所有操作方法中添加了 virtual,但是对于无参数构造函数,我遇到了问题,因为我们的服务对象被注入那里并在控制器中使用。使用无参数构造函数,服务对象为空。你如何解决这个问题?非常感谢!

标签: asp.net-mvc logging asp.net-web-api ninject ninject-interception


【解决方案1】:

更新

因此,使用您的代码和 Remo 关于需要将操作方法​​设为虚拟并放入一个空的默认构造函数(只是为了安抚动态代理,保持您的其他构造函数仍然存在)这两个方面我都有动作过滤器和拦截方法工作。

我想说,就目前而言,您的代码将拦截 ApiController 上可能不需要的方法,因此您可能还需要放置一些代码来过滤掉这些方法,例如ExecuteAsync 和 Dispose。

我唯一的另一点是性能。 巨大的免责声明这些只是非常基本的测试(每次使用动作过滤器方法来记录统计数据),我邀请您自己做(!)...但是使用我得到的 DynamicProxy 拦截器每个获取请求大约需要 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 的可能性?这是 AOP 在 MVC 操作上的自然扩展点。

如果您对控制器上的实际操作以外的方法感兴趣,那么我会理解,但我想我还是会提出建议。

灵感来自 Are ActionFilterAttributes reused across threads? How does that work?Measure Time Invoking ASP.NET MVC Controller Actions

更新以显示在方法标记时排除计时器。来自核心 WebApi 框架的灵感,特别是 AllowAnonymousAttributeAuthorizeAttribute

全局注册 this 以便所有操作都被 this 监控:

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()
    {
       //
    }
}

【讨论】:

  • +1 嗨,马克,感谢您的回答。但我的经理不想使用操作过滤器属性。他希望记录所有操作方法,除了我们标记属性 [nolog] 的方法。
  • @ray247,感谢您让我提出建议。为了一分钱,为了一英镑,我也更新了使用这种方法来展示 NoLog 能力。我也会很快用一些 Ninject 例子来更新答案。
  • 非常感谢马克。我还没有尝试过你的代码,但我绝对同意你上面显示的代码。 GlobalConfiguration.Configuration.Filters.Add 将使每个动作都发生,NoLog 属性将过滤掉我们不想要的那些。一位同事提出了一个带有 HttpModule 的解决方案,可以拦截每个请求。但是你的对于 MVC 项目来说更自然。谢谢你,新年快乐。标记为答案!
  • @MarkJones 请问你能帮我解决这个问题吗stackoverflow.com/questions/24235617/…
  • 如果你能走这条路,这听起来像是 OWIN 中间件的好案例。
【解决方案2】:

对于仍在潜伏的任何人,我想使用 Ninject 的原因是我可以将记录器(或其他任何东西)注入拦截器,但我想拦截所有操作。

马克的回答很完美,但不是使用全局注册

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

使用 Ninject 绑定你的过滤器

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

您需要在 TimingActionFilter 类中创建一个适当的构造函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-08
    • 2017-03-14
    • 2018-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-18
    • 2012-11-04
    相关资源
    最近更新 更多