12

I have view model which has another child model to render the partial view (below).

public class ExamResultsFormViewModel
{
    public PreliminaryInformationViewModel PreliminaryInformation { get; set; }

    public string MemberID { get; set; }

    public string MemberName { get; set; }

    public int PatientID { get; set; }

    public string ConfirmationID { get; set; }

    public bool IsEditable { get; set; }

    #region Select Lists
    public SelectList ProviderOptions { get; set; }
    #endregion
}

public class PreliminaryInformationViewModel
{
    public string ProviderName { get; set; }

    public string ProviderID { get; set; }

    public string ServiceLocation { get; set; }
}

This PreliminaryInformationViewModel view model also used as a child models in another view model since this preliminary information can be updated at different pages.

So I created this preliminary information as a separate partial and to include in other pages.

@{Html.RenderPartial("_PreliminaryInformation", Model.PreliminaryInformation);}

Inside the partial

@model Web.Models.Preliminary.PreliminaryInformationViewModel
<div>
    @Html.TextBoxFor(x => x.DateOfService })
</div>

But the problem is during submit this preliminary model is always null due to the reason HTML name attribute is always is rendered as

but when I pass the parent model to the partial as below.

@model Web.Models.Exam.ExamResultsFormViewModel
<div>
    @Html.TextBoxFor(x => x.PreliminaryInformation.DateOfService })
</div>

Now the HTML element is generated as

<input type = 'text' name='PreliminaryInformation.DateOfService.DateOfService' id='PreliminaryInformation.DateOfService'>

and it binds properly during the submit.

I understand MVC bind the element value based on the name attribute value, but the second implementation would need me to create a multiple partial for each page, which I don't like.

So far I couldn't find a solution to work with the first implementation, is there way I can make preliminary information model value bind during submit with the first implementation.

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
Sathish
  • 195
  • 1
  • 2
  • 5

7 Answers7

39

I know its a bit late but it might help to someone

If you have complex model, you can still pass it into partial using:

@Html.Partial("_YourPartialName", Model.Contact, new ViewDataDictionary()
{
    TemplateInfo = new TemplateInfo()
    {
        HtmlFieldPrefix = "Contact"
    }
})

where I have defined model with property "Contact". Now what HtmlFieldPrefix do is add the property binding for each model "so the model binder can find the parent model"

There is a blog post about it: http://www.cpodesign.com/blog/bind-partial-view-model-binding-during-submit/

.NET Core 2 binding

In .NET Core 2 and MVC the answer above will not work, the property is no longer settable.

How ever the solution is very similar.

 @{ Html.ViewData.TemplateInfo.HtmlFieldPrefix = "Contact"; }
 @await Html.PartialAsync("_YourPartialName", Model.Contact)

after you can submit your model, it will bind again.

Hope that helps

amurra
  • 15,221
  • 4
  • 70
  • 87
cpoDesign
  • 8,953
  • 13
  • 62
  • 106
  • What if I have a List of Contact objects and want to bind that to the model? – user3281466 Apr 20 '15 at 10:55
  • You Will need to wrap in on for each loop with the style above – cpoDesign Apr 20 '15 at 17:26
  • I don't see any reasonable place where the loop is supposed to go, could you please advise? – user3281466 Apr 22 '15 at 09:06
  • Superb, thank you for this AWESOME tip! This gives all the functionality of Editor Templates with the flexibility of partial views. The whole notion of Editor Templates is a bit overblown IMHO they are just a different syntax for calling a partial view with the added constraint of the view being tied to a specific directory in the project. Using your answer you get the best of both worlds. Thanks again! –  Jul 23 '15 at 20:30
  • This helps me a lot:D Thanks – duongthaiha Oct 22 '15 at 12:13
  • @duongthaiha I am glad that it has helped – cpoDesign Oct 26 '15 at 11:02
8

You can add the HtmlFieldPrefix to the top of your partial view:

@{
    ViewData.TemplateInfo.HtmlFieldPrefix = "Contact";
}

