Reputation: 9342
I have the following code:
[HttpGet]
public ActionResult Edit(int req)
{
var viewModel = new EditViewModel();
viewModel.RequestId = int;
return View(viewModel);
}
[HttpPost]
Public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
It works fine: when the edit form is posted, I have the action controller who is called.
Now I modify some little bit my code like this:
[HttpGet]
public ActionResult Edit(int req)
{
var viewModel = new EditViewModel(req);
return View(viewModel);
}
[HttpPost]
Public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
public class EditViewModel()
{
public EditViewModel(int req)
{
requestId = req;
}
...
}
In this new version, I have a view model with a contructor.
This time, when my form is posted back, the action controller is never triggered.
Any idea?
Thanks.
Upvotes: 13
Views: 11498
Reputation: 3267
public class EditViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var req = bindingContext.ValueProvider.GetValue("req");
if (req == ValueProviderResult.None || string.IsNullOrEmpty(req.FirstValue))
{
bindingContext.ModelState.TryAddModelError("req", "Missing req parameter");
}
int reqValue;
if (!int.TryParse(req.AttemptedValue, out reqValue))
{
bindingContext.ModelState.TryAddModelError($"The req parameter contains an invalid value: {req.AttemptedValue}");
}
var model = new EditViewModel(req.FirstValue);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
You don't need to register anything with startup.cs anymore. Just assign the binder to your ViewModel and you're away.
[ModelBinder(BinderType = typeof(EditViewModelBinder))]
public class EditViewModel
Upvotes: 5
Reputation: 1038800
That's normal. The default model binder can no longer instantiate your view model as it doesn't have a parameterless constructor. You will have to write a custom model binder if you want to use view models that don't have a default constructor.
Normally you don't need such custom constructor. You could simply have your view model like that:
public class EditViewModel()
{
public int RequestId { get; set; }
}
and the POST action like that:
[HttpPost]
public ActionResult Edit(EditViewModel viewModel)
{
// some code here...
}
and now all you have to do is POST the requestId
parameter instead of req
and the default model binder will do the job.
And if for some reason you wanted to use a view model with custom constructor, here's an example of how the custom model binder might look like:
public class EditViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var req = bindingContext.ValueProvider.GetValue("req");
if (req == null)
{
throw new Exception("missing req parameter");
}
int reqValue;
if (!int.TryParse(req.AttemptedValue, out reqValue))
{
throw new Exception(string.Format("The req parameter contains an invalid value: {0}", req.AttemptedValue));
}
return new EditViewModel(reqValue);
}
}
which will be registered in your Application_Start
:
ModelBinders.Binders.Add(typeof(EditViewModel), new EditViewModelBinder());
Upvotes: 23