Get ready for a roller coaster ride around the insanity that is me. You might actually find it amusing but most likely you’ll just leave sick or underwhelmed. Don’t feel bad if you do, you wouldn’t be the first and, thanks to somehow being impervious to Natural Selection, you won’t be the last. Be proud.
Here’s what this post is about: Say you have a url like eh:
/User/View/1
Where 1 represents a UserId to a user that exists in some matter of context. (Does that makes sense?) Well there are a couple of things that could go wrong here. For one, if you don’t want to have a nullable id in your signature:
public ActionResult View(Int32? userId)
This could cause ouch:
/User/View/
But this problem is deeper. Much deeper. In fact so deep it deeper than even Piper Perabo has ever deeped before. Yrraaaahh!
What if the id doesn’t even refer to anything? Say the id is 101 but there’s no user with the id of 101? Beyond that, what if the id could be Id in some routes but UserId in others? WHAT WOULD YOU DO???
The idea here is to have something neato like this:
[UserExistsById]
public ActionResult View(Int32 userId)
And if that id is junk, then you redirect to an error page. Well if this sounds interesting, I’d be surprised, but read on in the event that it does.
Now this isn’t accomplished in the most simple manor, but for good reason: The more time in means the less time repeating. First method we need is something to simply check the route data to see if something exists:
public static Boolean RouteDataValueExists(ActionExecutingContext filterContext, String idToCheck)
{
return filterContext.RouteData.Values.ContainsKey(idToCheck);
}
Real easy. It either has been digested by MVC and regurgitated into some kind of route value or it hasn’t. Basically this is the first check. After all, if it doesn’t exist why bother going further?
Next is the method that will be calling this one. Mainly one that uses the RouteDataValueExists and if returns true, then it actually checks the value against where ever the user is persisted.
public static Boolean ValueFoundAndItemExists
(
ActionExecutingContext filterContext,
Enum idToUse,
Func<ActionExecutingContext, Enum, Func<Int32, Boolean>, Boolean> check,
Func<Int32, Boolean> exists
)
{
Boolean checksOut = false;
String convertedId = idToUse.ToString();
if (RouteDataValueExists(filterContext, convertedId))
{
checksOut = check(filterContext, idToUse, exists);
}
return checksOut;
}
Ok so kind of a lot at first and it’s hard to decide how to present this system, so just go with it.
Enum idToUse,
This is a design choice. In reality this will be turned into a string anyhow, but the idea is it will be what to check the route values for. So if you are checking for UserId, you will pass in UserRequestTypes.UserId. Again this was a choice on my part as I hate passing text around.
Func<ActionExecutingContext, Enum, Func<Int32, Boolean>, Boolean> check,
This is the method that will be used by ValueFoundAndItemExists to delegate out the actual checking if the id is an integer and is a real user.
Func<Int32, Boolean> exists
This will be the method that you will use to delegate the whole checking if it exists in the database. Something like:
class User
{
public Exists(Int32 id)
{
return EntityContext.Context.User.Any(user => user.Id == id);
}
}
Still with me? No? Greeeeat. Next up is the base class that contains the
Func<ActionExecutingContext, Enum, Func<Int32, Boolean>, Boolean> check,
parameter from above.
public abstract class BaseExistsAttribute : ActionFilterAttribute
{
protected Boolean Exists
(
ActionExecutingContext filterContext,
Enum idToUse,
Func<Int32, Boolean> exists
)
{
Int32? id = Convert.ToString(filterContext.RouteData.Values[idToUse.ToString()]).ConvertTo<Int32>();
return id.HasValue && exists(id.Value);
}
}
Ok so something might look familiar like well basically all the parameters. The reason for this is that there is a little bit of delegate passing going on with this whole system. One method passes on methods to other method in some kind of crazy method hoe down without the flanel shirts.
You will notice the use of the enum in this part:
idToUse.ToString()
Again, you can easily just pass in a string instead of an enum, I just did this to stay away from strings.
Only thing that might be of interest is the ToConvert method that you can find here. You don’t have to use it, you can simply just do a try parse on the
filterContext.RouteData.Values[idToUse.ToString()])
To get an integer. That’s up to you. I could have just put that in the code for you to see but then I couldn’t randomly plug another post of mine.
Finally you have the actual attribute class:
[AttributeUsage(AttributeTargets.Method)]
public class QuoteExistsByQuoteId : BaseExistsAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if
(
!MvcMethods.ValueFoundAndItemExists
(
filterContext,
UserRequestTypes.UserId,
Exists,
UserEntity.Exists
)
)
{
filterContext.Result = SiteMethods.ErrorOut(ErrorNames.General_PageError);
}
}
}
And now it all comes together.
UserRequestTypes.UserId
This is the enum I’ve been talking about this entire post! Now that I think of it, not really exciting.
Exists
That’s the method on the base class that checks for everything.
UserEntity.Exists
And that’s the method I need to check the persistence if the user is real. Remember? Takes in an integer and returns a boolean? Yes? Yes? No?
What’s ErrorOut? Again this is just a method I made to create an ActionResult that is an redirect to an error page. Not hugely important. It’s just what handles the situation when the value is bogus…. dude.
[UserExistsById]
public ActionResult View(Int32 userId)
And there you have it. Hopefully it was useful in some way but I’m not of touch with reality.