使用ASP.NET MVC,从FormCollection中填充ViewModel

7

我有很多类似的ViewModel:

public class RequestForSalaryVM : StatementViewModel
{
  // RequestForSalaryVM properties
}

public class ReliefVM : StatementViewModel
{
  // ReliefVM properties
}

以及许多类似的方法:

[HttpPost]
public ActionResult SaveRelief(User currentUser, ReliefVM statement)
{
    ReliefVM model = (ReliefVM)SaveModel(currentUser, statement);
    if (model == null)
        return RedirectToAction("List");
    return View("Relief", model);
}

[HttpPost]
public ActionResult SaveRequestForSalary(User currentUser, RequestForSalaryVM statement)
{
    RequestForSalaryVM model = (RequestForSalaryVM)SaveModel(currentUser, statement);
    if (model == null)
        return RedirectToAction("List");
    return View("RequestForSalary", model);
}

我希望获得类似于这样的东西:
[HttpPost]
public ActionResult SaveStatement(User currentUser, FormCollection statement, string ViewModelName)
{
  Assembly assembly = typeof(SomeKnownType).Assembly;
  Type type = assembly.GetType(ViewModelName);
  object ViewModel = Activator.CreateInstance(type);

  //Fill ViewModel from FormCollection  <= how can I use asp.net mvc binding for this?
  //I do not want to create their own implementation of asp.net mvc binding 
    return View(ViewModelName, ViewModel);
}
4个回答

9

您可以尝试使用Controller.UpdateModelController.TryUpdateModel方法:

[HttpPost]
public ActionResult SaveStatement(User currentUser, FormCollection statement, string ViewModelName)
{
    ...
    object ViewModel = Activator.CreateInstance(type);
    if (TryUpdateModel(viewModel))
    {
        // save the ViewModel
    }   

    return View(ViewModelName, ViewModel);
}

然而,我建议您创建一个自定义的ModelBinder,因为它有责任创建和填充模型属性。

我可以给您展示一个简单的例子,说明如何实现:

基本ViewModel

public abstract class StatementViewModel
{
    public abstract StatementType StatementType { get; }
    ...
}

public enum StatementType
{
    Relief,
    RequestForSalary,
    ...
}

视图模型

public class RequestForSalaryVM : StatementViewModel
{
    public override StatementType StatementType
    {
        get { return StatementType.RequestForSalary; }
    }
    ...
}

public class ReliefVM : StatementViewModel
{
    public override StatementType StatementType
    {
        get { return StatementType.Relief; }
    }
    ...
}

模型绑定器
public class StatementModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var statementTypeParameter = bindingContext.ValueProvider.GetValue("StatementType");
        if (statementTypeParameter == null)
            throw new InvalidOperationException("StatementType is not specified");

        StatementType statementType;
        if (!Enum.TryParse(statementTypeParameter.AttemptedValue, true, out statementType))
            throw new InvalidOperationException("Incorrect StatementType"); // not sure about the type of exception

        var model = SomeFactoryHelper.GetStatementByType(statementType); // returns an actual model by StatementType parameter
                                                                         // this could be a simple switch statement
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
        bindingContext.ModelMetadata.Model = model;
        return model;
    }
}

Global.asax中注册模型绑定程序:

ModelBinders.Binders.Add(typeof(StatementViewModel), new StatementModelBinder());

控制器

[HttpPost]
public ActionResult Index(StatementViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        // save the model
    }
    return View(viewModel);
}

2
您可以使用如下的 CustomModelBinder 来解决该问题:
public class StatementVMBinder : DefaultModelBinder
{
    // this is the only method you need to override:
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        if (modelType == typeof(StatementViewModel)) // so it will leave the other VM to the default implementation.
        {
            // this gets the value from the form collection, if it was in an input named "ViewModelName":
            var discriminator = bindingContext.ValueProvider.GetValue("ViewModelName");
            Type instantiationType;
            if (discriminator == "SomethingSomething")
                instantiationType = typeof(ReliefVM);
            else // or do a switch case
                instantiationType = typeof(RequestForSalaryVM);

            var obj = Activator.CreateInstance(instantiationType);
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
            bindingContext.ModelMetadata.Model = obj;
            return obj;
        }
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

您的操作需要此签名:

public ActionResult SaveStatement(User currentUser, StatementViewModel viewModel)

但是你在方法中收到的viewModel将会是适当派生类型的,所以你应该能够像在各个方法中一样进行强制转换。

唯一剩下的就是在Global.asax中注册自定义绑定器。


1
你是否尝试使用UpdateModel或TryUpdateModel从表单集合中初始化模型值?请查看下面的代码示例。
[HttpPost]
public ActionResult SaveStatement(User currentUser, FormCollection statement, string    ViewModelName)
{
  Assembly assembly = typeof(SomeKnownType).Assembly;
  Type type = assembly.GetType(ViewModelName);
  object ViewModel = Activator.CreateInstance(type);

  if (!TryUpdateModel(ViewModel, statement.ToValueProvider()))
  {
     //some another actions
  }

  return View(ViewModelName, ViewModel);
}

0
如果我是你,我会使用一个DTO(数据传输对象)来封装视图的名称和通过接口访问的ViewModel。然后你就有了这样的东西:
[HttpPost]
public ActionResult SaveStatement(User currentUser, VMWrapper wrapper)
{
    IVM model = SaveModel(currentUser, wrapper.Statement);
    if (model == null)
        return RedirectToAction("List");
    return View(wrapper.ViewName, model);
}

但这假设您的视图可以处理VM之间的差异...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接