Today, one of the colleague asked me a question about how to post a form with a complex object. It is actually a bit tricky. This is because of the nature of HTML form [1]. When a form is submitted for processing, some controls have their name paired with their current value and these pairs are submitted with the form. Those controls for which name/value pairs are submitted are called “successful controls”
HTML defines the following control types:
checkbox– When a form is submitted, only “on” checkbox controls can become “successful control”
Radio button – Radio button are like checkbox.
Input– the input text becomes the control’s current value.
hidden controls– Authors may create controls that are not rendered but whose values are submitted with a form. it generally sued for storing information between client/server exchanges. The Input element is used to create a hidden control.
The problem my colleague had is that he wants to pass the view model to his controller by submitting the form. However, the model is a complex model, after submitting the form. Only those simple type property has values, but value of the property like ICollection is null. As explained above, This is because that it is not a name/value pair value. For solving this issue, we think what if we convert the complex object into a serialized object and set it as a hidden control value. So, after submitting the form, the value will be passed to the server.
Thus, we added the code as below :
@using(Html.BeginForm(…))
{
…
@Html.Hidden("reportViewModel", JsonConvert.SerializeObject(Model))
…
}
Then, the action parameter is expecting a string. However, we still want to use a strong type object. In order to do that, we end up with creating a custom model binder. Thanks to Mike Stall.
Follow the steps below to create custom model binder:
Step 1 – Create Model Binder
public class ReportViewModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string key = bindingContext.ModelName;
ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
if (val != null)
{
string s = val.AttemptedValue as string;
if (s != null){
return JsonConvert.DeserializeObject<ReportViewModel>(s);
}
}
return null;
}
}
Step 2 – Add [ModelBinder] attribute on the parameter’s signature
public FileContentResult ReportSpreadshee([ModelBinder(typeof(ReportViewModelBinder))]ReportViewModel reportViewModel){
…
}
After all the efforts, we finally can pass the complex object with post method, when submitting a form. Please feel free to make a comment, if you have an optimised solution.
References
[1]https://www.w3.org/TR/REC-html40/interact/forms.html#successful-controls
[2]https://blogs.msdn.microsoft.com/jmstall/2012/04/20/how-to-bind-to-custom-objects-in-action-signatures-in-mvcwebapi/