New to The Big Boy MVC Series?
Read the series from its humble beginnings.
The good news: in my last Big Boy post, we created a prettyish design-friendly EditorTemplate. That was fun. So, with our EditorTemplate completed, our FoodAdd page is looking all dolled up and decently man-spiffy:
The Bad News: I’m behind schedule. I wanted to finish this project in a month, and, if you’ve been paying close attention to the time I’ve wasted fiddling with the project, you’d know that I’m a week past my deadline. I’m not mad, or ashamed, or crying. Please don’t make fun of me. Of all the holdups, blogging has been the greatest hindrance (it takes me a couple of hours to write about a task that takes 3 – 5 minutes to accomplish), but blogging has also been the most beneficial restraint that I’ve imposed on myself–an even more beneficial restraint than the ol’ unit tests. And, as far as I’m concerned, the public documentation (and the public support/critique) is well-worth the holdup. So, it’s been a learning experience, and I want to keep learning. So, the blogging shall continue.
Anyways, I want to apologize for the quasi-failure (if you knew me better, you would’ve seen it coming), and I pray that, if you’re reading this series after I’ve completed writing it, you’ll still be able to create your website in a month–as a benefit of my slower process of meandering and divulging.
As a thank you for following along, I’m going to post the project on Codeplex sometime in the next couple of days. If the speed of my blogging is holding you up, you can download the project, look at the changes I’ve made, and be on your merry way. For those of you that are okay with the slower pace, never fear; I’ll keep chugging along in blog form. Until my arms fall off. And my legs. And until every last one of you is sick of my mouthwatering Yegge-like chops. Mmm.
Alright. Enough banter. Today is special. Why, you ask? Because, today, we’re going to tie up a bunch of loose ends. And, when those loose ends are, eh, so unloose it hurts, we’ll finally be able to experience an uber-desired form post. That’s right. A successful form post. It will be a truly fist-pump-worthy event in our otherwise banal and lonely lives.
Let’s start with an homage to Kazi Manzur’s List of MVC Best Practices. Before you read any further in this post, I recommend that you read Kazi’s recommendations. While reading, you’ll notice that Kazi recommends that you implement the PRG pattern (something I blogged about here). In laymen’s terms: when the user posts data, we’ll verify the data. If the data sucks (no Food Name, no Restaurant Name, bad Address that we can’t successfully geolocate), we’ll redirect right back to the FoodAdd page, and we’ll tell the user what he did wrong. If the data doesn’t suck, we’ll redirect to the FoodAdded page, and we’ll thank the user for not sucking.
We have one hurdle when we’re implementing the PRG pattern: when the user sucks, we’re going to redirect back to the FoodAdd page. When we redirect, MVC is going to destroy any/all of our ModelState errors. But, in this case, we need to preserve those precious ModelState errors because we’ll use those errors to show the user exactly why and how he sucks. So, in essence, ModelState is our mirror; our user is a fat ugly delusional mess, and we need to do the legwork to plop that mirror right in front of our fat, ugly user’s mess-of-a face.
Here’s where Kazi comes in. To pass our ModelState from one request to another, we’ll create two ActionFilters. The first attribute, called PassState, will insert our ModelStateDictionary into TempData. The second attribute, called GetState, will retrieve our ModelStateDictionary from TempData. If you’re new to ActionFilters, you might want to check out this msdn article before diving into deeper waters.
Here’s our PassState ActionFilter. Notice that we’re adding ModelState to TempData when our ModelState is invalid. This type of scenario is exactly why TempData exists: we can use it to pass data to an action when we redirect (Craig Stuntz goes so far as to call TempData “RedirectData” for this very reason). If you’re using TempData for any purpose other than passing data right before you redirect, you’re probably better off using ViewData. That said, here’s our PassState ActionFilter, which uses TempData appropriately:
using System.Web.Mvc;
namespace Big.Fat.Dish.Controllers.ActionFilters
{
public class PassStateAttribute : ActionFilterAttribute
{
public const string TempDataTransferKey = "__Big.Fat.Dish.TempDataTransferKey";
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[TempDataTransferKey] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
}
And here’s our GetState ActionFilter. We’re grabbing the ModelState object that we lodged into TempData, and we’re merging it with the current request’s ModelState. Here goes nothing:
using System.Web.Mvc;
namespace Big.Fat.Dish.Controllers.ActionFilters
{
public class GetStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string key = PassStateAttribute.TempDataTransferKey;
ModelStateDictionary modelState = filterContext.Controller.TempData[key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(key);
}
}
base.OnActionExecuted(filterContext);
}
}
}
So, we stole this code from someone else. But that doesn’t mean that we shouldn’t test it. As you might imagine, testing ActionFilters can be a little tricky, because you have to mock out a bunch of classes that contain the word “Context” in their cozy little class names. As you’ll surely come to realize (if you haven’t already), “Context” is an MS keyword for “We’re doing some crazy unmockable shit in this hizzy!” Unfortunately, it’s these very “Context” hizzies that we really should mock up, because we’re way more likely to do something really stupid when we play with them.
Up to this point, I’ve been using RhinoMocks to mock out my objects. To be honest, I used RhinoMocks because I wanted to try it out. I don’t mind RhinoMocks, but, the reality remains: I’m an avid Moq fan. I love Moq like I love the ladies (and the Legos). And I’d be doing you a real disservice if I didn’t at least introduce you to Moq’s vibratory Mockgasms.
If you’ve never worked with Moq, you can read the QuickStart guide here. Once you’ve digested the basics, I suggest that you head over to StackOverflow and read some of the Moq-related posts there. Once you get the hang of it, I think you’ll enjoy Moq’s terser syntax. I think you’ll also notice that Moq is just as powerful as RhinoMocks, if not more so, especially when it comes to giving you undeniable and pleasurable mockgasms (did I already try that joke?).
So, let’s setup a couple tests to verify that Kazi’s ActionFilters are going to work properly in our project. (Never doubt Kazi; you might be struck by a heavy flash of Jesus-steered lightning). To begin, let’s go ahead and test PassState:
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Big.Fat.Dish.Controllers.ActionFilters;
using Big.Fat.Dish.Controllers;
namespace Big.Fat.Dish.Tests.Controllers.ActionFilters
{
[TestClass]
public class PassStateTest
{
PassStateAttribute passState;
FoodController controller;
Mock<ActionExecutedContext> actionContext;
[TestInitialize]
public void TestInitialize()
{
actionContext = new Mock<ActionExecutedContext>();
passState = new PassStateAttribute();
controller = new FoodController();
actionContext.Setup(m => m.Controller).Returns(controller);
}
[TestMethod]
public void Controllers_ActionFilters_PassState_Inserts_ModelState_Into_TempData_If_ModelState_Is_Invalid_And_Result_Is_Redirect()
{
controller.ModelState.AddModelError("anyString", "anyError");
actionContext.Object.Result = new RedirectResult("/");
passState.OnActionExecuted(actionContext.Object);
Assert.IsTrue(controller.TempData.ContainsKey(PassStateAttribute.TempDataTransferKey));
Assert.AreEqual(controller.ModelState, controller.TempData[PassStateAttribute.TempDataTransferKey]);
}
[TestMethod]
public void Controllers_ActionFilters_PassState_Does_Not_Insert_ModelState_Into_TempData_If_ModelState_Is_Invalid_And_Result_Is_Not_Redirect()
{
controller.ModelState.AddModelError("anyString", "anyError");
actionContext.Object.Result = new ViewResult();
passState.OnActionExecuted(actionContext.Object);
Assert.IsFalse(controller.TempData.ContainsKey(PassStateAttribute.TempDataTransferKey));
}
[TestMethod]
public void Controllers_ActionFilters_PassState_Does_Not_Insert_ModelState_Into_TempData_If_ModelState_Is_Valid()
{
actionContext.Object.Result = new RedirectResult("/");
passState.OnActionExecuted(actionContext.Object);
Assert.IsFalse(controller.TempData.ContainsKey(PassStateAttribute.TempDataTransferKey));
}
}
}
And, similarly, let’s test GetState:
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Big.Fat.Dish.Controllers;
using Big.Fat.Dish.Controllers.ActionFilters;
namespace Big.Fat.Dish.Tests.Controllers.ActionFilters
{
[TestClass]
public class GetStateTest
{
GetStateAttribute getState;
FoodController controller;
Mock<ActionExecutedContext> actionContext;
ModelStateDictionary passedDictionary;
[TestInitialize]
public void TestInitialize()
{
getState = new GetStateAttribute();
controller = new FoodController();
actionContext = new Mock<ActionExecutedContext>();
passedDictionary = new ModelStateDictionary();
passedDictionary.AddModelError("anyError", "anyMessage");
actionContext.Setup(m => m.Controller).Returns(controller);
}
[TestMethod]
public void Controllers_ActionFilters_GetState_Merges_ModelState_If_Result_Is_ViewResult_And_TempData_Contains_Key()
{
actionContext.Object.Result = new ViewResult();
controller.TempData[PassStateAttribute.TempDataTransferKey] = passedDictionary;
getState.OnActionExecuted(actionContext.Object);
Assert.IsTrue(controller.ModelState.ContainsKey("anyError"));
}
[TestMethod]
public void Controllers_ActionFilters_GetState_Does_Not_Merge_ModelState_If_Result_Is_Not_ViewResult()
{
actionContext.Object.Result = new RedirectResult("/");
controller.TempData[PassStateAttribute.TempDataTransferKey] = passedDictionary;
getState.OnActionExecuted(actionContext.Object);
Assert.IsFalse(controller.ModelState.ContainsKey("anyError"));
}
[TestMethod]
public void Controllers_ActionFilters_GetState_Does_Not_Merge_ModelState_If_TempData_Does_Not_Contain_Key()
{
actionContext.Object.Result = new ViewResult();
getState.OnActionExecuted(actionContext.Object);
Assert.IsFalse(controller.ModelState.ContainsKey("anyError"));
}
}
}
Now that we have our ActionFilters built and tested, we can move on (or return yet again) to our FoodController. Since we last visited our FoodController, we’ve done more than our fair share of work. For starters, we created the ViewModel layer (and mapped our ViewModel to our Model via AutoMapper). We also created a Geocoder, a Permalink generator, and, more recently, we created two ActionFilters (remember?). Let’s tie it all together:
using System;
using System.Web.Mvc;
using Big.Fat.Dish.Models;
using Big.Fat.Dish.ViewModels;
using Big.Fat.Dish.WebRequests.Geocoding;
using Big.Fat.Dish.Controllers.ActionFilters;
namespace Big.Fat.Dish.Controllers
{
public class FoodController : Controller
{
IGeocoder lazyGeocoder;
IGeocoder geocoder
{
get
{
if (lazyGeocoder == null)
{
lazyGeocoder = new Geocoder();
}
return lazyGeocoder;
}
}
IRepository lazyRepo;
IRepository repo
{
get
{
if (lazyRepo == null)
{
lazyRepo = new Repository();
}
return lazyRepo;
}
}
public FoodController() : this(null, null) { }
public FoodController(IRepository repo, IGeocoder geocoder)
{
this.lazyRepo = repo;
this.lazyGeocoder = geocoder;
}
[HttpGet, GetState]
public ViewResult Add()
{
return View("Add");
}
[HttpPost, PassState]
public ActionResult Add(FoodAdd form)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Add");
}
Food food = form.Map();
food.Restaurant.Address = geocoder.GetAddressFromString(form.OneLineAddress);
if (food.Restaurant.Address == null)
{
ModelState.AddModelError("OneLineAddress", "You provided an invalid Restaurant Address.");
return RedirectToAction("Add");
}
this.repo.Foods.CreatePermalinksFor(food);
this.repo.Add(food);
this.repo.Save();
return RedirectToAction("Added", new { id = food.Id });
}
public ViewResult Added(int id)
{
var model = new FoodAdded
{
Food = this.repo.Foods.WithId(id)
};
return View(model);
}
public ViewResult Details(int id)
{
var model = new FoodDetails
{
Food = this.repo.Foods.WithId(id)
};
return View(model);
}
}
}
So, let’s take a minute to reflect. When a user posts data in our food form, that data will get bound (via MVC’s DefaultModelBinder). If the user did anything stupid, we’ll redirect him back to the FoodAdd page (with ModelState errors intact). If the user didn’t screw anything up, we go ahead and geocode his OneLineAddress. If the geocoder can’t create an address, we’ll redirect the user back to the FoodAdd page (with a OneLineAddress error). If the geocoder successfully creates an address, we generate permalinks for all of our records (Food, Address, Restaurant), and we save our food in our good ol’ fashioned database.
You’ll notice that I’m not showing you everything here. For example, I created a ViewModel class for each of my ViewPages (FoodAdd, FoodAdded, FoodDetails, and HomeIndex). And I also extended my Foods pipe with the CreatePermalinks() method. And, prior to anything else, I created some more coveted unit tests for the new functionality. Since I’ve completed all of these tasks before (in some fashion or another), I’m hoping that you can take the initiative to do some work on your side of tracks, thus allowing me to conserve some of my “precious keystrokes”.
Extra Credit
If you’re a visual learner, I don’t blame you. Let’s take a look at the monster we’ve created. For starters, set a stopping point in your post method. It doesn’t matter where, really:
Start your project, and navigate to the “/Food/Add” page. Add some data into the form. Here’s my attempt at being user-like with my QAing:
Hit the huge-ass Add Food button. Your post method should get invoked, so your project should pause at the breaking point. So, VS should look (and feel) something like this:
Take a look at the FoodAdd parameter. You’ll notice that the data I posted has been property populated (i.e. “bound”) into my FoodAdd class:
Now, let’s check out the Map() method, which should convert our FoodAdd ViewModel into a Food class. Lo and behold, it does exactly what we had hoped and dreamed!
Now, the geocoder. I gave the geocoder a really crappy address to resolve, so I was hoping that we’d get a ModelState error. But, life is funny. And it looks like our geocoder is trying a little too hard to impress us. So, we get an Address returned to us after all (something we’ll definitely want to tweak at a later date [an easy fix]). Hey, at least we know that the geocoder is working, which is good:
The post succeeds, and we’re transported to the FoodAdded page. I’ve spruced this page up just a little bit, so don’t be afraid. Also notice that I’m still tinkering with the verbiage:
Now, to top it all off, take a look in your database. Everything should be fine and dandy:
Cool. So we have one successful (although unintentional) post under our belts. Let’s navigate back to the “/Add/Food” page and submit a blank form. Again, our project is paused at the breaking point. If you fish around, you’ll notice that ModelState is not valid. Then, you’ll notice that ModelState contains three errors. Then, if you do some serious digging, you’ll find the error messages that ModelState has created for us:
Now it’s time for our EditorTemplate to do its magic. The user is redirected to the FoodAdd page, which now displays all of the ModelState error messages (thanks to our PassStateAttribute, GetStateAttribute, and EditorTemplate):
Good times. More good times are surely ahead of us.
Move on to Part 26: It’s Codeplex Time!.
Read More
You can leave a response, or trackback from your own site.
8 Responses to “The Big Boy MVC Series — Part 25, Going Postal: ActionFilters, Moq, ModelState, Oh My!”
Leave a Reply














