ASP.NET MVC3控制器AOP代理不拦截所有方法,仅拦截IController.Execute

发布于 2024-12-28 04:15:41 字数 3377 浏览 0 评论 0 原文

我有一个包含多个层的项目 - 其中包括 Web 前端(ASP.NET MVC3)和服务后端(主要是业务逻辑)。该项目已经进行了几个月,所以一切都按预期进行。现在,我尝试使用自定义 [Log] 属性向某些 MVC3 控制器方法添加日志记录方面。

我正在使用 Castle Windsor 进行依赖注入。为了获得日志记录方面,我通过 Castle DynamicProxy /" rel="nofollow">快照。正在使用 WindsorControllerFactory 来自 Krzysztof Koźmic 的有用教程 - 但我修改了它以查找控制器的默认界面(见下文)。

在我的服务层中:

[Log(LoggingLevel.Info)]
public void Save(MyBusinessDto dto)
{
    // business logic and other checks

    this.repository.Save(mbo);
}

在我的 Web 前端的 IWindsorInstaller 中控制器:

private static BasedOnDescriptor FindControllers()
{
    return AllTypes
            .FromThisAssembly()
            .BasedOn<IController>()
            .WithService.DefaultInterface();
}

在我的(稍微定制的)WindsorControllerFactory中,它查找控制器的默认接口:

protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(404, string.Format(Error404, requestContext.HttpContext.Request.Path));
    }

    string controllerName = controllerType.Name;
    string defaultInterfaceName = 'I' + controllerName;
    Type defaultInterface = controllerType.GetInterface(defaultInterfaceName);

    object controller = this.kernel.Resolve(defaultInterface);

    return (IController)controller;
}

在我的控制器中:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

这在服务项目中一切正常,但在控制器中这些方法没有被拦截。

  • 我已确认 WindsorControllerFactory 返回代理控制器。
  • 我已确认控制器已注册拦截器。
  • 我已经确认 SNAP 中的 MasterProxy 会拦截控制器 - 但它仅拦截 IController.Execute(RequestContext requestContext)。

如何拦截具有我的 [Log] 属性的所有控制器方法?

更新 1:我考虑过直接使用 DynamicProxy 而不是 SNAP,但这对于获取它来说是次要的也适用于控制器。

更新 2+4:似乎 SNAP 从 github 中消失了 回到 github

更新 3:这是我在中断 WindsorControllerFactory 时在 Visual Studio 调试器中看到的内容(见上文)。检查的 controller 变量是返回给 MVC 的内容,并且它确实是被代理的。

  • 控制器 {Castle.Proxies.IMyBusinessControllerProxy}
    • __interceptors {Castle.DynamicProxy.IInterceptor[1]}
      • [0] {Snap.MasterProxy}
    • __target {My.Business.Web.Controllers.MyBusinessController}
      • 服务 {Castle.Proxies.IMyBusinessServiceProxy}
      • (其他构造函数注入)
    • MyInjectedProperty {My.Business.Useful.MyOtherType}

I have a project with several layers - among them the web front end (ASP.NET MVC3) and the service back end (mainly business logic). The project is a few months old, so everything is working as expected. Now I am trying to add a logging aspect to some of the MVC3 controller methods using custom [Log] attributes.

I am using Castle Windsor for dependency injection. To get a logging aspect I leverage Castle DynamicProxy through SNAP. Controllers are being resolved using WindsorControllerFactory from Krzysztof Koźmic's helpful tutorial - but I modified it to look for the default interface for the controller (see below).

In my service layer:

[Log(LoggingLevel.Info)]
public void Save(MyBusinessDto dto)
{
    // business logic and other checks

    this.repository.Save(mbo);
}

In my web front end's IWindsorInstaller for controllers:

private static BasedOnDescriptor FindControllers()
{
    return AllTypes
            .FromThisAssembly()
            .BasedOn<IController>()
            .WithService.DefaultInterface();
}

In my (slightly customized) WindsorControllerFactory that looks for the default interface for the controller:

protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(404, string.Format(Error404, requestContext.HttpContext.Request.Path));
    }

    string controllerName = controllerType.Name;
    string defaultInterfaceName = 'I' + controllerName;
    Type defaultInterface = controllerType.GetInterface(defaultInterfaceName);

    object controller = this.kernel.Resolve(defaultInterface);

    return (IController)controller;
}

In my controllers:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

This all works fine in the service project, but in the controllers the methods are not being intercepted.

  • I have confirmed that the WindsorControllerFactory returns proxied controllers.
  • I have confirmed that the controllers have the interceptor registered.
  • I have confirmed that the MasterProxy in SNAP intercepts the controller - but it only intercepts IController.Execute(RequestContext requestContext).

How can I intercept all controller methods that have my [Log] attribute?

Update 1: I have considered using DynamicProxy directly instead of SNAP, but this is secondary to getting it to work for controllers as well.

Update 2+4: It seems that SNAP is missing from github back on github.

Update 3: This is what I see in the Visual Studio debugger when breaking in the WindsorControllerFactory (see above). The inspected controller variable is what is returned to MVC, and it is indeed proxied.

  • controller {Castle.Proxies.IMyBusinessControllerProxy}
    • __interceptors {Castle.DynamicProxy.IInterceptor[1]}
      • [0] {Snap.MasterProxy}
    • __target {My.Business.Web.Controllers.MyBusinessController}
      • service {Castle.Proxies.IMyBusinessServiceProxy}
      • (other contructor injections)
    • MyInjectedProperty {My.Business.Useful.MyOtherType}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

天生の放荡 2025-01-04 04:15:41

IController GetControllerInstance(...)中,不提供接口代理,而是使用虚拟方法提供类代理。

IController GetControllerInstance(...) 返回的控制器将不会通过代理的 IMyBusinessController 接口访问,而是从 IController 转换为实际的控制器的类;例如MyBusinessController。请改用类代理,以使 MVC3 的强制转换返回代理。另外,将方法标记为虚拟,否则拦截代理将无法拦截方法调用并检查自定义属性。

在控制器中,将 virtual 添加到带有属性的方法中:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public virtual ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

为什么只有 Execute(...) 被拦截? IController< /code> 接口仅包含Execute(...)。在返回的控制器接口代理上调用execute,因此可以拦截。但是,一旦 MVC3 的内部 ControllerBase.Execute(...) 获得调用,它就会执行从 ControllerFactory 期望的类的转换。

该问题类似于 this 泄漏,因为两者都绕过了接口代理。我想这个问题可以通过多种方式解决;也许通过创建自定义类型转换器、从工厂中的接口代理目标创建类代理、巧妙的 Windsor 配置等。

Krzysztof Koźmic 的 IController 安装程序WindsorControllerFactory 应该开箱即用。从更大的角度来看,可能会推荐使用接口代理(并且在控制器中使用拦截器之前它们工作得很好),但在这种情况下,可能有理由不走那么远,以避免进一步的副作用。

感谢马吕斯为我指明了正确的方向!

In IController GetControllerInstance(...), don't serve interface proxies, serve class proxies with virtual methods.

The user-implemented methods in the controller returned from IController GetControllerInstance(...) will not be accessed through the proxied IMyBusinessController interface, but cast from IController to to the actual class of the controller; for example MyBusinessController. Use a class proxy instead, to make MVC3's cast return the proxy. Also, mark methods as virtual, otherwise the intercepting proxy won't be able to intercept the method calls and check for custom attributes.

In the controllers, add virtual to your methods with attributes:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public virtual ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

Why is only Execute(...) intercepted? The IController interface only contains Execute(...). Execute is called on the returned controller interface proxy, thus it can be intercepted. But once MVC3's internal ControllerBase.Execute(...) gets the call, it performs the cast to the class it expected from the ControllerFactory.

The problem is similar to this leaking, in that both bypass the interface proxy. I guess it could be solved in a number of ways; perhaps by creating a custom type converter, creating a class proxy from the interface proxy's target in the factory, a clever Windsor configurations etcetera.

Krzysztof Koźmic's IController installer and WindsorControllerFactory should work out of the box. Interface proxies may be recommended in the bigger picture (and they work well until using interceptors in the controllers) but in this case there might be a reason not to go that far, to avoid further side effects.

Thanks to Marius for pointing me in the right direction!

≈。彩虹 2025-01-04 04:15:41

由于 DynamicProxy (SNAP 使用动态代理)无法拦截非虚拟方法,我猜测返回的代理是控制器的派生类,因此非虚拟方法将被忽略。您要么需要使 SNAP(虽然不知道这是如何工作的)返回带有目标(您的实现)的接口代理,要么只是尝试使您的控制器方法虚拟。

Since DynamicProxy (SNAP uses dynamicproxy) can't intercept non-virtual methods I am guessing that the returned proxy is a derived class of your controller and thus, the non virtual methods are ignored. You either need to make SNAP (don't know how this works though) return an interface proxy with target (your implementation) or simply try to make your controller methods virtual.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文