Asp.Net MVC programming advice. Toodles, Evan Nagle.
Jun
25

New to The Big Boy MVC Series?
Read the series from its humble beginnings.


Happy times.

Today, I’m going to introduce you to “someone”. You might already know this “someone”. This “someone” is as important to the professional developer as the melded force of the developer’s wife, the developer’s favorite pet, and the developer’s favorite mechanical tool. That “someone” is a pest, a genius, a nuisance, a narcissist, and a nerd. And that “someone” is… (dramatic pause, please…) the user who loves to make you suffer. At any cost. At any time. Without fear of any repercussion. In fact, as a principle:

The user who loves to make you suffer will be the first person to visit your website, and he’ll be the very last person to leave.

And, perhaps more to the point:

The user who loves to make you suffer will do anything in his power to force the meaningfulness of your life to disappear. Like milk globules gurgled down in a McDonald’s frappe.

So, start getting to know the user who loves to make you suffer. Like, biblically. Because you’ll be living with him for the next umpteen years. And, learning to live with TUWLTMYS (Tulmys?) now is better than learning to live with him later. Tulmys replies:

a’;DROP TABLE users; SELECT * FROM userinfo WHERE ‘t’ = ‘t

Today, then, we’ll start our all-out battle against Tulmys, the user who loves to make us suffer (screw ‘em). And we’ll start by hitting him with a quasi-low-blow to the balls: Data Validation. That’s right. Data Validation. Take that, Tulmys (Thomas?).

Lucky for us, with Asp.Net MVC 2, Data Validation is as easy as using DataAnnotations. If you’re new to the idea of DataAnnotations-based validation, or if you just want to refresh your memory, check out Brad Wilson’s post on DataAnnotations and MVC. If you’re hungry for more, check out Phil Haack’s great post on DataAnnotations. If you are still yearning for a better sense of how it all works, you can check out Steve Sanderson’s xVal project, which is where the idea was initially conceived (and birthed). If you still want more, check out Scott Gu’s post (yet another relic from Scott’s rather obscure blog o’ profanity).

Step 1: Create a ValidationHelperTest class. Place the class in the Models/Validation folder of your Test project. We’ll add more files to this folder in a hot minute.

Val Class e1277422231577 The Big Boy MVC Series    Part 20: Buddy Classes, Reflection, and Model Validation

Step 2: Write some unit tests That’s right. More unit tests. But these unit tests are special. We’re actually creating some meta unit tests, as we’re testing functionality that we’ll be using in other unit tests (and not in our actual project). Whoa momma. Crazy stuff. I’ll explain more later. But, for now, try to figure it out for yourself:


//Models/Validation/ValidationTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Tests.Models.Validation
{
    [TestClass]
    public class ValidationTest
    {
        [MetadataType(typeof(AMD))]
        public class A
        {
            public class AMD
            {
            }
        }

        public class B
        {
            public class BMD
            {
            }
        }

        [TestMethod]
        public void Testing_Meta_ValidationHelper_HasMetaDataTypeOf_Returns_True()
        {
            Assert.IsTrue(typeof(A).HasMetadataTypeOf<A.AMD>());
        }

        [TestMethod]
        public void Testing_Meta_ValidationHelper_HasMetaDataTypeOf_Returns_False()
        {
            Assert.IsFalse(typeof(B).HasMetadataTypeOf<B.BMD>());
        }
    }
}

Figure it out? Kind of? Maybe? Well, if you didn’t: we’re designing (via TDD) an extension method for the Type class. This extension method returns true if the calling Type is associated with the MetadataType specified in angle brackets. So, for example, A has a MetadataType of A.AMD, so we should expect our extension method to return true. B is our control, and it does not have a MetadataType of B.BMD, so we should expect our extension method to return false. Easy enough. Like that, the design work is done, now we just have to… well…

Step 3: Create a ValidationHelper class. Place the class in the Models/Validation folder of your Test project. Like always, we’ll only build as much as we need to in order to make our meta unit tests (above, in Step 2) pass. Hence, we write:


using System;
using System.Linq;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Tests.Models//.Validation
{
    public static class ValidationHelper
    {
        public static bool HasMetadataTypeOf<TAtt>(this Type t)
        {
            var metadataType = t
                .GetCustomAttributes(typeof(MetadataTypeAttribute), true)
                .FirstOrDefault()
                as MetadataTypeAttribute;
            return metadataType != null && typeof(TAtt) == metadataType.MetadataClassType;
        }
    }
}

