Why does Html Helper add prefix to attributes for

2019-05-06 19:51发布

问题:

I have a view model with a single child entity and various other properties.

In my view I want to display a form for the child entity. Using the following code:

@Html.HiddenFor(model => model.Item.ItemID)

produces the following output:

<input id="Item_ItemID" name="Item.ItemID" type="hidden" value="234" />

As you can see, the Html helper has prefixed the id and name attributes, whereas I would have expected the output to be

<input id="ItemID" name="ItemID" type="hidden" value="234" />

Consequently, the former output causes an error when the form is submitted because the form elements do not correspond to properties on the child entity.

I know I can get around this by hard-coding the hidden field

<input id="ItemID" name="ItemID" type="hidden" value="@Model.Item.ItemID" />

which kind of defeats the reason for having Html helpers, or creating a partial view and passing in the child object

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

I am aware of being able to pass in html attributes to the method, and also writing my own extension method, but things get more complicated when data validation and jQuery are involved i.e the following code:

@Html.EditorFor(model => model.Item.Title)
@Html.ValidationMessageFor(model => model.Item.Title)

produces this code:

<input class="text-box single-line" data-val="true" data-val-required="The Title field is required." id="Item_Title" name="Item.Title" type="text" value="Some text" />
<span class="field-validation-valid" data-valmsg-for="Item.Title" data-valmsg-replace="true"></span>

so there needs to be an elegant method to keep the property names in synch.

So can anyone answer why does an HtmlHelper add a prefix to attributes for a child item of a view model? And as a follow up question, is there any other neat way of preventing the prefix from being added?

回答1:

HTML helpers are built with model binding conventions in mind. So for example I suppose that you have the following model:

public class MyViewModel
{
    public ItemViewModel Item { get; set; }

    // ... other properties
}

public class ItemViewModel
{
    public int ItemID { get; set; }
}

and the corresponding view is strongly typed to MyViewModel.

So when you use:

@Html.HiddenFor(model => model.Item.ItemID)

the helper generates:

<input id="Item_ItemID" name="Item.ItemID" type="hidden" value="234" />

because that's what would allow you to properly bind your view model in the corresponding controller action when the form is POSTed back to the server:

[HttpPost]
public ActionResult Process(MyViewModel model)
{
    // model.Item.ItemID will be correctly bound here from the hidden field value
}

I guess that your problems are coming from the fact that your controller action is taking an ItemViewModel as parameter instead of MyViewModel:

[HttpPost]
public ActionResult Process(ItemViewModel model)
{
    // model.ItemID will not be correctly bound here from the hidden field value
}

So you could use the [Bind] attribute and specify a prefix to help the model binder:

[HttpPost]
public ActionResult Process([Bind(Prefix="Item")] ItemViewModel model)
{
    // model.ItemID will be correctly bound here from the hidden field value
}

or simply design another view model stripping all the unnecessary properties from it and leaving only the Item property. Now your controller action could take this specifically designed view model.

So as you can see at the end of the day you could perfectly fine control the model binding if you design your view models correctly. It's all about view models in ASP.NET MVC. They solve all problems.



标签: razor