我目前有一个自定义PrototypingControllerFactory
,它会针对当前请求调用的操作方法查找自定义[Prototype]
属性,并且根据属性是否存在将注入不同的实现接口ISomeService
。 (在这种情况下,ISomeService
或多或少地抽象了消息传递服务,因此模拟实现允许在实际实现尚未准备好处理特定消息时返回" canned"结果。)
例如,如果我有一个类似的控制器类:
public class MyController : Controller
{
private readonly ISomeService _someService;
public MyController(ISomeService someService /*, .... other dependencies */
{ _someService = someService; //... etc }
public ActionResult Action1()
{
//...
_someService.SomeMethod();
//...
}
[Prototype]
public ActionResult Action2()
{
//...
_someService.SomeMethod();
//...
}
}
然后,当从http请求调用Action1
时,_someService
应该使用ISomeService
的生产实现,但是当Action2
被调用时,_someService
应该有ISomeService
的模拟版本。
从严格的设计角度来看,我意识到这可能表明在特定控制器中有太多动作(否则,例如,我可能只是将整个控制器标记为[Prototype]
),但是由于项目惯性,我宁愿不尝试强制改变操作在控制器中的位置。
目前autofac注册具有以下内容:
builder.RegisterControllers(Assembly.GetExecutingAssembly());
if (ConfigurationManager.AppSettings["AllowPrototyping"] == "true")
{
builder.RegisterType<PrototypingControllerFactory>().As<IControllerFactory>().InstancePerRequest();
}
然而,这意味着控制器工厂必须做一些奇特的工作来找出构造函数参数,并从DI容器中获取实例。最近,我发现了自定义控制器工厂和&#34; real&#34;之间的一些细微差别。控制器工厂是不可取的。
我想取消自定义控制器工厂,而是让autofac完全处理控制器的分辨率。
如何告诉autofac解析接口的不同实现,具体取决于当前正在执行的操作是否使用我的自定义[Prototype]
属性进行修饰?
答案 0 :(得分:1)
第一个选项取决于可靠地直接从路径数据确定控制器动作方法,这不一定是直截了当的。部分问题是,即使在授权过滤器运行之前,控制器的创建(以及因此注入依赖关系)也会在过程的早期发生。
如果一个神奇方法的某些可靠的实现说实际上存在ActionDescriptor GetActionDescriptor(RoutData routeData)
,那么我可以执行以下操作:
builder.Register<ISomeService>(c =>
{
var httpRequest = c.Resolve<HttpRequestBase>();
var actionDescriptor = GetActionDescriptor(httpRequest.RequestContext.RouteData);
if (actionDescriptor.GetAttributes<PrototypeAttribute>().Any())
{
return new PrototypeSomeService();
}
return new RealSomeService();
}).InstancePerRequest();
然而,我最接近在控制器被实例化的位置获得ActionDescriptor
是最重要的DefaultControllerFactory
,这正是我想要摆脱的。
从默认控制器工厂,您可以使用受保护的DefaultControllerFactory.GetControllerType()
,然后您可以从中创建ReflectedControllerDescriptor
。但是你仍然需要进行一些调整以从controllerDescriptor.GetCanonicalActions()
获取正确的动作描述符。 (事实上,我怀疑这种“混乱”导致原始自定义控制器工厂的微妙差异)。当从其他http处理程序使用路由时,这可能会变得更加复杂(例如,想想Elmah或MiniProfiler)。
最后,我选择通过RouteData
继承ActionDescriptor
来避免尝试将[PrototypeAttribute]
映射到ActionFilterAttribute
,以便我可以轻松加入OnActionExecuting
从那时起,我为当前的HttpContext添加了一个标识符,例如
public class PrototypeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Items.Add("Prototype", "Prototype");
base.OnActionExecuting(filterContext);
}
}
然后我修改了我的PrototypeSomeService
,以便它包含ISomeService
的实现,如果上下文不包含原型键,则委托给它,例如。
public class PrototypeSomeService : ISomeService
{
private readonly ISomeService _wrappedService;
private readonly HttpRequestBase _httpRequest;
public PrototypeSomeService(ISomeService wrappedService, HttpRequestBase httpRequest)
{
_wrappedService = wrappedService;
_httpRequest = httpRequest
}
public object SomeMethod()
{
if(_httpRequest.RequestContext.HttpContext.Items.Contains("Prototype"))
return _wrappedService.SomeMethod();
//other prototype logic...
return prototypeResult;
}
}
将所有这些组合在一起的最后一块是使用autofac的装饰器功能:
var someServiceRegistration = builder.RegisterType<SomeService>().InstancePerRequest();
if (ConfigurationManager.AppSettings["AllowPrototyping"] == "true")
{
someServiceRegistration.Named<ISomeService>("Prototype");
builder.RegisterDecorator<ISomeService>(
(c, inner) => new PrototypeSomeService(inner, c.Resolve<HttpRequestBase>()),
fromKey: "Prototype"
);
}
else
{
someServiceRegistration.As<ISomeService>();
}
一个小缺点是你必须确保使用httpcontext项的唯一键,否则会发生奇怪的事情,但是很容易避免这种情况。使用guid。
这种方法允许我保留大部分现有代码不变,只修改服务的原型实现和autofac注册。
您可以在以下位置阅读有关autofac装饰器的更多信息: