2012-10-31 72 views
3

我有一个的UnitOfWork属性,像这样:ASP.NET MVC 3缓存过滤器吗?

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试图以某种方式缓存动作过滤器,所以可能会导致问题?

如果是这样,如何解决这个问题 - 不使用任何属性,并试图用里面的方法服务定位?

是否可以告诉ASP.NET MVC如果它不缓存它不缓存过滤器?

回答

2

我会强烈反对忠告使用这样的结构。由于几个原因:

  1. 它不是控制器(或在控制器装饰属性上)提交数据上下文的责任。
  2. 这会导致大量重复的代码(您将不得不使用该属性修饰大量方法)。
  3. 同时在执行(在OnActionExecuted法)该点是否是实际安全提交数据。

特别是第三点,应该已经引起你的注意。模型是有效的这一事实并不意味着提交数据上下文的变化是可以的。看看这个例子:

[UnitOfWorkAttribute] 
public View MoveCustomer(int customerId, Address address) 
{ 
    try 
    { 
     this.customerService.MoveCustomer(customerId, address); 
    } 
    catch { } 

    return View(); 
} 

当然这个例子有点幼稚。你几乎不会吞下每一个异常,那简直是错误的。但它所表现的是,当数据不应被保存时,动作方法很可能成功完成。

但除此之外,提交事务真的是MVC的一个问题,如果你决定这个问题,你是否仍然想用这个属性来修饰所有的操作方法。如果你只是在不需要在控制器层面上做任何事情的情况下实现这一点,会不会更好?因为,在这之后你会添加哪些属性?授权属性?记录属性?追踪属性?它停在哪里?

您可以尝试的方法是对所有需要在事务中运行的业务操作进行建模,以允许您动态添加此行为,而无需更改任何现有代码或在整个过程中添加新属性地点。一种方法是为这些业务操作定义一个接口。例如:

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(); 
} 

对于系统中的每个业务操作你定义一个类(一个DTOParameter 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

+0

很好的答案,博客文章,这需要重写整个应用程序,所以它可能不会在目前的答案,但它提供了很好的食物的想法。 – Giedrius

+0

对于我而言,如果没有看到您的应用程序代码,我很难说这些,但在大多数情况下,添加此设计并不难。您可以逐步介绍它,并通过让处理程序简单地传递数据开始。然而,这可能是一个很大的转变。 – Steven

+1

也google搜索“不使用CQRS”给出了很多的想法也一样,所以我想我需要自己试试吧,看看它如何适应。 – Giedrius

0

今天我一半无意中发现的问题原来的问题。
从问题中可以看出,过滤器具有属性,即由Castle.Windsor注入,因此那些使用ASP.NET MVC的人知道,为了工作,您需要具有IFilterProvider实现,这将能够使用IoC容器进行依赖注射。

所以我开始看它的实现,并注意到,它从FilterAttributeFilterProvider derrived和FilterAttributeFilterProvider具有构造:

public FilterAttributeFilterProvider(bool cacheAttributeInstances) 

所以,你可以控制缓存或不是你的属性实例。

禁用此缓存后,网站与NullReferenceExceptions吹,所以我能找到一两件事,那被忽略并导致不良副作用。

事情是,原来的过滤器没有被删除,我们添加了Castle.Windsor过滤器提供商。因此,执行高速缓存,在启用时,国际奥委会过滤器供应商处创建实例和缺省过滤器供应商被重复使用它们,所有依赖属性充满了价值 - 这是不清晰醒目,除了事实上,过滤器的两倍运行,缓存之后被禁用,默认提供者需要通过它创建实例,所以依赖项属性没有填充,这就是为什么发生了NullRefereceExceptions