Skip to main content

2 posts tagged with "dataannotations"

View All Tags

A folk story wherein we shall find dates, DataAnnotations & data impedance mismatch

If you ever take a step back from what you're doing it can sometimes seem pretty abstract. Here's an example. I was looking at an issue in an app that I was supporting. The problem concerned a field which was to store a date value. Let's call it, for the sake of argument, valuation_date. (Clearly in reality the field name was entirely different... Probably.) This field was supposed to represent a specific date, like June 15th 2012 or 19th August 2014. To be clear, a date and *not* in any way, a time.

valuation_date was stored in a SQL database as a <a href="http://msdn.microsoft.com/en-gb/library/ms187819.aspx">datetime</a>. That's right a date with a time portion. I've encountered this sort of scenario many times on systems I've inherited. Although there is a <a href="http://msdn.microsoft.com/en-gb/library/bb630352.aspx">date</a> type in SQL it's pretty rarely used. I think it only shipped in SQL Server with 2008 which may go some way to explaining this. Anyway, I digress...

valuation_date was read into a field in a C# application called ValuationDate which was of type <a href="http://msdn.microsoft.com/en-us/library/system.datetime.aspx">DateTime</a>. As the name suggests this is also a date with a time portion. After a travelling through various layers of application this ended up being serialized as JSON and sent across the wire where it became a JavaScript variable by the name of valuationDate which had the type <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">Date</a>. Despite the deceptive name this is also, you guessed it, a date with a time portion. (Fine naming work there JavaScript!)

You can probably guess where I'm going with this... Despite our (cough) rock solid naming convention, the situation had arisen where actual datetimes had snuck in. That's right, in the wilds of production, records with valuation_dates with time components had been spotted. My mission was to hunt them, kill them and stop them reproducing...

A Primitive Problem#

Dates is a sticky topic in many languages. As I mentioned, SQL Server has a <a href="http://msdn.microsoft.com/en-gb/library/bb630352.aspx">date</a> data type. C# has <a href="http://msdn.microsoft.com/en-gb/library/system.datetime.aspx">DateTime</a>. If you want to operate on Dates alone then you're best off talking looking at Jon Skeet's NodaTime - though most people start with DateTime and stick with it. (After all, it's native.) As to JavaScript, well primitive-wise there's no alternative to Date - but <a href="http://momentjs.com/">Moment.js</a> may help.

My point is that it is a long standing issue in the development world. We represent data in types that aren't entirely meant for the purpose that they are used. It's not just restricted to dates, numbers have a comparable story around the issue of decimals and doubles. As a result of data type issues, developers experience problems. Like the one I was facing.

An Attribute Solution#

The source of the problem turned out to be the string JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">Date constructor</a> in an earlier version of Internet Explorer. The fix was switching away from using the JavaScript Date constructor in favour of using Moment.js's more dependable ability to parse strings into dates. Happy days we're working once more! Some quick work to put together a SQL script to fix up the data and we have ourselves our patch!

But we didn't want to get bitten again. We wanted ourselves a little belts and braces. What do do? Hang on a minute, lads โ€“ I've got a great idea... It's <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute(v=vs.110).aspx">ValidationAttribute</a> time!

