How to prevent Razor from adding prefixes to inputs when using nested display templates?
Asked Answered
S

2

12

When I use nested display templates and add input elements through the HTML helper the Razor engine adds a prefix to the fields names.

I do understand this is done to guarantee input name uniqueness at page level (and to rebuild the whole model on post back).

However I have many small forms which perform ad-hoc actions, and I don't need neither the name uniqueness nor the ability to rebuild the whole model.

I just need that single property value, and having Razor alter the input items names breaks the model binder when I submit one of the forms, since all the names will be different.

This example contains a simplified nested model

public class Student
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<Course> Courses { get; set; }
}

public class Course
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public List<Grade> Grades { get; set; }
}

public class Grade
{
    public Guid Id { get; set; }
    public DateTime Date { get; set; }
    public decimal Value { get; set; }
}

and it has an Index view with three nested display templates

IndexView
    StudentDisplayTemplate
        CourseDisplayTemplate
            GradeDisplayTemplate

In the grade display template I add a button to remove the grade

@model Playground.Sandbox.Models.Home.Index.Grade

<li>
    @this.Model.Date: @this.Model.Value

    @using (Html.BeginForm("Remove", "Home", FormMethod.Post))
    {
        <input name="GradeId" type="hidden" value="@this.Model.Id" />

        <input type="submit" value="Remove" />
    }
</li>

and on the other side of the request my controller action receives the grade ID

public ActionResult Remove(Guid id)
{
    // Do various things.
    return this.RedirectToAction("Index");
}

If I try to do it using the model helper

@Html.HiddenFor(x => x.Id)

I get the HTML element

<input data-val="true"
       data-val-required="The Id field is required."
       id="Courses_0__Grades_1__Id"
       name="Courses[0].Grades[1].Id"
       type="hidden"
       value="76f7e7ed-a479-42cb-add5-e58c0090770c" />

where the field name gets a prefix based on the whole parent's view model tree.

Using the "manual" helper

@Html.Hidden("GradeId", this.Model.Id)

gives the HTML element

<input id="Courses_0__Grades_0__GradeId"
       name="Courses[0].Grades[0].GradeId"
       type="hidden"
       value="bbb3c11d-d2d0-464a-b33b-ff7ac9815601" />

where the prefix is still present, albeit with my name at the end.

Adding manually the hidden input

<input name="GradeId" type="hidden" value="@this.Model.Id" />

gives the HTML element

<input name="GradeId"
       type="hidden"
       value="a1a35e81-29cd-41b5-b619-bab79b767613" />

which is what I want.

Is it possible to achieve what I want, or am I getting the display templates thing wrong?

Scrubland answered 3/9, 2013 at 15:2 Comment(2)
#14157876Lafrance
@MohamadBataineh as I said, I do know why it's doing it. What I'm asking is if there is a way to prevent it when using the HTML helpers (I don't think I can use the Bind attribute on the receiving action, since the helpers are placing array indexes in the names).Scrubland
A
12

You want to set ViewData.TemplateInfo.HtmlFieldPrefix in your Grade template:

@model Playground.Sandbox.Models.Home.Index.Grade

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

This gives the desired output of:

<input id="GradeId" name="GradeId" type="hidden" />
Adjustment answered 30/9, 2013 at 19:5 Comment(1)
It does solve the issue with names, but now the markup is invalid. It looks like he's using a List of Grades and renders it as a list of form elements. By setting ViewData.TemplateInfo.HtmlFieldPrefix to "" id field of form inputs won't be unique anymore. Any ideas for workaround? Besides specifying custom id for every input of course.Atal
L
0

From your question, it's not clear if you're looking to be able to submit both the entire form, or just the individual actions. If you want to be able to do both, I think you'll have to use a JS-based solution to manually submit the subform requests.

However, if you just want to submit the subforms, piecemeal, read along..

If the syntax Courses_0__Grades_1__Id for collections is causing problems, this is relatively easy to fix, in my experience.

You can get different behavior on how the names/ids are generated in child objects in collections by using foreach instead of a traditional for.

Let me explain:


1) This will break the model binding for entire-form submission. All inputs for child items will have no context of their parent path.

@foreach(var child in Model.Children)
{
    @Html.EditorFor(x=> child)
}


2) These will respect parent context and allow model binding for entire-form submission. All inputs for child items WILL HAVE context of their parent path.

@for(var i = 0; i < Model.Children.Count(); i++)
{
    @Html.EditorFor(x=> Model.Children[i])
}
// or..
var i = 0; 
@foreach(var child in Model.Children)
{
    @Html.EditorFor(x=> Model.Children[i])
    i++;
}

However

You will still have issues with objects NOT in collections, hanging off the main model, like Model.SomeOtherType.AnotherType will have inputs in the nested EditorFor with names like SomeOtherType.Property1 and SomeOtherType.Property2

For these, you can pull the object into a temporary variable in the razor:

@var tempObj = Model.SomeOtherType;
<div class='somemarkup'>
    @Html.EditorFor(x=> tempObj);
</div>
Lycanthrope answered 27/10, 2014 at 17:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.