About your host

Photo of your host - Charles Vallance Charles Vallance is a web developer with a slight case of OCD when it comes to nice clean standards compliant html and code.

Badges

twitter / cvallance
My articles have been featured in The Morning Brew - Daily .NET News and Views

Tag Cloud

more tags...
August 2009 Entries

With the new release of ASP.NET MVC 2 CTP 1 there are a lot of cool new features that are driven by html attributes on your models. Read both The Gu’s and Phil Haack’s blog posts about the release to get the full low down.

I’m guessing that (like myself) a lot of people out there are using ORM’s to generate their models. Yeah? Thought so. Anyway, looking at that previous sentence, the key word is *generate*. You see, if you put property attributes in these generated files, they’re just going to be wiped each time you make a db schema change – far from ideal! ;-)

So where do you put these attributes?

Nope, you can’t put them in a partial class because C# and VB.NET don’t allow you to add attributes to properties defined in another partial class.

Enter the Buddy Class!

The Gu touched on buddy classes in his aforelinked1 post, but didn’t go into them in any detail – so I’m going to try and fill that void with this post.

Say we have a 'Customer' table (similar to The Gu's example) in our database and our orm generates a class like so:

public partial class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
    public string Country { get; set; }
}

Before we go any further (if you haven’t already) you should add a reference to System.ComponentModel.DataAnnotations.dll to your project – you’re gonna need it.

Next we create a partial class for the generated class and also create it’s buddy class.

The best way to explain this is with code, so here you go:

[MetadataType(typeof(Customer_Metadata))]
public partial class Customer
{
    //Custom model stuff here
}

public class Customer_Metadata
{
    [DisplayName("First name")]
    [Required(ErrorMessage = "First name is required")]
    public string FirstName { get; set; }

    [DisplayName("Last name")]
    [Required(ErrorMessage = "Last name is required")]
    public string LastName { get; set; }

    [Range(1, 120, ErrorMessage = "Invalid Age")]
    public int Age { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Country is required")]
    [UIHint("CountryDropDown")]
    public string Country { get; set; }
}

Savvy? Good, because that's really all there is to it! ;-)

1aforelinked - new word? © Charles Vallance 2009

EDIT: Making the buddy class a private inner class

As outlined below by Tyrone and Steve, it's probably best practice to have the buddy class as a private inner class.

E.g.

[MetadataType(typeof(Customer.Metadata))]
public partial class Customer
{
    //Custom model stuff here

    private sealed class Metadata
    {
        [DisplayName("First name")]
        [Required(ErrorMessage = "First name is required")]
        public string FirstName { get; set; }

        [DisplayName("Last name")]
        [Required(ErrorMessage = "Last name is required")]
        public string LastName { get; set; }

        [Range(1, 120, ErrorMessage = "Invalid Age")]
        public int Age { get; set; }

        [Required(ErrorMessage = "Email is required")]
        [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Country is required")]
        [UIHint("CountryDropDown")]
        public string Country { get; set; }
    }
}

I just finished writing an epic (in length) answer on StackOverflow and thought that it is nearly blog worthy… so in keeping with my mediocre posts, I’m going to blog it! ;-)

The question was this:

My ultimate goal is to have a menu that adds a class to the list item that associates with the current page I am on.

So I have it set up such that each controller will be associated with an item in my menu. I need to add a class to that list item (changing the color, background, whatever).

Is there a simple way to do this? Pass a value to the View, then what?

It seems like a good idea to pass a value to the view, but if your menu items are  coupled to your controllers and actions then it would make sense to simply use the values you can find in the ViewContext.RouteData.Values collection.

So using the following simple HtmlHelper extension as a base we can cater for any number of scenarios.

public static string OnClass(this HtmlHelper html, bool isOn)
{
    if (isOn)
        return " class=\"on\"";

    return string.Empty;
}

Just test the current action:

public static string OnClass(this HtmlHelper html, string action)
{
    string currentAction = html.ViewContext.RouteData.Values["action"].ToString();

    return html.OnClass(currentAction.ToLower() == action.ToLower());
}

Test for any number of actions:

public static string OnClass(this HtmlHelper html, string[] actions)
{
    string currentAction = html.ViewContext.RouteData.Values["action"].ToString();

    foreach (string action in actions)
    {
        if (currentAction.ToLower() == action.ToLower())
            return html.OnClass(true);
    }

    return string.Empty;
}

Test for action and controller:

public static string OnClass(this HtmlHelper html, string action, string controller)
{
    string currentController = html.ViewContext.RouteData.Values["controller"].ToString();

    if (currentController.ToLower() == controller.ToLower())
        return html.OnClass(action);

    return string.Empty;
}

Test for any number of actions on a controller:

public static string OnClass(this HtmlHelper html, string[] actions, string controller)
{
    string currentController = html.ViewContext.RouteData.Values["controller"].ToString();

    if (currentController.ToLower() == controller.ToLower())
        return html.OnClass(actions);

    return string.Empty;
}

Etc etc.

Then you simply call any of them from your view like so:

<ul id="left-menu">
    <!-- simple boolean -->
    <li <%= Html.OnClass(something == somethingElse) %>>Blah</li>
    <!-- action -->
    <li <%= Html.OnClass("Index") %>>Blah>/li>
    <!-- any number of actions -->
    <li <%= Html.OnClass(new string[] { "Index", "Details", "View" }) %>>Blah</li>
    <!-- action and controller -->
    <li <%= Html.OnClass("Index", "Home") %>>Blah</li>
    <!-- any number of actions on a controller -->
    <li <%= Html.OnClass(new string[] { "Index", "Details", "View" }, "Home") %>>Blah</li>
</ul>

So yeah, that was the approach I used in my last ASP.NET MVC application. No doubt next time I’ll do something completely different… maybe create my own versions of HtmlHelper.ActionLink that create the <li> & <a> & set the on class... mmm yeah, I like that idea.