ASP.NET MVC - Verify the Existence of a Route
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?
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.
A quick and more crude option is to just hit the url, the following code may help you quickly test something,
Note: you're actually hitting your site and be aware of what that means in your application.
This was helpful for us diagnosing some environment issues in some integration tests.
var urlToExec = "http://yoursite.com/" + controllerAndAction;
var wr = (HttpWebRequest) WebRequest.Create(urlToExec);
try
{
var resp = (HttpWebResponse)wr.GetResponse();
if (resp.StatusCode != HttpStatusCode.OK || resp.StatusCode == HttpStatusCode.NotFound)
//it was found
}
catch (Exception ex)
{
//404 or other http error
//404 and the like result in GetResponse() throwing an exception
//this was verified by having actions return via:
//'return new HttpNotFoundResult("This doesn't exist");'
}
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.
精彩评论