Dynamically modify RouteValueDictionary

2019-01-19 06:29发布

问题:

As a followup of this question, I'm trying to implement a custom route constraint in order to dynamically modify the RouteValueDictionary for a specific route.

I'm 95% of the way there: I can match any route based on the supplied parameter pattern matching the action specified in the url. The very last step of this process is to overwrite the RouteValueDictionary to rename the keys as the appropriate parameters for the controller action.

Here's the relevant snippet of my code (the entire class is almost 300 lines so I don't want to add all of it, but I can if needed):

public class CustomRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string paramName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection.Equals(RouteDirection.UrlGeneration)) {
            return false;
        }

        //this will grab all of the "actual" parameters out of the RVD, excluding action and controller 
        Dictionary<string, object> unMappedList = values.Where(x => x.Key.Contains("param")).OrderBy(xi => xi.Key).ToDictionary(
            kvp => kvp.Key, kvp => kvp.Value);

        string controller = values["controller"] as string;
        string action = values["action"] as string;

        //this method tries to find the controller using reflection
        Type cont = TryFindController(controller);

        if (cont != null) {
            MethodInfo actionMethod = cont.GetMethod(action);

            if (actionMethod != null) {
                ParameterInfo[] methodParameters = actionMethod.GetParameters();

                //this method validates that the parameters of the action that was found match the expected parameters passed in the custom constraint; it also performs type conversion
                if (validateParameters(methodParameters, unMappedList)) {
                    for (int i = 0; i < methodParameters.Length; i++) {
                        values.First(x => x.Key == unMappedList.ElementAt(i).Key).Key = methodParameters.ElementAt(i).Name; 
                        //above doesn't work, but even if it did it wouldn't do the right thing

                        return true;
                    }
                }
            }
        }

        return false;
    }
}

And here's how I use the custom constraint:

routes.MapRoute(
    name: "Default2",
    url: "{controller}/{action}/{param1}-{param2}",
    defaults: new { controller = "Admin", action = "Index" },
    constraints: new { lang = new CustomRouteConstraint(new RoutePatternCollection( new List<ParamType> { ParamType.INT, ParamType.INT })) }
);

(What I'm passing into the constructor is basically saying "The parameter pattern to look for is two integers". So when the Match method is called, it will return true if the action specified in the url has two two integer parameters, regardless of the actual parameter names.)

So, is there a way I can overwrite the RouteValueDictionary for this request? Or is there some other way I can do this that I'm missing?

回答1:

If I do understand correctly what is the question:

So, is there a way I can overwrite the RouteValueDictionary for this request? Or is there some other way I can do this that I'm missing?

I've taken your solution and changed these lines to replace parameter names:

for (int i = 0; i < methodParameters.Length; i++)
{
    // I. instead of this

    //values.First(x => x.Key == unMappedList.ElementAt(i).Key)
    //    .Key = methodParameters.ElementAt(i).Name; 
    //above doesn't work, but even if it did it wouldn't do the right thing

    // II. do this (not so elegant but working;)
    var key = unMappedList.ElementAt(i).Key;
    var value = values[key];
    values.Remove(key);
    values.Add(methodParameters.ElementAt(i).Name, value);

    // III. THIS is essential! if statement must be moved after the for loop
    //return true;
}
return true; // here we can return true

NOTE: I like your approach. Sometimes it is simply much more important to extend/adjust routing then go to code and "fix incorrect parameter names". And of course, they are most likely NOT incorrect.

And here is the power of Separation of concern.

  • Url addresses are one place.
  • Controller, action and parameter names other one.
  • And Routing with powerful customization...

...as the only correct place where to handle that. This is separation of concern. Not tweaking the action names to serve Url...



回答2:

You can easily restrict the parameters to positive integers with a regular expression:

private const string IdRouteConstraint = @"^\d+$";

routes.MapRoute(
   name: "Default2",
   url: "{controller}/{action}/{param1}-{param2}",
   defaults: new { controller = "Admin", action = "Index" },
   constraints: new { param1 = IdRouteConstraint, param2 = IdRouteConstraint});

As I said in my answer to your earlier question, you shouldn't worry about the parameter names in the controller actions. Giving those overly meaningful names will tie you in knots.