Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding properties of a base View Model in ASP.NET MVC 3

I have a view model that is shared by two different pages. The view models are fairly similar with the exception of one property: Address. The view model contains name and location fields. However, the customer view's address label should read: Customer Address and the employee view's address label should read: Employee Address. They will also display different error messages.

Here's a simplified version of what I'm trying to accomplish:

public class BaseLocation
{
  [Display(Name="Your Name")]
  public string Name {get;set;}

  public virtual string Address {get;set;}
}

public class CustomerLocation : BaseLocation
{
  [Display(Name="Customer Address")]
  public override string Address {get;set;}
}

public class EmployeeLocation : BaseLocation
{
  [Display(Name="Employee Address")]
  public override string Address {get;set;}
}

Then I created a partial for the base location, like so:

@model BaseLocation
***ASP.NET MVC Helpers here: labels, text, validation, etc.

Finally, in the Customer and Employee pages, I would call the partial and send it the subclassed type.

Customer.cshtml
@model CustomerLocation
@Html.Render("_BaseLocation", Model)


Employee.cshtml
@model EmployeeLocation
@Html.Render("_BaseLocation", Model)

The result is that I would not see the data attributes for the specific type. So for example, in the customer page, I would get a label of "Address" instead of "Customer Address".

I'd rather not create two partials with the same data for each specific type, simply because one property in the shared view model should have a different label and error message. What's the best way to go about this? Thanks.

like image 479
Mike Avatar asked Jan 11 '12 20:01

Mike


1 Answers

Because of the way view inheritance works and how the model is defined the parameter passed into something like LabelFor and TextBoxFor uses the model type defined in the class. In your case it's going to always be BaseLocation which is why it's not being overridden.

You don't necessarily have to create partials for your class but you will have to create two views one for customer and one for employee. Since you already have two views specific to each type you will just have to create another location view or merge the baselocation view into it's parent.

Customer.cshtml
@model CustomerLocation
@Html.Render("_CustomerBaseLocation", Model)


Employee.cshtml
@model EmployeeLocation
@Html.Render("_EmployeeBaseLocation", Model)

I definitely understand your issue since you only want to change one view and you could have several of these similar types of situations already with BaseLocation.

You could do something like this...

public static IHtmlString LabelTextFor<TModel, TValue>(this HtmlHelper<TModel> html, object model, Expression<Func<TModel, TValue>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    var propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;

    //no property name
    if (string.IsNullOrWhiteSpace(propertyName)) return MvcHtmlString.Empty;

    //get display text
    string resolvedLabelText = null;
    var displayattrib = model.GetType().GetProperty(propertyName)
                .GetCustomAttributes(true)
                .SingleOrDefault(f => f is DisplayAttribute) 
                                      as DisplayAttribute;
    if (displayattrib != null) {
        resolvedLabelText = displayattrib.Name;
    }

    if (String.IsNullOrEmpty(resolvedLabelText)) {
        return MvcHtmlString.Empty;
    }

    TagBuilder tag = new TagBuilder("label");
    tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName("")));
    tag.SetInnerText(resolvedLabelText);
    return new HtmlString(tag.ToString());
}

Then in your _BaseLocation.cshtml you would make a call like:

@Html.LabelTextFor(Model, m => m.Address)

Writing a custom extension method to do this is about all I can think of

like image 178
Buildstarted Avatar answered Nov 20 '22 19:11

Buildstarted