We whipped ourselves up an attribute that looked like this:

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace My.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public class DateOnlyAttribute: ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
if (value is DateTime)
{
// Date but not Time check
var date = (DateTime) value;
if (date.TimeOfDay != TimeSpan.Zero)
{
return new ValidationResult(date.ToString("O", CultureInfo.InvariantCulture) + " is not a date - it is a date with a time", new[] { validationContext.MemberName });
}
}
else
{
return new ValidationResult("DateOnlyAttribute can only be used on DateTime? and DateTime", new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
}

This attribute does 2 things:

  1. Most importantly it fails validation for any DateTime or DateTime? that includes a time portion. It only allows through DateTimes where the clock strikes midnight. It's optimised for Cinderella.
  2. It fails validation if the attribute is applied to any property which is not a DateTime or DateTime?.

You can decorate DateTime or DateTime? properties on your model with this attribute like so:

namespace My.Models
{
public class ImAModelYouKnowWhatIMean
{
public int Id { get; set; }
[DateOnlyAttribute]
public DateTime ValuationDate { get; set; }
// Other properties...
}
}

And if you're using MVC (or anything that makes use of the validation data annotations) then you'll now find that you are nicely protected from DateTimes masquerading as dates. Should they show up you'll find that ModelState.IsValid is false and you can kick them to the curb with alacrity!

Unit testing ModelState

  • Me: "It can't be done"
  • Him: "Yes it can"
  • Me: "No it can't"
  • Him: "Yes it can, I've just done it"
  • Me: "Ooooh! Show me ..."

The above conversation (or one much like it) took place between my colleague Marc Talary and myself a couple of weeks ago. It was one of those faintly embarrassing situations where you state your case with absolute certainty only to subsequently discover that you were *completely* wrong. Ah arrogance, thy name is Reilly...

The disputed situation in this case was ModelState validation in ASP.Net MVC. How can you unit test a models validation driven by DataAnnotations? If at all. Well it can be done, and here's how.

Simple scenario#

Let's start with a simple model:

And let's have a controller which makes use of that model:

When I was first looking at unit testing this I was slightly baffled by the behaviour I witnessed. I took an invalid model (where the properties set on the model were violating the model's validation DataAnnotations):

I passed the invalid model to the Edit controller action inside a unit test. My expectation was that the ModelState.IsValid code path would *not* be followed as this was *not* a valid model. So ModelState.IsValid should evaluate to false, right? Wrong!

Contrary to my expectation the validity of ModelState is not evaluated on the fly inside the controller. Rather it is determined during the model binding that takes place *before* the actual controller action method is called. And that completely explains why during my unit test with an invalid model we find we're following the ModelState.IsValid code path.

Back to the dispute#

As this blog post started off I was slightly missing Marc's point. I thought he was saying we should be testing the ModelState.IsValid == false code path. And given that ModelState is determined before we reach the controller my view was that the only way to achieve this was through making use of ModelState.AddModelError in our unit test (you can read a good explanation of that here). And indeed we were already testing for this; we were surfacing errors via a JsonResult and so had a test in place to ensure that ModelState errors were transformed in the manner we would expect.

However, Marc's point was actually that we should have unit tests that enforced our design. That is to say, if we'd decided a certain property on a model was mandatory we should have a test that checked that this was indeed the case. If someone came along later and removed the Required data annotation then we wanted that test to fail.

It's worth saying, we didn't want a unit test to ensure that ASP.Net MVC worked as expected. Rather, where we had used DataAnnotations against our models to drive validation, we wanted to ensure the validation didn't disappear further down the track. Just to be clear: we wanted to test our code, not Microsoft's.

Now I get to learn something#

When I grasped Marc's point I thought that the the only way to write these tests would be to make use of reflection. And whilst we could certainly do that I wasn't entirely happy with that as a solution. To my mind it was kind of testing "at one remove", if you see what I mean. What I really wanted was to see that MVC was surfacing validations in the manner I might have hoped. And you can!

.... Drum roll... Ladies and gents may I present Marc's ModelStateTestController:

This class is, as you can see, incredibly simple. It is a controller, it inherits from System.Web.Mvc.Controller and establishes a mock context in the constructor using MOQ. This controller exposes a single method: TestTryValidateModel. This method internally determines the controller's ModelState given the supplied object by calling off to Mvc's (protected) TryValidateModel method (TryValidateModel evaluates ModelState).

This simple class allows us to test the validations on a model in a simple fashion that stays close to the way our models will actually be used in the wild. It's pragmatic and it's useful.

An example#

Let me wrap up with an example unit test. The test below makes use of the ModelStateTestController to check the application of the DataAnnotations on our model:

Wrapping up#

In a way I think it's a shame that TryValidateModel is a protected method. If it weren't it would be simplicity to write a unit test which tested the ModelState directly in context of the action method. It would be possible to get round this by establishing a base controller class which all our controllers would inherit from which implemented the TestTryValidateModel method from above. On the other hand maybe it's good to have clarity of the difference between testing model validations and testing controller actions. Something to ponder...