我有一个包含多个层的项目 - 其中包括 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]}
__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]}
__target
{My.Business.Web.Controllers.MyBusinessController}
service
{Castle.Proxies.IMyBusinessServiceProxy}
- (other contructor injections)
MyInjectedProperty
{My.Business.Useful.MyOtherType}
发布评论
评论(2)
在
IController GetControllerInstance(...)
中,不提供接口代理,而是使用虚拟
方法提供类代理。从
IController GetControllerInstance(...)
返回的控制器将不会通过代理的IMyBusinessController
接口访问,而是从IController
转换为实际的控制器的类;例如MyBusinessController
。请改用类代理,以使 MVC3 的强制转换返回代理。另外,将方法标记为虚拟
,否则拦截代理将无法拦截方法调用并检查自定义属性。在控制器中,将
virtual
添加到带有属性的方法中:为什么只有
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 withvirtual
methods.The user-implemented methods in the controller returned from
IController GetControllerInstance(...)
will not be accessed through the proxiedIMyBusinessController
interface, but cast fromIController
to to the actual class of the controller; for exampleMyBusinessController
. Use a class proxy instead, to make MVC3's cast return the proxy. Also, mark methods asvirtual
, 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:Why is only
Execute(...)
intercepted? TheIController
interface only containsExecute(...)
. Execute is called on the returned controller interface proxy, thus it can be intercepted. But once MVC3's internalControllerBase.Execute(...)
gets the call, it performs the cast to the class it expected from theControllerFactory
.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!
由于 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.