views:

758

answers:

3

Hi,
today I got confused when doing a couple of <%=Html.LabelFor(m=>m.MyProperty)%> in ASP.NET MVC 2 and using the [DisplayName("Show this instead of MyProperty")] attribute from System.ComponentModel.

As it turned out, when I put the attribute on an overridden property, LabelFor didn't seem to notice it.
However, the [Required] attribute works fine on the overridden property, and the generated errormessage actually uses the DisplayNameAttribute.

This is some trivial examplecode, the more realistic scenario is that I have a databasemodel separate from the viewmodel, but for convenience, I'd like to inherit from the databasemodel, add View-only properties and decorating the viewmodel with the attributes for the UI.

public class POCOWithoutDataAnnotations
{
    public virtual string PleaseOverrideMe { get; set; }        
} 
public class EditModel : POCOWithoutDataAnnotations
{
    [Required]
    [DisplayName("This should be as label for please override me!")]
    public override string PleaseOverrideMe 
    {
        get { return base.PleaseOverrideMe; }
        set { base.PleaseOverrideMe = value; }
    }

    [Required]
    [DisplayName("This property exists only in EditModel")]
    public string NonOverriddenProp { get; set; }
}

The strongly typed ViewPage<EditModel> contains:

        <div class="editor-label">
            <%= Html.LabelFor(model => model.PleaseOverrideMe) %>
        </div>
        <div class="editor-field">
            <%= Html.TextBoxFor(model => model.PleaseOverrideMe) %>
            <%= Html.ValidationMessageFor(model => model.PleaseOverrideMe) %>
        </div>

        <div class="editor-label">
            <%= Html.LabelFor(model => model.NonOverriddenProp) %>
        </div>
        <div class="editor-field">
            <%= Html.TextBoxFor(model => model.NonOverriddenProp) %>
            <%= Html.ValidationMessageFor(model => model.NonOverriddenProp) %>
        </div>

The labels are then displayed as "PleaseOverrideMe" (not using the DisplayNameAttribute) and "This property exists only in EditModel" (using the DisplayNameAttribute) when viewing the page.
If I post with empty values, triggering the validation with this ActionMethod:

    [HttpPost]
    public ActionResult Edit(EditModel model)
    {
        if (!ModelState.IsValid)
            return View(model);
        return View("Thanks");
    }

the <%= Html.ValidationMessageFor(model => model.PleaseOverrideMe) %> actually uses [DisplayName("This should be as label for please override me!")] attribute, and produces the default errortext "The This should be as label for please override me! field is required."

Would some friendly soul shed some light on this?

+6  A: 

Model binding and metadata using the strongly-typed helpers looks at the declared, rather than the runtime, type of the model. I consider this a bug, but apparently the MVC team disagrees with me, as my Connect issue on this was closed as "By Design."

Craig Stuntz
Well, I'll try filing it as an bug @ Connect and be prepared for "By Design" then. Thanks!
Lasse Krantz
I just think you have convinced me to never use MVC again. What a load of BS.
leppie
Not a bug? I moved my entire project to MVC 3 and this is a pain in my side. It used to work just fine - is the DisplayName attribute not supposed to work this way now?
Jack
A: 

I had the same issue when I had a partial view strongly-typed to an interface. The interface defined a DisplayName and the class that implemented the interface tried to override it. The only way I found to get it to respect the override was to type to the implementing class. I had to either change the view's model type or cast. Unfortunately, that completely negates the benefits of using the interface as the model type. I am guessing that I will end up with some level of duplicated view markup for each implementing class while not casting within the strongly-typed "helpers".

In the remote chance that this type of workaround is even remotely helpful (not getting my hopes up), here is an example. There are certainly ways of working handling into this for all possible implementing classes that try to override a name, but it is definitely more hassle than it should be.

public interface IAddressModel {
    ...
    [DisplayName("Province")]
    public string Province { get; set; }
    ...
}
public class UsAddressModel : IAddressModel {
    ...
    [DisplayName("State")]
    public string Province { get; set; }
    ...
}

<%= Html.LabelFor(m => m.State) %> <!--"Province"-->
<%= Html.LabelFor(m => (m as UsAddressModel).State) %> <!--"State"-->
patridge