4

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.

For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.

I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:

routes.MapRoute(
   null,
   "Document/Display/{id}?{args}",
   new { controller = "OtherController", action = "OtherAction" },
   new RouteValueDictionary { { "args", "vendor" } });

Can I write a route that takes the query string into account? If not, do you have any other ideas?


Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.

I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".

flipdoubt
  • 13,897
  • 15
  • 64
  • 96

3 Answers3

8

QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.

As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:

public class QueryStringConstraint : IRouteConstraint 
{ 
    private readonly Regex _regex; 

    public QueryStringConstraint(string regex) 
    { 
        _regex = new Regex(regex, RegexOptions.IgnoreCase); 
    } 

    public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 
    { 
        // check whether the paramname is in the QS collection
        if(httpContext.Request.QueryString.AllKeys.Contains(parameterName)) 
        { 
            // validate on the given regex
            return _regex.Match(httpContext.Request.QueryString[parameterName]).Success; 
        } 
        // or return false
        return false; 
    } 
}

Now you can use this in your routes:

routes.MapRoute("object-contact", 
    "{aanbod}", 
    /* ... */, 
    new { pagina = new QueryStringConstraint("some|constraint") });
Jan Jongboom
  • 26,598
  • 9
  • 83
  • 120
  • Thanks, Jan. I am looking closer into this and might change the answer over to you if I can make it work for what I am trying to do. As a followup, would you change any of this for MVC 3? – flipdoubt Feb 21 '11 at 13:26
  • No, I'm pretty sure this will work as well in MVC 3. Those are pretty basic concepts in the Microsoft implementation. We use it because ASP.NET doesn't like 10.000.000 different URLs that don't really map to a real file. – Jan Jongboom Feb 21 '11 at 13:30
2

You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:

public ActionResult Foo(string id, string script, string vendorname)
{
    // the id parameter will be bound from the default route token
    // script and vendorname parameters will be bound from the request string
    ...    
}

UPDATE:

If you don't know the name of the query string parameters that will be passed you could loop through them:

foreach (string key in Request.QueryString.Keys)
{
    string value = Request.QueryString[key];
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Thanks, Darin, but the sample code I posted wants to re-route the request to a different controller. Also, the component makes multiple subsequent requests, each one with different parameters, where some of the parameters are prefixed with the vendor name. – flipdoubt Nov 24 '10 at 17:19
  • @flipdoubt, if you don't know the names of the query string parameters that will be passed you could loop through them. And your route might look like this: `routes.MapRoute(null, "Document/Display/{id}", new { controller = "OtherController", action = "OtherAction" } );`. – Darin Dimitrov Nov 24 '10 at 17:30
  • So are you saying "No, query string parameters can not be used in route constraints"? – flipdoubt Nov 26 '10 at 15:07
-1

This post is old, but couldn't you write a route before your default route

this would only catch routes with "vendor" in the args

routes.MapRoute(
   null,
   "Document/Display/{id}?{args}",
   new { controller = "VendorController", action = "OtherAction" },
   new {args=@".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args

);

And This would catch the rest

 routes.MapRoute(
       null,
       "Document/Display/{id}?{args}",
       new { controller = "DisplayController", action = "OtherAction" }
    );

Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.

I tried this out and it worked for me.