Upgrading to ASP.NET MVC 2.0

Posted 17 March 2010, 19:46 | by | Perma-link

As you might have seen, ASP.NET MVC 2.0 was released this week at MIX along with a handy tool to help upgrade your solutions, so I decided to take the plunge and see how much effort I'd need to go through to upgrade. It turns out not much.

I also managed to move my admin section off to an area, which meant I could finally have a URL structure that allowed me to have duplicate controllers for blogs and albums, which is something I'd wanted since I built them (as opposed to an AdminBlogs controller and an AdminAlbums controller, and it meant that the Tabs controller, used for autocomplete on the photo admin screens, could be nicely grouped with them as well).

The only outstanding piece of work now is to move to using Data Annotations for my form validation, which is where the bulk of the pain I'd encountered lay.

Basically, I'd taken a similar approach as the validation pattern in Nerd Dinner (pp. 37f): I'd created a view model that implemented the following IValidatable interface:

public interface IValidatable
{
  bool IsValid { get; }
  IEnumerable<RuleViolation> GetRuleViolations();
  void OnValidate();
}

I initially created an abstract BaseValidater class, but of course you can't have multiple inheritance in C#, so this only worked for my non-entity classes… The RuleViolation is identical to the Nerd Dinner one.

So for each View Model I created an enumerable Errors with yields for each possible error, for example (line breaks added to long strings for formatting):

public IEnumerable<RuleViolation> GetRuleViolations() {
  if (string.IsNullOrEmpty(PostTitle) || 3 > PostTitle.Trim().Length) {
    yield return new RuleViolation("The title is required.", "PostTitle");
  }

  if (3 > PostPath.Trim().Length || !PostPath.IsUrlPart()) {
    yield return new RuleViolation("The path is required, and should only " +
                        "contain alpha-numerics and hyphens.", "PostPath");
  }

  if (50 > PostBody.Trim().Length) {
    yield return new RuleViolation("The post body is required, and should " +
                        "be longer than 50 characters", "PostBody");
  }
}

And coupled this with the following implementation (tweaked from Nerd Dinner - I prefer using .Any() to .Count() as that only checks to see if there are any values, rather than enumerating over the entire set - not an issue for in memory objects like this, but something to consider with databases Wink):

public bool IsValid {
  get { return (!GetRuleViolations().Any()); }
}

Then in the controller I went with the following code to handle errors:

if (blog.IsValid)
{
  [...]
  return [...]
}

foreach (RuleViolation violation in photo.GetRuleViolations()) {
  ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
  ModelState.SetModelValue(violation.PropertyName,
             new ValueProviderResult(
                   ValueProvider[violation.PropertyName].AttemptedValue,
                   collection[violation.PropertyName],
                   System.Globalization.CultureInfo.CurrentCulture));
}

The ValueProvider in 1.0 was an IDictionary but this has changed in 2.0 to be an IValueProvider, which means that you can't just iterate through without knowing what the form items were called (which I wasn't, but others are) so my code changed to:

foreach (RuleViolation violation in photo.GetRuleViolations()) {
  ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
  ModelState.SetModelValue(violation.PropertyName,
           new ValueProviderResult(
                 ValueProvider.GetValue(violation.PropertyName).AttemptedValue,
                 collection[violation.PropertyName],
                 System.Globalization.CultureInfo.CurrentCulture));
}

The only other issue I had was how this was all going to play on IIS 6, but that turned out to be very easy in the RegisterArea method:

public override void RegisterArea(AreaRegistrationContext context) {
  context.MapRoute(
        "Admin_default",
        "Admin.aspx/{controller}/{action}/{id}",
        new { action = "Index", id = UrlParameter.Optional }
  );
}

All in all, very easy and smooth.

Filed under: ASP.NET MVC, Site Updates