Question

I have an Asp.net MVC app that currently works well using the default model binder and urls with complex parameters like this:

example.com/Controller/Action?a=hello&b=world&c=1&d=2&e=3 (notice the question mark)

The different urls automatically map to Action Method parameters using the built in model binder. I would like to continue using the standard model binder but I need to get rid of the query string. We want to put these urls behind a CDN that does not support resources that vary by query strings (Amazon Cloud front) so we need to remove the question mark from our urls and do something silly like this

example.com/Controller/Action/a=hello&b=world&c=1&d=2&e=3 (no question mark)

These urls are only used via AJAX, so I'm not interested in making them user or SEO friendly. I want to just drop the question mark and keep all my code exactly the same. The hitch is, I'm unsure about how to keep using the MVC model binder and abandoning it would be a lot of work.

I don't want to use a complex route to map my objects like this question did and, instead, I am planning to use a single simple route like the one below

   routes.MapRoute(
        "NoQueryString",                    // Route name
        "NoQueryString/{action}/{query}", // 'query' = querystring without the ?
        new {
            controller = "NoQueryString",
            action = "Index",
            query = "" }  // want to parse with model binder - By NOT ROUTE
    );

Option 1 (preferred): OnActionExecuting I plan to use the catchall "query" value in the route above to inject the old query string into the default model binder before the Controller Actions execute using the OnActionExecuting method in my controller. However, I'm a bit unsure if I can just add back the question mark. Can I do this? How would you recommend modifying the url?

Option 2: Custom Model Binder I also could make some sort of Custom Model Binder that just tells the default model binder to treat the "query" value like a query string. Would you prefer this method? Can you point me to a relevant example?

I am a bit worried that this is an edge case and would love some input before I start trying to implement Option 1 or Option 2 and stumble onto unforseen bugs.

Was it helpful?

Solution

You could use a custom value provider with a catchall route:

routes.MapRoute(
    "NoQueryString",
    "NoQueryString/{controller}/{action}/{*catch-em-all}",
    new { controller = "Home", action = "Index" }
);

and the value provider:

public class MyCustomProvider : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        var value = controllerContext.RouteData.Values["catch-em-all"] as string;
        var backingStore = new Dictionary<string, object>();
        if (!string.IsNullOrEmpty(value))
        {
            var nvc = HttpUtility.ParseQueryString(value);
            foreach (string key in nvc)
            {
                backingStore.Add(key, nvc[key]);
            }
        }
        return new DictionaryValueProvider<object>(
            backingStore, 
            CultureInfo.CurrentCulture
        );
    }
}

which you register in Application_Start:

ValueProviderFactories.Factories.Add(new MyCustomProvider());

and now all that's left is a model:

public class MyViewModel
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public string D { get; set; }
    public string E { get; set; }
}

and a controller:

public class HomeController : Controller
{
    [ValidateInput(false)]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

and then navigate to: NoQueryString/Home/Index/a=hello&b=world&c=1&d=2&e=3. The Index is hit and the model is bound.

Remark: Notice the ValidateInput(false) on the controller action. That's probably gonna be needed because ASP.NET won't allow you to use special characters such as & as part of a URI. You might also need to tweak your web.config a little:

<httpRuntime requestValidationMode="2.0" requestPathInvalidCharacters=""/>

For more information about those tweaks make sure you have read Scott Hansleman's blog post.

OTHER TIPS

Whats the problem with "complex" routing definitions? The routes the question you linked to are very very simple. Defaults can go a long way. Better to use the default capabilities of MVC to turn:

example.com/Controller/Action/a=hello&b=world&c=1&d=2&e=3

into

example.com/Controller/Action/hello/world/1/2/3

than start re-wiring core bits like RouteValue handling ( option 1 ) or implementing a custom model binder.

I know this doesn't answer your question but it seems like you made a choice about not using MVC's routing capabilities without understanding the technical ramifications. You know what they say "good friends stop each other from doing stupid things".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top