views:

304

answers:

1

I'm working with some third-party software that creates querystring parameters with hyphens in their names. I was taking a look at this SO question and it seems like their solution is very close to what I need but I'm too ignorant to the underlying MVC stuff to figure out how to adapt this to do what I need. Ideally, I'd like to simply replace hyphens with underscores and that would be a good enough solution. If there's a better one, then I'm interested in hearing it.

An example of a URL I want to handle is this:

http://localhost/app/Person/List?First-Name=Bob&My-Age=3

with this Controller:

public ActionResult List(string First_Name, int My_Age)
{
    {...}
}

To repeat, I cannot change the querystring being generated so I need to support it with my controller somehow. But how?

For reference, below is the custom RouteHandler that is being used to handle underscores in controller names and action names from the SO question I referenced above that we might be able to modify to accomplish what I want:

public class HyphenatedRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler  GetHttpHandler(RequestContext requestContext)
    {
        requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
        requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
        return base.GetHttpHandler(requestContext);
    }
}
+3  A: 

Have you tried [Bind(Prefix="First-name")]? It might work...

One way would be with a custom model binder. Another way would be with an action filter. Use the model binder if you want to do this on a specific type. Use the action filter if you want to do this on a specific action or controller. So for the latter method you could do something like:

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var keys = filterContext.HttpContext.Request.QueryString.AllKeys.Where(k => k.Contains('-'));
        foreach(var k in keys)
        {
            filterContext.ActionParameters.Add(
                new KeyValuePair<string, object>(
                    k.Replace('-', '_'), filterContext.HttpContext.Request.QueryString[k]));
        }
        base.OnActionExecuting(filterContext);
    }
Craig Stuntz
Actually instead of adding to the ActionParameters which would result in an exception as the element already exists you could set the value: `filterContext.ActionParameters[k.Replace('-', '_')] = filterContext.HttpContext.Request.QueryString[k];`
Darin Dimitrov
Interesting idea, @Darin. I'm not sure which way I like more.
Craig Stuntz
It's not a matter of liking, your code will throw an exception as there's already `First_Name` parameter in the `ActionParameters` collection coming from the argument name of the method.
Darin Dimitrov
@Darin, I'll disagree with you there. If you have other code that looks at `ActionParameters` (e.g., another filter), that code might expect the parameter to contain what the user actually sent. The exception, I think, is a good thing; if the user sends `foo_bar` **and** `foo-bar` then the assumptions described in the question are invalid. That's a contract violation and throwing is the correct response to a contract violation.
Craig Stuntz
@Craig, I've tested the code, it does throw an exception - ASP.NET MVC 2.0, .NET 4.0
Darin Dimitrov
Um, yes, @Darin, I agreed with you that it does throw an exception. Again, I think it **should** throw an exception, as the situation which causes it to throw an exception violates the assumptions in the question. The part I disagreed with was "It's not a matter of liking..."
Craig Stuntz
You're both correct. ;-) So back to business. This gets me 85% of the way there but the problem with this solution is that the parameters that I add into the ActionParameters collection end up always being strings now instead of coming into my controller as a strongly-typed object. I realize I didn't emphasize that in my original question, and I'll update it to reflect it, but there must be a better solution than one-off parsing the strings for my objects. Any ideas?
Jaxidian
You'd want to invoke the normal model binding with the different key. It's possible, though I don't know how off the top of my head. Some poking around in the source should turn it up, though. Another option: Have you tried `[Bind(Prefix="First-name")]`? It might work...
Craig Stuntz
Aha! That's it! :-) Can you update your answer (don't delete the existing stuff but add to it)? Once you do that, I'll mark it answered. Many thanks - this is a perfect answer!! :-)
Jaxidian