Step 4: Create more unit tests. Verify that each entity class in our project has an associated MetadataType class. We have four entity classes: Food, Restaurant, Address, and State. So, we’ll create four test classes, and four unit tests. As always (I’ll stop regurgitating soon), we’re testing something that we haven’t built yet, so intellisense will try to destroy our will to live. That’s okay. Fight back. But don’t throw anything.

Step A: Create FoodTest.cs. Add FoodTest.cs to the Validation folder in your Test project. Notice that we’re using the HasMetadataTypeOf extension method that we created in our last step:


using Microsoft.VisualStudio.TestTools.UnitTesting;
using Big.Fat.Dish.Models;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Tests.Models//.Validation
{
    [TestClass]
    public class FoodValidationTest
    {
        [TestMethod]
        public void Models_Validation_Food_Has_MetadataType_Of_FoodMD()
        {
            Assert.IsTrue(typeof(Food).HasMetadataTypeOf<Food.FoodMD>());
        }
    }
}

Step B: Create RestaurantTest.cs. Add RestaurantTest.cs to the Validation folder in your Test project. I feel redundant right now:


using Microsoft.VisualStudio.TestTools.UnitTesting;
using Big.Fat.Dish.Models;

namespace Big.Fat.Dish.Tests.Models//.Validation
{
    [TestClass]
    public class RestaurantValidationTest
    {
        [TestMethod]
        public void Models_Validation_Restaurant_Has_MetadataType_Of_RestaurantMD()
        {
            Assert.IsTrue(typeof(Restaurant).HasMetadataTypeOf<Restaurant.RestaurantMD>());
        }
    }
}

Step C: Create AddressTest.cs. Add AddressTest.cs to the Validation folder in your Test project. If you really need to see this code, I’m crying on the inside for you:


using Microsoft.VisualStudio.TestTools.UnitTesting;
using Big.Fat.Dish.Models;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Tests.Models//.Validation
{
    [TestClass]
    public class AddressValidationTest
    {
        [TestMethod]
        public void Models_Validation_Address_Has_MetadataType_Of_AddressMD()
        {
            Assert.IsTrue(typeof(Address).HasMetadataTypeOf<Address.AddressMD>());
        }
    }
}

Step D: Create StateTest.cs Add StateTest.cs to the Validation folder in your Test project. If you’re still reading this, you must’ve stumbled upon this blog looking for cute pictures of cats and lions. And, instead:


using Microsoft.VisualStudio.TestTools.UnitTesting;
using Big.Fat.Dish.Models;

namespace Big.Fat.Dish.Tests.Models//.Validation
{
    [TestClass]
    public class StateValidationTest
    {
        [TestMethod]
        public void Models_Validation_State_Has_MetadataType_Of_StateMD()
        {
            Assert.IsTrue(typeof(State).HasMetadataTypeOf<State.StateMD>());
        }
    }
}

Step 5: Run your unit tests. Enjoy the epic fail. Looks like we need to create four new classes (Food.FoodMD, Restaurant.RestaurantMD, Address.AddressMD, and State.StateMD). And, we need to create these classes inside the entity classes that were autogenerated by L2S:

epic fail e1277424465694 The Big Boy MVC Series    Part 20: Buddy Classes, Reflection, and Model Validation

Step 6: Extend each entity class with a partial class of your own. Lucky for us, the entity classes that L2S autogenerates are partial classes. So, we can extend the classes by creating a partial class of our own. We just need to make sure that our partial class (a.) has the same name, and (b.) lives in the same namespace.

Step A: Create a /Models/Validation folder in your web app. You’re getting older and more ninja-like, so I’ll spare you the photo.

Step B: Create some partial classes for Food, Restaurant, Address, and State. Add the partial classes to the /Models/Validation folder. In each file, make sure to crop out “.Validation” in the namespace:


//Models/Validation/Food.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    public partial class Food
    {
    }
}

//Models/Validation/Restaurant.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    public partial class Restaurant
    {
    }
}

//Models/Validation/Address.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    public partial class Address
    {
    }
}

//Models/Validation/State.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    public partial class State
    {
    }
}

Step 7: Inside each partial class, create a public buddy class. Each buddy class should be named {EntityClass}MD (per our test design specs). If only we had an entity class named DoogieHowser. Ah, anyhow. We’ll use DataAnnotations to link our buddy classes to our entity classes. That should make our unit tests giggly:


//Models/Validation/Food.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(FoodMD))]
    public partial class Food
    {
        public class FoodMD
        {
        }
    }
}

