ViewModels
//MainViewModel
public class MainViewModel
{
public string UserID { get; set; }
public string ServiceType { get; set; }
public List<Service> Services {get; set;}
}
public class Service
{
public string ServiceID {get; set;}
public string ServiceName {get; set;
}
Here is the View
//Index.cshtml
@model ParentViewModel
@{
<div>
@Html.DropDownListFor(model => model.UserID, new{onchange="ddSelectionChanged(this)"})
<div id="services">
//This is filled with the ajax
</div>
</div>
}
Here is the AJAX Call
<script>
function ddSelectionChanged(element){
$('#services').load('@(Url.Action("GetServices"))?serviceType =' element.value);
};
</script>
Here is the Controller
//Controller
public class UserServicesController : Controller
{
public ActionResult Index()
{
return View();
}
public PartialViewResult GetServices(string serviceType)
{
//Service servicesViewModel = Fetch Services from DB
return PartialView("PartialViews/Shared/_Services", servicesViewModel);
}
}
This works fine and the partial View is rendered as expected but when I post the Index, the property "Services" of MainViewModel is returned "null". The issue is that the 'Services' Property of the model is not rebinding to the MainViewModel because its rendered as a partial view dynamically.
How can I rebind the model either at the time of Partial View Rendering or before POSTing the form?
When you post to an action, the only parts that get filled by the modelbinder are the fields and data that were posted. If you're losing your services, then you need to provide fields in your form to hold on to that data during the post.
For example, in whatever HTML is returned by your AJAX, you'd need to include something like a hidden input for each of the properties on Service
:
@Html.HiddenFor(m => m.ServiceID)
@Html.HiddenFor(m => m.ServiceName)
Then, that data will be posted along with everything else when your form is posted and the modelbinder will be able to fill your Services
list appropriately. However, you also need to pay attention to the field names that are produced. Particularly if your partial view returned by your action that responds to the AJAX request utilizes a model like List<Service>
directly instead of MainViewModel
, then the rendered HTML will lose the context of knowing that it's supposed to bind to a property called Services
. In your HTML, in other words, your field names will end up as something like [0].ServiceID
instead of Services[0].ServiceID
. The latter will be necessary to get the modelbinder to handle the posted data properly.
You can compensate for this by passing a custom ViewDataDictionary
to your Html.Partial
or Html.RenderPartial
calls. By defining a new TemplateInfo
with HtmlFieldPrefix
set to whatever it needs to be, i.e.:
@Html.Partial("_SomePartial", someModel, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "SomePrefix" }
}
However, it's trickier when returning PartialView
directly, because PartialView
doesn't accept a parameter for a custom ViewDataDictionary
. I haven't tried it myself, but it's possible you can set HtmlFieldPrefix
directly in your action:
public PartialViewResult GetServices(string serviceType)
{
ViewData.TemplateInfo.HtmlFieldPrefix = "Services";
//Service servicesViewModel = Fetch Services from DB
return PartialView("PartialViews/Shared/_Services", servicesViewModel);
}
Also worth mentioning, if you haven't worked with binding collections before, is that you need to use a for
loop instead of a foreach
loop in order to provide the appropriate context for the field names. For example, something like the following:
@foreach (var service in Model)
{
@Html.HiddenFor(m => service.ServiceID)
}
Would result in a field with the name, service.ServiceID
. The modelbinder would not know where this value should go and would basically ignore it. Instead, you would need to do:
@for (var i = 0; i < Model.Count(); i++)
{
@Html.HiddenFor(m => m[i].ServiceID)
}
That would get you fields with names like [0].ServiceID
. Again, the prefix is a problem, so you'll need both components to get this to work correctly.
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