31

Here is my scenario. For the example lets say that I need to return a list of cars based on a search criteria. I would like to have a single View to display the results since the output will be the same, but I need several ways of getting there. For instance, I may have a Form with a textbox to search by year. I may have another separate page that contains a hyperlink for all red, Toyota cars. How do I handle these multiple scenarios in the same View and Controller. My dilemma is that the search could contain several options… year, make, model, etc but I don’t know where to put them.

What is the best approach for this? Should I define the parameters in the routing or go with query strings, etc?

3 Answers 3

77

Option 1

Of course you always can choose the way of /car/search/?vendor=Toyota&color=Red&model=Corola and I think it will be good for you.

routes.MapRoute(
    "CarSearch",
    "car/search",
    new { controller = "car", action = "search" }
);

You can get params from Request.Params in action in this case.

Option 2

Or you can define params in the routing table, but AFAIK it will be required to make a set of rules for all possible combinations, because an order of the params matter, for example:

        routes.MapRoute(
            "CarSearch1",
            "car/search/vendor/{vendor}/color/{color}/model/{model}",
            new {controller = "car", action = "search"}
        );

        routes.MapRoute(
            "CarSearch2",
            "car/search/color/{color}/vendor/{vendor}/model/{model}",
            new {controller = "car", action = "search"}
        );

        routes.MapRoute(
            "CarSearch3",
            "car/search/model/{model}/color/{color}/vendor/{vendor}",
            new {controller = "car", action = "search"}
        );

... an so on. It's true if you are going with the standard MvcRouteHandler.

But it was an easy ways :)

Option 3

The hard, but, I think, most elegant way, is to make your own IRouteHandler implementation - it will give you much more flexibility in params order. But again, its a hard way, dont go with it if you have a simple app. So, just for example of how to make it this way (very simple example):

Add new route to the list of routes:

routes.Add
    (
        new Route
            (
                "car/search/{*data}",
                new RouteValueDictionary(new {controller = "car", action = "search", data = ""}),
                new MyRouteHandler()
            )
    );

Add classes that will tweak the standard request processing chain:

class MyRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MyHttpHandler(requestContext);
    }
}

class MyHttpHandler : MvcHandler
{
    public MyHttpHandler(RequestContext requestContext) : base(requestContext)
    {
    }

    protected override void ProcessRequest(HttpContextBase httpContext)
    {
        IController controller = new CarController();
        (controller as Controller).ActionInvoker = new MyActionInvoker();
        controller.Execute(RequestContext);
    }
}

class MyActionInvoker : ControllerActionInvoker
{
    protected override ActionResult InvokeActionMethod(MethodInfo methodInfo, IDictionary<string, object> parameters)
    {
        // if form of model/{model}/color/{color}/vendor/{vendor}
        var data = ControllerContext.RouteData.GetRequiredString("data");
        var tokens = data.Split('/');

        var searchParams = new Dictionary<string, string>();
        for (var i = 0; i < tokens.Length; i++)
        {
            searchParams.Add(tokens[i], tokens[++i]);
        }

        parameters["searchParams"] = searchParams;

        return base.InvokeActionMethod(methodInfo, parameters);
    }
}

In controller:

public ActionResult Search(IDictionary<string, string> searchParams)
{
    ViewData.Add
        (
            // output 'model = Corola, color = red, vendor = Toyota'
            "SearchParams",
            string.Join(", ", searchParams.Select(pair => pair.Key + " = " + pair.Value).ToArray())
        );
    return View();
}

And it will work with any search parameters order:

/car/search/vendor/Toyota/color/red/model/Corola
/car/search/color/red/model/Corola/vendor/Toyota
/car/search/model/Corola/color/red/vendor/Toyota

But also dont forget to make a link generation logic, because Html.ActionLink and Html.RenderLink will not give you url in pretty form of /car/search/model/Corola/color/red/vendor/Toyota, so you'll need to make a custom link generator.

So, if you need a really flexible routing - you'd better go with this hard way :)

Sign up to request clarification or add additional context in comments.

4 Comments

I think that my best course of action at the moment will be Option 1. Option 2 was the way I was initially headed, but it seems inefficient to have to put all possible routing combinations. Option 3 looks good for later if I want to refactor, but being an MVC NOOB I'll stick with the easy route.
I dont think i have enough pts to vote up yet, but I'll get there ;)
@maxnk Are there any dummy projects of this you know of?
@CR41G14 I do not aware of such projects, but you can use this code as a kickstart to make your own custom solution. Just note that it was written in time of MVC 1.0, so some small tweaks may need to be done.
0

Each method (action) on the controller would take different parameters, but create the same collection of search results. Then, each would

return View("SearchResult", searchResultCollection);

They all use the same view, SearchResult.aspx.

Comments

0

Something along these lines should do what you're after. Notice how there's two different action methods, but both of them return a call to DisplayResults() - so they end up using the same view, with different ViewData.

public class SearchController : Controller {

    public ActionResult ByColor(Color[] colors) {
         List<Car> results = carRepository.FindByColor(colors);
         return(DisplayResults(result));
    }

    public ActionResult ByMake(string make) {
         List<Car> results = carRepository.FindByMake(make);
         return(DisplayResults(results));
    }

    private ActionResult DisplayResults(IList<Car> results) {

        // Here we explicitly return the view /Views/Search/Results.aspx
        // by specifying the view name in the call to View();
        return(View("Results", results));
    }
}

1 Comment

Thanks. Your example is how my thinking started, but I think my lack of understanding for MVC routing caused me to get stumped. I've only been looking at it for a few days. How would you pass an Array of colors to the ActionResult. My understanding is that these params come from URL

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.