2009-09-09 95 views
6

的ASP.NET MVC UpdateModel我想讓UpdateModel填充一個在編譯時只被設置爲一個接口的模型。例如,我有:帶有接口

// View Model 
public class AccountViewModel { 
    public string Email { get; set; } 
    public IProfile Profile { get; set; } 
} 

// Interface 
public interface IProfile { 
    // Empty 
} 

// Actual profile instance used 
public class StandardProfile : IProfile { 
    public string FavoriteFood { get; set; } 
    public string FavoriteMusic { get; set; } 
} 

// Controller action 
public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is populated already 
    UpdateModel(viewModel.Profile, "Profile"); // This isn't working. 
} 

// Form 
<form ... > 
    <input name='Email' /> 
    <input name='Profile.FavoriteFood' /> 
    <input name='Profile.FavoriteMusic' /> 
    <button type='submit'></button> 
</form> 

另外請注意,我有一個從DefaultModelBinder繼承使用自定義模型粘合劑用於填充IProfile與在被覆蓋的CreateModel方法的StandardProfile的實例。

問題是FavoriteFood和FavoriteMusic從不填充。有任何想法嗎?理想情況下,這將全部在模型聯編程序中完成,但是我不確定在沒有編寫完全自定義的實現的情況下是可能的。

感謝布

回答

2

我會檢查ASP.NET MVC編碼(DefaultModelBinder),但我猜測,它反映的類型IProfile,而不是實例,StandardProfile。

因此,它會查找任何可以嘗試綁定的IProfile成員,但它的空接口,所以它認爲自己完成。

你可以嘗試像更新的BindingContext和改變ModelType到StandardProfile,然後調用

bindingContext.ModelType = typeof(StandardProfile); 
IProfile profile = base.BindModel(controllerContext, bindingContext); 

不管怎麼說,有一個空的接口是怪異〜


編輯:只想補充上面的代碼只是僞代碼,您需要檢查DefaultModelBinder以確切地查看您想要寫入的內容。


編輯#2:

你可以這樣做:

public class ProfileModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { 
    { 
     bindingContext.ModelType = typeof(StandardProfile); 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

沒有必要做一個模型粘結劑AccountView,一個工作正常。


編輯#3

測試了它,上述粘合劑的作品,只需要添加:

ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder(); 

你的行動看起來像:

public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is fully populated, including profile, don't call UpdateModel 
} 

您可以使用IOC在設置模型聯編程序時(例如注入類型構造函數)。

+0

空的接口允許我在每個站點上重複使用相同的核心代碼,同時使用IOC提供不同的配置文件類型。我可能能夠嘗試一個基類而不是一個接口來達到這個目的,但我只是不確定我還能做什麼,這給了我所尋找的靈活性。我會研究你提到的ModelType。 – 2009-09-09 17:35:58

+0

空的基類對你來說也不會有太大的好處。因此,在您的CreateModel方法的代碼中,您調用的是如下所示的代碼:IoC.GetInstance ()您插入了哪個返回新的StandardProfile?有趣的:)。我仍然不清楚當使用IProfile的任何東西必須首先將它轉換爲正確的類時,您可以獲得多少代碼重用,但是,我認爲在綁定上下文中指定Type將起作用。 – anonymous 2009-09-09 19:24:39

+0

重複使用來自於我的許多站點的控制器都在我參考的常見組件中。我建立的每個站點都引用了這個用於控制器和模型的公共組件然後,我可以爲每個站點添加其他控制器,模型,視圖等。在這種情況下,我需要能夠逐個網站地定義完全不同的配置文件字段。因此這裏只需要一個接口。 – 2009-09-09 19:37:12

0

不檢查的實際類型背後的接口在這裏討論:http://forums.asp.net/t/1348233.aspx

這麼說,我找到了解決該問題的方法的hackish。由於我已經有了這種類型的自定義模型綁定器,因此我可以添加一些代碼來爲我執行綁定。下面是我的模型綁定貌似現在:

public class AccountViewModelModelBinder : DefaultModelBinder 
{ 
    private readonly IProfileViewModel profileViewModel; 
    private bool profileBound = false; 

    public AccountViewModelModelBinder(IProfileViewModel profileViewModel) 
    { 
     this.profileViewModel = profileViewModel; 
    } 

    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // Bind the profile 
     if (profileBound) 
      return; 

     profileBound = true; 

     bindingContext.ModelType = profileViewModel.GetType(); 
     bindingContext.Model = profileViewModel; 
     bindingContext.ModelName = "Profile"; 

     BindModel(controllerContext, bindingContext); 
    } 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) 
    { 
     var model = new AccountViewModel(); 
     model.Profile = profileViewModel; 

     return model; 
    } 
} 

基本上,當模型粘合劑「做」結合主要AccountViewModel,然後我改變結合上下文(由eyston的建議),並再次呼籲BindModel。這然後綁定我的配置文件。請注意,我在profileViewModel上調用GetType(由構造函數中的IOC容器提供)。另請注意,我包含一個標誌來指示配置文件模型是否已被綁定。否則會有一個無限循環的OnModelUpdated被調用。

我不是說這很漂亮,但它的確適用於我的需求。我仍然喜歡聽到其他建議。

+0

見編輯#2 /#3。 – anonymous 2009-09-09 19:58:18