Partial View ViewModel Doesn't bind to main ViewModel after loading via AJAX

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 displayed as expected, but when I submit the index, the "Services" property of the MainViewModel returns "null". The problem is that the "Services" property of the model does not bind to the MainViewModel because it is rendered as a partial view dynamically.

How can I redo the model during partial view or before submitting the form?

+3


source to share


1 answer


When you post a post to an action, the only parts that get populated with the modelbinder are the fields and data that was posted. If you lose your services, you need to provide fields in your form to retain this data during posting.

For example, in any HTML returned by your AJAX, you need to enable something like hidden input for each of the properties on Service

:

@Html.HiddenFor(m => m.ServiceID)
@Html.HiddenFor(m => m.ServiceName)

      

This data will then be posted along with everything else when your form is posted and the model block can populate your list Services

accordingly. However, you also need to pay attention to the field names that are being produced. In particular, if your partial view returned by your action that responds to an AJAX request uses a model instead of t <24>, for example List<Service>

, then the rendered HTML will lose context knowing it should bind to a property called Services

. In other words, your field names will look like [0].ServiceID

instead Services[0].ServiceID

. The latter is necessary in order to force the model unit to correctly process the published data.

You can compensate for this by passing ViewDataDictionary

custom ViewDataDictionary

calls to Html.Partial

or Html.RenderPartial

. Once you define a new TemplateInfo

c HtmlFieldPrefix

, install whatever it needs, for example:

@Html.Partial("_SomePartial", someModel, new ViewDataDictionary
{
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "SomePrefix" }
}

      

However, on return, PartialView

this is trickier because it PartialView

doesn't take a parameter for a custom one ViewDataDictionary

. I haven't tried it myself, but 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 to binding before, you need to use a loop for

instead of a loop foreach

to provide appropriate context for the field names. For example, something like the following:

@foreach (var service in Model)
{
    @Html.HiddenFor(m => service.ServiceID)
}

      

A name box will appear service.ServiceID

. Modelbinder would not know where this value should go and basically ignores it. Instead, you will need to do:

@for (var i = 0; i < Model.Count(); i++)
{
    @Html.HiddenFor(m => m[i].ServiceID)
}

      

This will give you fields with type names [0].ServiceID

. Again, the prefix is ​​a problem, so you need to get both components to work correctly.

+6


source







All Articles