我有一个UnitOfWork属性,如下所示:
public class UnitOfWorkAttribute : ActionFilterAttribute
{
public IDataContext DataContext { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Controller.ViewData.ModelState.IsValid)
{
DataContext.SubmitChanges();
}
base.OnActionExecuted(filterContext);
}
}
如您所见,它具有DataContext
属性,由Castle.Windsor注入。 DataContext
具有PerWebRequest的生活方式 - 意味着每个请求都会重复使用单个实例。
事情是,我不时在这个属性中得到DataContext is Disposed
异常,我记得ASP.NET MVC 3试图以某种方式缓存动作过滤器,所以这可能导致问题吗?
如果是这样,如何解决问题 - 不使用任何属性并尝试在方法中使用ServiceLocator?
是否有可能告诉ASP.NET MVC在缓存过滤器时不缓存过滤器?
答案 0 :(得分:2)
我强烈建议不要使用这样的结构。原因有两个:
OnActionExecuted
方法中)是否确实可以安全地提交数据。特别是第三点应引起你的注意。模型有效的这一事实并不意味着可以提交数据上下文的更改。看看这个例子:
[UnitOfWorkAttribute]
public View MoveCustomer(int customerId, Address address)
{
try
{
this.customerService.MoveCustomer(customerId, address);
}
catch { }
return View();
}
当然这个例子有点幼稚。你几乎不会吞下每一个例外,这是完全错误的。但它的作用表明,当不应保存数据时,动作方法很有可能成功完成。
但除此之外,提交事务确实是MVC的问题,如果你决定它,你是否还想用这个属性装饰所有的动作方法。如果您只是实现这一点而不必在Controller级别执行任何操作,那会不会更好?因为,您要在此之后添加哪些属性?授权属性?记录属性?追踪属性?它停在哪里?
您可以尝试的是为需要在事务中运行的所有业务操作建模,以允许您动态添加此行为,而无需更改任何现有代码或在整个地方添加新属性。一种方法是为这些业务操作定义接口。例如:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
使用此界面,您的控制器将如下所示:
private readonly ICommandHandler<MoveCustomerCommand> handler;
// constructor
public CustomerController(
ICommandHandler<MoveCustomerCommand> handler)
{
this.handler = handler;
}
public View MoveCustomer(int customerId, Address address)
{
var command = new MoveCustomerCommand
{
CustomerId = customerId,
Address = address,
};
this.handler.Handle(command);
return View();
}
对于系统中的每个业务操作,您可以定义一个类(DTO和Parameter Object)。在示例中MoveCustomerCommand
类。该类仅包含数据。实现在一个实现ICommandHandler<MoveCustomerCommand>
的类中定义。例如:
public class MoveCustomerCommandHandler
: ICommandHandler<MoveCustomerCommand>
{
private readonly IDataContext context;
public MoveCustomerCommandHandler(IDataContext context)
{
this.context = context;
}
public void Handle(MoveCustomerCommand command)
{
// TODO: Put logic here.
}
}
这看起来像是一个非常多的无用的代码,但这实际上非常有用(如果你仔细观察,它实际上不是那么多额外的代码)。
有趣的是,您现在可以定义一个处理系统中所有命令处理程序的事务的装饰器:
public class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly IDataContext context;
private readonly ICommandHandler<TCommand> decoratedHandler;
public TransactionalCommandHandlerDecorator(IDataContext context,
ICommandHandler<TCommand> decoratedHandler)
{
this.context = context;
this.decoratedHandler = decoratedHandler;
}
public void Handle(TCommand command)
{
this.decoratedHandler.Handle(command);
this.context.SubmitChanges();
}
}
这不比你的UnitOfWorkAttribute
多得多,但区别在于这个处理程序可以包含在任何实现中并注入到任何控制器中,而控制器无需知道这一点。并且在执行命令后直接确实是您实际知道是否可以保存更改的唯一安全位置。
您可以在本文中找到有关此应用程序设计方法的更多信息:Meanwhile... on the command side of my architecture
答案 1 :(得分:0)
今天,我有一半意外地找到了问题的原始问题
正如从问题中看到的那样,过滤器具有由Castle.Windsor
注入的属性,因此使用ASP.NET MVC
的人知道,为了实现这一点,您需要实现IFilterProvider
,这将能够使用IoC容器进行依赖注入。
所以我开始考虑它的实现,并注意到它来自FilterAttributeFilterProvider
而FilterAttributeFilterProvider
有构造函数:
public FilterAttributeFilterProvider(bool cacheAttributeInstances)
因此,您可以控制是否缓存属性实例。
禁用此缓存后,网站被NullReferenceExceptions
炸毁了,所以我能够找到另外一件事,这被忽略了,造成了不良的副作用。
事情是,在我们添加Castle.Windsor
过滤器提供程序后,原始过滤器未被删除。因此,当启用缓存时,IoC过滤器提供程序正在创建实例,默认过滤器提供程序正在重用它们,并且所有依赖项属性都填充了值 - 这一点并不明显,除了事实上,在禁用缓存后,过滤器运行了两次,默认情况下需要提供者自己创建实例,因此依赖属性未填充,这就是NullRefereceExceptions
发生的原因。