6

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);
    }
}
Community
  • 1
  • 1
Jaxidian
  • 13,081
  • 8
  • 83
  • 125

2 Answers2

7

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
  • 125,891
  • 12
  • 252
  • 273
  • 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 May 06 '10 at 15:56
  • Interesting idea, @Darin. I'm not sure which way I like more. – Craig Stuntz May 06 '10 at 16:21
  • 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 May 06 '10 at 16:33
  • @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 May 06 '10 at 16:44
  • @Craig, I've tested the code, it does throw an exception - ASP.NET MVC 2.0, .NET 4.0 – Darin Dimitrov May 06 '10 at 16:46
  • 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 May 06 '10 at 17:06
  • 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 May 06 '10 at 19:33
  • 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 May 06 '10 at 20:37
  • 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 May 06 '10 at 20:47
1

I had the same problem. In the end rather than doing something too complex I just get the query string parameters using

string First_Name = Request["First-Name"];

You may want to check for NUlls incase the parameter is not there, but this sorted it out for me. You can also include an optional parameter for the ActionResult for test purposes etc..

NER1808
  • 1,829
  • 2
  • 33
  • 45