//Models/Validation/Restaurant.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(RestaurantMD))]
    public partial class Restaurant
    {
        public class RestaurantMD
        {
        }
    }
}

//Models/Validation/Address.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(AddressMD))]
    public partial class Address
    {
        public class AddressMD
        {
        }
    }
}

//Models/Validation/State.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(StateMD))]
    public partial class State
    {
        public class StateMD
        {
        }
    }
}

Step 8: You don’t suck! (As much). We’ve already created all of our partial classes and our buddy classes. So, at this point, our unit tests should actually pass. Don’t believe me? Try it for yourself.

tests pass e1277426761148 The Big Boy MVC Series    Part 20: Buddy Classes, Reflection, and Model Validation

Step 9: Configure your DataAnnotations. Here’s where we create our validation rules. Good times. Suck it, Thomas. We’re coming after you.

Sidenote: “configure” is the operative term here. Since we’re configuring our app (and not creating new app behavior), we shouldn’t feel too obliged to create unit tests to verify the current state of our configuration settings. In fact, unit tests would probably only cause unnecessary headache every time we decided to tweak one of our validation settings. That would suck for us, and, as far as I’m concerned, the tests we’ve already created do more than enough legwork: we’ve verified that our DataAnnotations are correctly attributed to respective L2S-generated entity classes, and, thus, we know that our validation settings will be applied to incoming data.

When specifying our validation rules, we’re really just regurgitating (and possibly refining) the rules that we specified for the columns in our SQL tables. So, when creating your DataAnnotations, it’s not a bad idea to use your database as a source of inspiration. I know I sure did:

Finished validation class: FoodMD. The rub:


using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(FoodMD))]
    public partial class Food
    {
        public class FoodMD
        {
            [Required, StringLength(200), DisplayName("Food Name")]
            public string Name { get; set; }

            [Required, StringLength(500), DisplayName("Food Description")]
            public string Description { get; set; }

            [Required]
            public string Permalink { get; set; }
        }
    }
}

Finished validation class: RestaurantMD. The rub:


using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(RestaurantMD))]
    public partial class Restaurant
    {
        public class RestaurantMD
        {
            [Required, StringLength(200), DisplayName("Restaurant Name")]
            public string Name { get; set; }
        }
    }
}

Finished validation class: AddressMD. The rub:


using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(AddressMD))]
    public partial class Address
    {
        public class AddressMD
        {
            [Required, StringLength(60), DisplayName("Address Line 1")]
            public string Line1 { get; set; }

            [StringLength(60), DisplayName("Address Line 2")]
            public string Line2 { get; set; }

            [Required, StringLength(30)] //TODO:regex?
            public string City { get; set; }

            [Required, StringLength(15)] //TODO:regex?
            public string PostalCode { get; set; }
        }
    }
}

Finished validation class: StateMD. The rub:


using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Big.Fat.Dish.Models//.Validation
{
    [MetadataType(typeof(StateMD))]
    public partial class State
    {
        public class StateMD
        {
            [Required, StringLength(50), DisplayName("State Name")]
            public string Name { get; set; }

            [Required, StringLength(5), DisplayName("State Abbreviation")]
            public string Short { get; set; }
        }
    }
}

Step 10: There is no Step 10! That’s it! Yep. You’ve successfully used buddy classes and DataAnnotations to configure your data validation settings. Feel good about yourself. You’re one step closer to finishing your website, and two steps closer to making Thomas your proverbial, lifelong bunkmate. Love at first site. Time to buy a bigger bed…


Move on to Step 21: Controller, Meet Repo. Repo, Meet Controller.

Read More

You can leave a response, or trackback from your own site.

4 Responses to “The Big Boy MVC Series — Part 20: Buddy Classes, Reflection, and Model Validation”

 
  1. [...] Move on to Part 20: Buddy Classes, Reflection, and Model Validation. [...]

  2. Michel says:

    Step 4A has the wrong source and Step 4D should have Assert.IsTrue(typeof(State).HasMetadataTypeOf());

  3. Evan says:

    Thanks, Michel. The first error was a stupid Copy/Paste mistake, and the second error was, well, exactly the same as error as what happened in your comment :) — I forgot to replace my left angle bracket with &lt;

    Should be fixed.

  4. [...] Don’t hesitate. You can actually delete all of the Buddy class crap that we created in this post. I feel better already. Like a cancerous weight has been lifted from my developmental [...]

 

Leave a Reply