2012-10-31 135 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