2010-08-25 120 views
6

在我的ASP.NET MVC應用程序,我有充當模板爲幾個不同的視圖模型的接口:傳遞接口到ASP.NET MVC控制器操作方法

public interface IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    Validator Validate(); 
} 

所以,我的觀點模型被這樣定義:

public interface MyViewModel1 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel1 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

public interface MyViewModel2 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel2 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

然後我現在有一個單獨的控制器動作做用於每個不同類型的驗證,使用模型綁定:

[HttpPost] 
public ActionResult MyViewModel1Validator(MyViewModel1 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

[HttpPost] 
public ActionResult MyViewModel2Validator(MyViewModel2 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

這很好 - 但如果我有30個不同的視圖模型類型,那麼將不得不有30個單獨的控制器操作,所有相同的代碼除了方法簽名,這似乎是不好的做法。

我的問題是,我如何合併這些驗證操作,以便我可以傳遞任何類型的視圖模型並調用它的Validate()方法,而不必關心它是哪種類型?

起初,我嘗試使用接口本身作爲動作參數:

public ActionResult MyViewModelValidator(IMyViewModel model)... 

但這並不起作用:我得到一個Cannot create an instance of an interface例外。我認爲模型的一個實例會被傳遞給控制器​​動作,但顯然情況並非如此。

我確定我缺少一些簡單的東西。或者我剛剛接近這一切都是錯誤的。誰能幫我嗎?

回答

10

你無法使用接口的原因是因爲序列化。當一個請求進來它只包含表示對象的字符串鍵/值對:

"Client1.Name" = "John" 
"Client2.Name" = "Susan" 

當動作方法被調用的MVC運行時嘗試創造價值填充方法的參數(通過一個稱爲模型工藝結合)。它使用參數的類型來推斷如何創建它。正如您已經注意到的那樣,該參數不能是接口或任何其他抽象類型,因爲運行時無法創建它的實例。它需要一個具體的類型。

如果你想刪除重複的代碼,你可以寫一個幫手:

[HttpPost]   
public ActionResult MyViewModel1Validator(MyViewModel1 model)   
{   
    return ValidateHelper(model);   
}   

[HttpPost]   
public ActionResult MyViewModel2Validator(MyViewModel2 model)   
{   
    return ValidateHelper(model);   
} 

private ActionResult ValidateHelper(IMyViewModel model) { 
    var validator = model.Validate();   

    var output = from Error e in validator.Errors   
       select new { Field = e.FieldName, Message = e.Message };   

    return Json(output); 
} 

但是,你仍然需要爲每個模型類型不同的操作方法。也許還有其他方法可以重構你的代碼。看來你的模型類唯一的區別就是validataion行爲。你可以找到一種不同的方法來在模型類中對驗證類型進行編碼。

+0

我完全是因爲時間限制而採用這種方法,但是您還解釋了爲什麼當對象傳入控制器時它不是一個實例,這對了解這一點非常有用。 – 2010-08-26 12:16:04

1

您可以考慮使用基類而不是接口。

+0

這會導致類似的錯誤消息。它也不能創建一個抽象類型的實例。 – 2014-08-20 16:52:59

+1

你可以使用非抽象基類。 – 2014-08-25 12:15:57

1

我想我會創建一個實現IMyViewModel的抽象基類。我將驗證一個抽象方法,並要求覆蓋從MyAbstractViewModel繼承的具體視圖模型。在控制器內部,如果需要,可以使用IMyViewModel接口,但綁定和序列化確實需要一個具體的類來綁定。我的$ .02。

+1

我應該添加一個額外的評論 - 我實際上不會將抽象類傳遞給控制器​​,而是將具體類傳遞給控制器​​。你可能有一個驗證服務或其他一些可以處理抽象類或接口的方法,但爲了清楚起見,你應該真正期待你在控制器中獲得什麼。 – Hal 2010-08-25 17:10:45

4

您可以檢查:http://msdn.microsoft.com/en-us/magazine/hh781022.aspx

這是由於DefaultModelBinder無法知道IMyViewModel應創建的具體類型。 對於解決方案,您可以創建自定義模型聯編程序並指示如何創建和綁定接口實例。

+0

+1恕我直言這是走的路,勝過所有其他答案(其中一些顯然還沒有在現實世界中測試過)。 – 2014-08-20 17:08:12

相關問題