ASP.NET MVC: Attributes and Semi Dynamic Check for Request Parameters

If I were self involved I would say something silly like IN THIS AWESOME POST but I’m not. However in this post that is awesome I gave some examples of how to use attributes to set defaults or just check to see if an incoming id could be matched to a record somewhere… Sorry lost my track of thought. Watching the begining of Transformers… You know the part where it could have been good.

Anyway, I figured I’d add another debatable use for an attribute, the CheckForGivenRequest one. Basically in the other post I had something that was specific it was checking for, this is used if you are checking for a request parameter but you don’t want to make an attribute for each and every one of them.

  //This is so you can use it many times on the same method
  //I know, I know... duh
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
  public sealed class CheckForGivenRequestAttribute : ActionFilterAttribute
  {
    //The constructor to set what should be looked for
    //Default amount is what it should be set if not there
    public CheckForGivenRequestAttribute(String requestParameterName, Object defaultAmount)
    {
      DefaultAmount = defaultAmount;
      RequestParameterName = requestParameterName;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      base.OnActionExecuting(filterContext);
      //Check the extra request parameters (ie &someId=1) for if it exists
      //If it doesn't exist, then add it
      if (!filterContext.ActionParameters.ContainsKey(RequestParameterName))
      {
        filterContext.ActionParameters.Add(RequestParameterName, null);
      }

      //If it's null set to the default
      filterContext.ActionParameters[RequestParameterName] =
         filterContext.ActionParameters[RequestParameterName] ?? DefaultAmount;
    }

    //Just the properties, nothing to see here.  Go away...
    private Object DefaultAmount { get; set; }
    private String RequestParameterName { get; set; }
  }

And then the use of it:

  [CheckForGivenRequestAttribute("someStupidId", 1)]
  public ActionResult INeedAnExample(Int32 someStupidId)
  {
    ...
  }

Now you might ask why not make that attiribute generic to avoid boxing.

Why not make that attribute generic to avoid boxing?

Glad you asked. Turns out that you can’t make attributes generic. Aparrently it’s somwhat debatable why but not possible at the time being. Besides, the ActionParameters collection is <String, Object> anyhow, so at some point any stuct would be boxed anyhow.

On a side note, I never noticed this before, but when one of the non descript Autobots crashes near a pool, some kid is there to ask if he is the Tooth Fairy? Seriously? Are kids really that dumb? Cause every picture I’ve seen of the Tooth Fairy has been a 20 foot tall metal thing with no discernible features.

ASP.Net MVC: Attributes, ActionFilterAttribute, and Why You Might Want To Use Them

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.

ASP.Net MVC: Routes, Route Contraints, and My Love Hate Relationship

So something that has been somewhat of an uphill climb as of late is routing in MVC. I think part of it is the way it works and part of it is that I’ve approached MVC like a 10 year old with a new game; F- the instructions, I’m all up in this.

What goes out must come in…

The biggest misconception I had with routes is that the route definitions meant something when a page is loading. This is bad. Now before the nerd rage starts to build, I will qualify that statement. Route definitions mean nothing beyond a simple mapping of values. What? Well suppose I have two routes:

   routes.MapRoute
   (
     ...
      "{controller}/{action}/{roomId}/{name}",
     ...
   );

and

   routes.MapRoute
   (
     ...
     "{controller}/{action}/{userId}/{name}",
     ...
   );

Now on the way out, the system can find the route easy if you supply the correct route data: (Lets say for the second route)

  routeValues = new RouteDicationary()
  routeValues.Add("controller", "Room");
  routeValues.Add("action", "Index");
  routeValues.Add("userId", 1);
  routeValues.Add("name", "ProgramminTool");

  neededHelper.RouteUrl("SecondRoute", routeValues);

Which will give me a wonderful address of:

  /User/Index/1/ProgramminTool

Awesome, the routing system did what I wanted. Problem is, I assumed too much coming in, ie when the page is loading from the url. (Say from a clicked link) I kept getting an error like:

RoomId doesn’t exist. Make roomId nullable. Blah blah blah I hate you and you should quit programming and do something more equal to your ability level like spinning in circles.

Though I might have take some artist license on the error, the meaning was simple: it was trying to run that url against the Room controller and it couldn’t find the roomId parameter in the request. Well that doesn’t make sense, after all it’s obvious that it should be looking for the user controller and use the route with the userId paramter. Sadly, it doesn’t work that way and not sure it could. Why? Well look at the url.

/User/Index/1/ProgramminTool

Now if you were too look at that, what would you think? You have four values, values that you can’t really guess the type since you have no real context. After all there could easily be a method that takes in a string userId as opposed to an integer userId. On top of that, it has no idea what the 1 is. There’s no userId=1 to tell it what it is. So what does it do? It goes down the route table and finds the best fit. Now you tell me, which does this url fit better?

  "{controller}/{action}/{roomId}/{name}"

Or

  "{controller}/{action}/{userId}/{name}"

It’s kind of a trick question since the answer is neither. It will just fit it to the first one that it likes, and in this example it’s the roomId one. Problem is, now the user controller method that is looking for the userId fails because there is no userId. Uhg.

Computers are dumb, they can only do what we tell them.

Fact is, in this situation I have to make some rules for the routing system to take in and start digesting the url properly, and there’s a way to do this: IRouteConstraint.

Say that you have certain controllers or actions you don’t want to use the first route. For this example I’ll use user actions from my current project. On the room index view there is a button for adding the room as a favorite. Now the action/method it needs is on the user controller, not the room controller. Therefore I have to post some information to the user controller using a route that looks like this:

  "{controller}/{action}/{userId}/{roomId}"

But it is preceded by one that looks like:

  "{controller}/{action}/{id}/{name}"

See the issue? Now when MVC generates the url, everything is fine. After all it uses the route name and the parameters to pick the correct route. On the way in, not so good. It naturally tries to conform the url to the id/name route. BOOM HEADSHOT! Now the solution.

public class NotGivenAction : IRouteConstraint
{
  //This is the action that we want to prevent from the route accepting
  private String GivenAction { get; set; }

  public NotGivenAction(String givenAction)
  {
    GivenAction = givenAction;
  }

  public Boolean Match(HttpContextBase httpContext, Route route, String parameterName, RouteValueDictionary values, RouteDirection routeDirection)
  {
    String parameterValue = values[parameterName].ToString();
    //Make sure the parameter exists and it doesn't match the bad action
    return values.ContainsKey(parameterName) && parameterValue != GivenAction;
  }
}

The IRouteConstraint interface has a method Match that has to be given life. The short of it is take in the parameter name and find it’s value and then see if the value is the same as the action it’s looking for. For example, if I had an action of AddToFavorites, this would go through and make sure it isn’t that action. If it is, it knows to not use the current route for this action.

routes.MapRoute
(
  GeneralConstants.RouteIdName,
  "{controller}/{action}/{id}/{name}",
  new { controller = "Room", action = "Index", id = "0", name = "None" },
  new { action = new NotGivenAction(UserControllerConstants.AddToUserFavorite) }
);

routes.MapRoute
(
  GeneralConstants.RouteUserIdRoomId,
  "{controller}/{action}/{userId}/{roomId}",
  new { controller = GeneralConstants.ControllerUser, action =   UserControllerConstants.AddToFavorites, userId = "-1", roomId = "-1" }
);

So now it will skip the first and push to the second when the url looks like:

  /User/routes.MapRoute/AddToFavorites/1/2/

Take that you f-ing routes. KING KONG AIN’T GOT S- ON ME!