How to persist data models passed to partial views

2019-01-08 00:44发布

问题:

To illustrate the problem I face, I have put together three simple data models:

 public class PersonalModel {
     public string FirstName { get; set; }
     public string LastName { get; set; }
 }

 public class AddressModel {
     public string Street { get; set; }
 }

 public class OverallModel {
    public string Id { get; set; }
    public PersonalModel Personal { get; set; }
    public AddressModel Address { get; set; }
 }

Here is my simple Index.chtml:

@model WebAppTest.Models.OverallModel
@using (Html.BeginForm())
{
  @Html.AntiForgeryToken()

  <div class="form-horizontal">
    <h4>OverallModel</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.Id)

    <div>
        @Html.Partial("Personal", Model.Personal)
    </div>
    <div>
        @Html.Partial("Address", Model.Address)
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
  </div>
}

When the "Save" button is clicked, the following controller method gets invoked:

[HttpPost]
public ActionResult Index(OverallModel model) {
    return View(model);
}

The problem I have is that model.Personal and model.Address values always show up as null in the controller method.

Although the values are correctly getting passed to the partial views, the Razor engine is not able to put the overall object back together when submit is clicked.

Would appreciate it if you could enlighten me on what is it that I am missing. Regards.

回答1:

Pass the OverallModel to your partials so that the controls will be correctly named for posting back to OverallModel. Currently you would have controls with name="FirstName" but they need to be name="Personal.FirstName"

@Html.Partial("Personal", Model)

and change the partials to suit

@model OverallModel
....
@Html.TextBoxFor(m => m.Personal.FirstName)

As an alternative, you can also pass the prefix to the partial

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


回答2:

I had same problem like you, so I'll put my 2 cents here for someone in the future with same problem.

My solution is by using EditorTemplates.

You create you models aka Personal, Address, Overal.

In (put whatever name)Controller you make an instance of Overal model and into Get method you send it into your View and in Post method you put it as incoming parameter.

 public ActionResult Index()
    {
        var model = new OveralModel();

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(OveralModel model)
    {
        return View(model);
    }

Index page (i will use yours) will look like this

@model EditorFor.Models.OveralModel

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>OverallModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(m => m.Id)

        <div>            
            @Html.EditorFor(m => m.Personal)
        </div>
        <div>
            @Html.EditorFor(m => m.Address)
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

Istead of PartialViews we can use EditorModels, i understand it as a PartialView which we can use as editor whenever we want.

So to use it, we create folder called EditorTemplates in the Views/Shared and create Views with same names as your models.

For example EditorTemplate for our PersonalModel will be called PersonalModel.cshtml and code in it can look something like this

@model EditorFor.Models.PersonelModel


<div class="form-group">
    @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
    </div>
</div>

<div class="form-group">
    @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
    </div>
</div>

So when we write in our sou @Html.EditorFor(m => m.Personal) it will look into EditorTemplates and will search for an EditorTemplate with same name and use it in site.

Index site will look like this

and when you click Submit button it will send back whole model back into Controller

Looks like it worked and we got our data back from view and use it for whatever we want to.

PS: My first post, so i hope it is understandable and will be helpful for others even now and in future. Have a good day