[...] Move on to The Big Boy MVC Series — Part 25, Going Postal: ActionFilters, Moq, ModelState, Oh My! [...]
[...] The Big Boy MVC Series – Part 25, Going Postal: ActionFilters, Moq, ModelState, Oh My! – Evan Nagle has hit part 25 of his comprehensive series on ASP.NET MVC. This post gathers together a number of themes from previous posts exploring the use of Moq, ModelState and TempData to implement the Post-Redirect-Get pattern fully, along the way providing links to further details on these topics. [...]
Interesting idea, but you seem to be trying to solve a minor edge case problem. You can preserve your model state by simply calling View(“FoodAdd”, form) when your model isn’t valid. Or, you can change your action method name to FormAdd so you have an overloaded method to handle posts (whereas the other method only renders the view version). In that case you can simply call View(form).
I suppose there are scenarios where that’s prohibitive, but that’s a small minority of the time. Again, interesting concept, but there are already built-in methods to handle moving state around without needing to muck around with TempData.
Moreover, TempData uses Session state under the hood. I’m not personally a fan of using session simply to pass data around… especially, again, since MVC can already do that trick.
Hey acoustic,
I definitely appreciate the simplicity. But how do you handle unintentional duplicate posts? And how do you avoid the “To display the webpage again” message?
I might be missing something, so please let me know. If you want a better explanation of the PRG pattern (better, at least, than the measly explanation I provided) take a look at this post: http://devlicio.us/blogs/tim_barcz/archive/2008/08/22/prg-pattern-in-the-asp-net-mvc-framework.aspx
Again, would love to hear what you think. Still not buying my dogfood?
hi evan!
i’m glad you decided to implement prg – not doing this by default is, in my opinion, one of the greatest flaws of the entire mvc framework.
after reading your last comment before the extra credit section, i’m a little curious on how you implemented the map between Food and FoodDetails, since it is in the opposite direction of the one you showed on FoodAdd->Food. do you make Food inherit MappedTo (and thus introducing a circular dependency…!) or did you solve it some other way?
keep up the good work!
// tomas
Hey Tomas,
I really appreciate your input. Make sure to keep me in line.
As for the FoodDetails class — I’m grabbing the Food record from the database via my repository (Part 19).
Since the FoodDetails page is just a ViewPage without a form, I figured I’d just create a ViewModel that wrapped around the Food record that I snagged from the database.
I suppose I could retrieve the Food record by id and then map it to a FoodDetails class (with all the same properties as my Food class), but I’m not sure if that’s worth all of the trouble. Is it? Hmm. I’d be interested to hear what you think.
Cheers,
Evan
Hi Evan,
Just wanted to write in to thank you for this series. It is the best approach I have seen for a relative newbie. I really appreciate all of your effort and dedication.
Hope your move is going well. The delay has given me a chance to get caught up to the current post, and try to better understand what we’ve already done.
Thanks Again.
Much thanks Jeremy. I’m in New York right now, but when I come back, I plan on DESTROYING the rest of this series with some SERIOUS WISDOM.
Until then,
Evan