Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using one Partial View Multiple times on the same Parent View

I am using MVC3 razor. I have a scenario where I have to use a partial view multiple times on the same parent view. The problem I am having is that when the Parent View gets rendered, it generates same names and ids of the input controls within those partial views. Since my partial views are binded to different models, when the view is posted back on "Save" it crashes. Any idea how can i make the control id/names unique, probably some how prefix them ?

Awaiting

Nabeel

like image 928
nabeelfarid Avatar asked Jun 27 '11 11:06

nabeelfarid


2 Answers

Personally I prefer using editor templates, as they take care of this. For example you could have the following view model:

public class MyViewModel
{
    public ChildViewModel Child1 { get; set; }
    public ChildViewModel Child2 { get; set; }
}

public class ChildViewModel
{
    public string Foo { get; set; }
}

and the following controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel
        {
            Child1 = new ChildViewModel(),
            Child2 = new ChildViewModel(),
        };
        return View(model);
    }

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

and inside the Index.cshtml view:

@model MyViewModel
@using (Html.BeginForm())
{
    <h3>Child1</h3>
    @Html.EditorFor(x => x.Child1)

    <h3>Child2</h3>
    @Html.EditorFor(x => x.Child2)
    <input type="submit" value="OK" />
}

and the last part is the editor template (~/Views/Home/EditorTemplates/ChildViewModel.cshtml):

@model ChildViewModel

@Html.LabelFor(x => x.Foo)
@Html.EditorFor(x => x.Foo)

Using the EditorFor you can include the template for different properties of your main view model and correct names/ids will be generated. In addition to this you will get your view model properly populated in the POST action.

like image 169
Darin Dimitrov Avatar answered Oct 03 '22 03:10

Darin Dimitrov


There is an alternative:

  1. Add a prefix to the PartialView
  2. Bind the model, removing the prefix

For 1, set the prefix in your View:

ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "prefix";

For 2, you can recover the data with UpdateModel, like this:

UpdateModel(producto, "prefix");

This is not very advisable because your action doesn't receive the data as a parameter, but updates the model later. This has several inconvenients: 1) it's not clear what your action needs by looking at its signature 2) it's not easy to provide the input to the action for unit testing it 3) the action is vulnerable to overflow parameters (parameters provided by the user that shouldn't be there and are mapped to the model).

However, for 2 there is an alternative: register a custom Model Binder that allows you to do remove the prefix. And the custom Model Binder must know about it.

A good solution is in this SO Q&A: How to handle MVC model binding prefix with same form repeated for each row of a collection? But it has a little flaw: if you add a hidden field with the name "__prefix" in a partial view, and you render it several times as a partial view, this ID will be repeated for several different elements in the page, which is not allowed, and can provoke some trouble. And one of the most important reasons to provide a prefix is precisely rendering the same "edit" view as partial views for several instances of an entity. I.e. this would happen in a page like gmail, where you can edit several emails at once.

There are several possible solutions for this problem.

One of them is providing the prefix as a query string or routedata value, and not as a form field, which avoid the Id conflicts, and can be found by the model binder. (It can always have the same name).

Another solution is to use a hidden field, with a fixed pattern, but which is different for every rendered view. The prefix could follow this pattern for uniqueness: "PP$ActionControllerId" like "PP$EditProduct23", which is unique for each rendered view, and can be easily found between the request parameters looking for one that starts with "PP$".

And a final solution would be to create the prefix only in the view, and not providing it in any kind of request parameter. The Model binder would have to look for the prefix examining the names of the request parameters, until it finds one whose prefix follow the pattern.

Of course, the custom ModelBinder must be adapted to work tieh the chosen convention.

like image 23
JotaBe Avatar answered Oct 03 '22 03:10

JotaBe