This is the same approach as that described by @cpoDesign but it means you can keep the prefix in your partial view if you need to do that.

Chaholl
  • 455
  • 5
  • 8
  • 1
    yes I know this could've been a comment on the original answer but I don't have the rep. – Chaholl Oct 26 '16 at 18:35
  • This is great for when you have a partial view used in different places & different forms, each of which may be submitted to the server along with forms that are meant to bind to different types of models. It may require some logic to specify to the correct Prefix for where the partial view is being embedded. – amartin Dec 23 '20 at 04:31
6

You need to create an editor template for PreliminaryInformationViewModel to replace the partial view, then call with Html.EditorFor( m => m.PreliminaryInformation ). Reference this solution. Creating the template should be as simple as moving your partial view to the Views/Shared/EditorTemplates directory. Html.EditorFor(...) will automatically use this template based on the type you're passing in as the model (in this case, PreliminaryInformationViewModel)

Community
  • 1
  • 1
Moho
  • 15,457
  • 1
  • 30
  • 31
  • Okay using EditerFor solves the binding problem, but it also replaces all the classes and styling I have applied in the view. '' – Sathish Dec 20 '13 at 00:43
  • move your partial view to Views\Shared\EditorTemplates, the call to `Html.EditorFor(...)` should automatically select this view for your editor template based on the type you're passing in as the model – Moho Dec 20 '13 at 01:12
  • Thanks for the reply Moho. I was able to render partial and elements value are bind during submit using EditorFor, but rendering using EditorFor adds some editor classes to divs and elements and removes the ones that was actually placed on the view Is there any way we can prevent adding these editor classes to the elements in the partial. – Sathish Dec 20 '13 at 05:30
3

I also ran into this problem. I will explain my solution using your code. I started with this:

@{
   Html.RenderPartial("_PreliminaryInformation", Model.PreliminaryInformation);
}

The action corresponding to the http post was looking for the parent model. The http post was submitting the form correctly but there was no reference in the child partial to the parent partial. The submitted values from the child partial were ignored and the corresponding child property remained null.

I created an interface, IPreliminaryInfoCapable, which contained a definition for the child type, like so:

public interface IPreliminaryInfoCapable
{
    PreliminaryInformationViewModel PreliminaryInformation { get; set; }
}

I made my parent model implement this interface. My partial view then uses the interface at the top as the model:

@model IPreliminaryInfoCapable

Finally, my parent view can use the following code to pass itself to the child partial:

@{
    Html.RenderPartial("ChildPartial", Model);
}

Then the child partial can use the child object, like so:

@model IPreliminaryInfoCapable
...
@Html.LabelFor(m => m.PreliminaryInformation.ProviderName)
etc.

All of this properly fills the parent model upon http post to the corresponding action.

gregsonian
  • 1,126
  • 13
  • 15
0

Quick Tip: when calling your EditorFor method, you can set the name of the template as a parameter of the Html.EditorFor method. Alternatively, naming conventions can be your friend; just make sure your editor template filename is exactly the same name as the model property type

i.e. model property type 'CustomerViewModel' => 'CustomerViewModel.cshtml' editor template.

Tony Pye
  • 31
  • 4
0

Please make the below changes to your partial page. so it will come with your Parent model

//Parent Page
@{Html.RenderPartial("_PreliminaryInformation", Model.PreliminaryInformation);}

//Partial Page
@model Web.Models.Preliminary.PreliminaryInformationViewModel
@using (Html.BeginCollectionItem("PreliminaryInformation", item.RowId, true))
    {
<div>
@Html.TextBoxFor(x => x.DateOfService })
</div>

}
ಅನಿಲ್
  • 606
  • 5
  • 7
0

For .net core 2 and mvc, use do like below:

@{
 Html.ViewData.TemplateInfo.HtmlFieldPrefix = "Contact"; 
}
 @await Html.PartialAsync("_YourPartialViewName", Model.Contact)
Abdus Salam Azad
  • 5,087
  • 46
  • 35