I have a lot of similar ViewModel:
public class RequestForSalaryVM : StatementViewModel
{
// RequestForSalaryVM properties
}
public class ReliefVM : StatementViewModel
{
// ReliefVM properties
}
and a lot of 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);
}
You can try 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 to create a custom ModelBinder, as it is its responsibility to create and populate model properties.
I can show you a simple example how you can achieve 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 the 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);
}
You can probably solve the problem with a CustomModelBinder
like this:
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
you receive in the method will be of the appropriate derived type, so you should be able to cast it as you are doing in the individual methods.
The only thing left is to register the Custom Binder in the Global.asax.
Did you try to use UpdateModel or TryUpdateModel to initialize your model values from form collection? Look at the code example 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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With