0

I have a .net MVC page that has a form used to edit an entity from database. The form has some dynamic select list fields that need to be populated from Database also.

I pass a PageView Model that contains the Data for the entity, and a list of entities to populate the select list like so:

public class EditPortalPage
{
    public PortalViewModel Data { get; set; }
    public IEnumerable<SelectListItem> Cultures { get; set; }        
}

in my View I have

@model Website.Models.Portals.EditPortalPage

at the top of the page

I can easily populate my form doing this:

    @Html.EditorFor(model => model.Data.Name, new { htmlAttributes = new { @class = "form-control" } })
    @Html.LabelFor(model => model.Data.Name)
    @Html.ValidationMessageFor(model => model.Data.Name, "", new { @class = "text-danger" })


    @Html.DropDownListFor(model => model.Data.DefaultCultureId, new SelectList(Model.Cultures, "Value", "Text"), new { @class = "mdb-select colorful-select dropdown-info" })
    @Html.LabelFor(model => model.Data.DefaultCultureId)
    @Html.ValidationMessageFor(model => model.Data.DefaultCultureId, "", new { @class = "text-danger" })

The problem is this produces messed up Id and Name attributes for my form fields. In the above example it produces:

Name Field ID: Data_Name 
Name Field Name: Data.Name

What is the correct way to set it up so I the name and ID does not include the 'Data' prefix

Can I pass the Data model directly to teh form?

Kevin Bradshaw
  • 6,327
  • 13
  • 55
  • 78
  • that is expected as name is used to bind the model object by ModelBinder, it would post back correct via form in the controller action – Ehsan Sajjad Aug 25 '17 at 13:26
  • You could pass just the model itself to the form and then populate the select lists another way. Possibly by creating helpers for them, or putting the data for them in the `ViewBag` and populating them from that. – David Aug 25 '17 at 13:28
  • Im trying to avoid using ViewBag and ViewData as ways of passing data to page. – Kevin Bradshaw Aug 25 '17 at 13:33

2 Answers2

1

The HtmlHelper methods generate the name and id attribute based on the name of the property. In your case, its a nested property so it generates the Data. prefix for the name attribute, and in the case of the id attribute, it replaces the .(dot) with an _(underscore) so that the id can be used in jquery selectors (otherwise the dot and the following text would be interpreted as a class selector.

Basing the id attribute on the property name ensures a unique id (and therefore valid html), and I'm not sure why you think its 'messed up', but you can always override the value by using

@Html.EditorFor(m => m.Data.Name, new { htmlAttributes = new { id = "xxx",  @class = "form-control" } })

which will render <input id="xxx" .... /> if you want to use xxx in a css or javascript/jquery selector (which is the only purpose of the id attribute)

However the name attribute is certainly not 'messed up' and if it were changed, it would not bind to your model when you submit. Under no circumstance should you ever attempt to change the name attribute when using the HtmlHelper methods.

I'm guessing that your (incorrectly) using your data model as a parameter in the POST method, in which case you could use the Prefix property of the [Bind] attribute (although a [Bind] attribute should never be used with a view model)

public ActionResult Edit([Bind(Prefix = "Data")]PortalViewModel model)

to strip the "Data." prefix and bind to PortalViewModel.

However, the real issue here is you incorrect use of a view model. View models, especially when editing data, should not contain data models. They contain only the properties of your data model that you need in the view. Some of the benefits of view models include Separation of Concerns, protection against under and over posting attacks, ability to add display attributes which are not applicable in data models, and the ability to add additional view specific properties. In your case, you have negated all but the last point. You view model should be

public class EditPortalPage
{
    public string Name { get; set; }
    [Display(Name = "Culture")]
    [Required(ErrorMessage = "Please select a culture")]
    public int? SelectedCulture { get; set; } //nullable and Required to protect against under-posting attacks
    ... // other properties of PortalViewModel that you need in the view
    public IEnumerable<SelectListItem> Cultures { get; set; }        
}

and in the POST method, you map your view model to the data model and save the data model. Refer also What is ViewModel in MVC?.

As a side note, using new SelctList(..) in your DropDownList() method is pointless extra overhead - its just creating another identical IEnumerable<SelectListItem> from the first one, and your code should just be

@Html.DropDownListFor(m => m.SelectedCulture, Model.Cultures, "Please select", new { ... })
  • Good advice and a thorough answer. – Kevin Bradshaw Aug 26 '17 at 11:18
  • The problem is that I was creating a view model for each data model to achieve the benefits you highlighted in your answer, and then grouping them together in page models when I needed reference to one or more entities on a page. I guess I need to break that way of thinking and create single flat view models per page – Kevin Bradshaw Aug 26 '17 at 11:22
  • If that is the case, then its fine because your not using data models in your views (although 'flatter' view models tend to be easier to work with). But because your have `@model EditPortalPage` in the view, then the parameter in the POST method should also be `EditPortalPage model` –  Aug 26 '17 at 11:26
0

If I understand the Q correctly, you could use a partial "_data.cshtml":

@model PortalViewModel
@Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })

Pass data to the partial from your view:

@Html.Partial("_data",model.Data)
mxmissile
  • 11,464
  • 3
  • 53
  • 79
  • No, the example above was not complete, I would still need to pass the Select List data to the partial that you suggested. Ive updated my example to hopefully make it clearer. You can see that the Dropdown list options in the updated code needs to be populated from Model.Cultures, so I would still have to pass Model.Cultures and Model.Data to the partial – Kevin Bradshaw Aug 25 '17 at 14:32
  • Couldnt you add `Enumerable Cultures` to `PortalViewModel`? – mxmissile Aug 25 '17 at 14:42
  • That is probably the best way to tackle it. Flatten everything into one view model. I had thought of that, but I wondered if I was missing something obvious. – Kevin Bradshaw Aug 25 '17 at 20:49