So if you’ve used MVC, and I know you have because you want to be cool like me, you’ve most likely run into an error like this:
Certain parameter wasn’t found. Make [parameter] nullable.
In other words, the parameter didn’t show up in the URL and therefor MVC can’t assign a value to it. This would of course happen with anything that isn’t nullable. Now you could do this:
void SomeAction(Int32? pageNumber) { if(someParameter.HasValue) { .. } }
But do you really feel like putting that in every stupid method that happens to have the same parameter? Say the parameter is something simple like pageNumber. Taking the above code, you would have to see if pageNumber has a value and if it doesn’t, continue to set some variable to a default. Kind of annoying to have to repeat that over and over again when you know you will be looking for pageNumber on many actions. Well Attributes, or more importantly ActionFilterAttribute, are the way to get around this.
Say with the pageNumber example you want a default of 1 if it doesn’t exist. Well that’s easy to do, it would be something like this:
public sealed class CheckPageNumberAttribute : ActionFilterAttribute { private const String DefaultPageNumber = 0; //This will fire automatically on page load. public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); //ActionParameters.Values holds all the request parameters after the ? as in ?pageNumber=1 //See if it exists first. If not, add it if (!filterContext.ActionParameters.ContainsKey("pageNumber")) { filterContext.ActionParameters.Add(RequestConstants.PageNumber, null); } //Set to default if it's not null filterContext.ActionParameters["pageNumber"] = filterContext.ActionParameters["pageNumber"] ?? DefaultPageNumber; } }
So the method looks more like this now:
[CheckPageNumberAttribute] void SomeAction(Int32 someParameter) { ... }
And you can now do your … without worry.
But what if the url is like this?
ShowMeACoolRoom/Index/1/
And you have your route set up like:
{controller}/{action}/{roomId}
And you want to make sure that not only does that userId exist in the url, but the user actually exists. Well you don’t look to the ActionParameters.
public sealed class RoomExistsAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); ChatRoom foundRoom = null; //RouteData.Values holds all the values in the url it was able to match to a route //Check the Values list in the RouteData for the id if(context.RouteData.Values.ContainsKey("id")) { //Convert to an integer, ConvertTo is just a method I've made. Int32? roomId = Convert.ToString(context.RouteData.Values["id"]).ConvertTo(); if(roomId.HasValue) { foundRoom = ChatRoom.GetRoomByID(roomId.Value); } } //If the room isn't found, handle the error. if (foundRoom == null) { //redirect on error... context.Result = new RedirectToRouteResult ( GeneralConstants.RouteName, new RouteValueDictionary { { "controller", "sharedError" }, { "action", "error" }, { "name", "someErrorKey" } } ); } } }
Now the new method looks something like this:
[RoomExistsAttribute] [CheckPageNumberAttribute] void SomeAction(Int32 roomId, Int32 someParameter) { ... }
And once again you are free to … without worry of the room not existing. Next post I plan on adding some more “advanced” attributes… hahaha advanced? This site? hahahaha sorry. But I will be adding a new post about certain attribute solutions I’ve found that work well.
You can omit the Attribute suffix when applying attributes:
[RoomExists]
[CheckPageNumber]
void SomeAction(Int32 roomId, Int32 someParameter)
{
…
}
Thanks for this nice article.
True story. Good eye. I’d like to say that I did that for instructional purposes, but it was probably me just being dumb.
Why not just use this.
public ActionResult Index(int page = 1)
{
return View();
}
If it doesn’t exist, it’s automatically 1.
Well the main reason was that I think that functionality was added in MVC 2, which wasn’t in full release mode when I wrote this post. Unfortunately, I didn’t mark this as MVC 1.