views:

108

answers:

2

My ASP.NET MVC application has a scenario where user input can directly influence the target of a call to RedirectToAction() (by way of a string) and there is a chance that the user could create a run-time error if improper input leads them to request an action that does not exist. I'd like to prevent this problem outright, but I'd like to do so in the least taxing way possible as it must be done on a large number of requests. That being said, reflection would be a viable solution to use to confirm that /Controller/ActionName actually exists, but reflection is a pretty heavy operation.

What would be the best way to confirm that a given Url in an ASP.NET MVC application is in fact wired to a controller action?

+2  A: 

One way to do it would be to have a list of allowable values for the user inputted data. For example, if the user inputted her favourite colour:

// userColour = the user set colour
var allowableColours = new [] { "Red", "Blue", "Green" };
if (!allowableColours.Contains(userColour))
{
    // Set to a default colour.
    userColour = "Red";
}

return RedirectToAction(userColour, "Colour");

Although not as dynamic as looking at the routing table, it would be fast and you could be confident they the user wasn't inputting some malicious value that was screwing with your routing.

cdmckay
Unfortunately, this option isn't really viable for my scenario because of how the routing decision is reached. Although, that does give me an idea of another way of handling the potential issue. If I preemptively prevent the possibility of the routing error I could at least partially control the issue at hand.
Nathan Taylor
A: 

The route I ended up taking here was reflection and a Dictionary containing all of the valid actions in the relevant Controller which is stored in Application[]. A valid Action is determined by checking the method's ReturnType and verifying that it is (or derives from) ActionResult and that it is not Private. I could do some more checks, but these are sufficient for now.

public static bool MethodIsAction(MethodInfo method)
{
    if (method == null)
        throw new ArgumentNullException("Invalid Parameter: method cannot be null.");

    if (method.ReturnType != typeof(ActionResult) && method.ReturnType.BaseType != typeof(ActionResult))
        return false;

    if (method.IsPrivate)
        return false;

    return true;
}

The dictionary of actions is built with the following method inside Application_Start:

public static Dictionary<string, MethodInfo> GetActionDictionary(Type controller)
{
    Dictionary<string, MethodInfo> dict = null;

    var methods = controller.GetMethods().Where(MethodIsAction);
    if (methods.Any())
    {
        dict = new Dictionary<string, MethodInfo>(StringComparer.OrdinalIgnoreCase);
        foreach (var action in methods)
            dict.Add(action.Name, action);
    }
    return dict;
}

When a user requests a qualifying action I simply point the action name at the Dictionary and if a MethodInfo exists for that action name I invoke it. While it still requires reflection, it's at least optimized so that it only ever happens once while the application is running.

Nathan Taylor