Asp.net mvc fill viewModel from FormCollection

I have many similar ViewModel:

public class RequestForSalaryVM : StatementViewModel { // RequestForSalaryVM properties } public class ReliefVM : StatementViewModel { // ReliefVM properties } 

and many similar methods:

 [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); } 

I want to get something like this:

 [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); } 
+7
c # asp.net-mvc model-binding
source share
4 answers

You can try the Controller.UpdateModel or Controller.TryUpdateModel method:

 [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); } 

However, I would suggest you create a custom ModelBinder, as it is responsible for creating and populating the model properties.

I can show you a simple example of how you can do this:

Base viewmodel

 public abstract class StatementViewModel { public abstract StatementType StatementType { get; } ... } public enum StatementType { Relief, RequestForSalary, ... } 

ViewModels

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

ModelBinder

 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; } } 

After that, register the model binder in Global.asax :

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

controller

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

You can probably solve the problem using CustomModelBinder as follows:

 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); } } 

Your action will need this signature:

 public ActionResult SaveStatement(User currentUser, StatementViewModel viewModel) 

but the viewModel that you get in the method will have the corresponding derived type, so you should be able to use it, as you do in the individual methods.

It remains only to register Custom Binder in Global.asax.

+2
source share

Have you tried using UpdateModel or TryUpdateModel to initialize your model values ​​from a collection of forms? Check out the sample code below.

 [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); } 
+1
source share

If I were you, I would use a DTO (Data Transfer Object) object that wrapped the view name and ViewModel, accessed through the interface. You have something like:

 [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); } 

But that suggests that your views can handle the differences between virtual machines ...

0
source share

All Articles