2009-09-09 78 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