views:

6001

answers:

4

I'm working on a basic Issue Management System in order to learn ASP.NET MVC. I've gotten it up and running to a fairly decent level but I've run into a problem.

I have a controller named Issue with a view called Open. /Issue/Open lists all of the open issues currently logged on the system. I've defined a route like so:

    routes.MapRoute( 
        "OpenSort",                                                         // Route name
        "Issue/Open/{sort}",                                                // URL with parameters
        new { controller = "Issue", action = "Open", sort = "TimeLogged" }  // Parameter defaults
    );

This is working fine so far, using the following code in IssueController.cs:

public ActionResult Open(string sort)
{            
    var Issues = from i in db.Issues where i.Status == "Open" orderby i.TimeLogged ascending select i;

    switch (sort)
    {
        case "ID":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.ID ascending select i;
            break;

        case "TimeLogged":
            goto default;

        case "Technician":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.Technician ascending select i;
            break;

        case "Customer":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.Customer ascending select i;
            break;

        case "Category":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.Category ascending select i;
            break;

        case "Priority":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.Priority ascending select i;
            break;

        case "Status":
            Issues = from i in db.Issues where i.Status == "Open" orderby i.Status ascending select i;
            break;

        default:
            break;
    }            

    ViewData["Title"] = "Open Issues";
    ViewData["SortID"] = sort.ToString();

    return View(Issues.ToList());
}

This is working fine (although, I wonder if there is a better way to handle my definition of the query than a switch?) but now I want to be able to do two things on the Open Issues view:

  1. Sort by any of the headings - OK
  2. Filter on certain headings (Technician, Customer, Category, Priority, Status) - ??

I can't figure out how to pass two parameters to the Controller so I can organise my queries. I've also just realised that unless I figure out how to generate my queries on the fly I am going to need (number of sort options) * (number of filter options) in my switch.

Argh, can anyone point me in the right direction? Cheers!

+11  A: 
  1. Remove sort from the route. Just use a route without a parameter.
  2. Add query string parameters to the query for the sort, filter, etc. So your query will look like:

http://example.com/Issue/Open?sort=ID&filter=foo

public ActionResult Open(string sort, string filter)

The MVC framework will fill in the arguments from the query string parameters. Make sure and use nullable types (like string) for any of these query string parameter arguments which might not be filled in.

I actually think this is a "more correct" way to write the URL. The URL itself identifies the resource (open issues); the query string parameters customize how to display the resource.

As far as the number of queries go, remember that you do not have to build the entire query at once. You can use the .OrderBy extension method to re-order an existing IQueryable<T>, and similarly with .Where.

var Issues = from i in db.Issues where i.Status == "Open" select i;

switch (sort)
{
    case "ID":
        Issues = Issues.OrderBy(i => i.ID);
        break;

    // [...]

    default:
        Issues = Issues.OrderBy(i => i.TimeLogged);
}
Craig Stuntz
+1 - Routes shouldn't include UI candy like sorting and filtering because it clutters up the route and it's hard to express things like sorting and filtering without making the purpose of the route ambiguous.
Daniel Schaffer
Also, if the sort string is always going to match the property name, I think you should be able to use reflection, grab the property, and build the expression at runtime... but I have no clue how to do that!
eyston
Huey, the easiest way is to use Microsoft Dynamic LINQ (Google it).
Craig Stuntz
tip: Avoid using 'id' as a parameter name
Khaled Musaied
Thanks Craig, your answer as help me with my project.
Gabriel Mongeon
+4  A: 

If you expect arbitary number of parameters, you could do something like this.


public ActionResult Open(){            
   string[] keys = Request.QueryString.AllKeys;
   Dictionary queryParams = new Dictionary();
   foreach (string key in keys)
   {
     queryParams[key] = Request.QueryString[key];
   }
   string sort = queryParams["sort"];
   ...


kimsk
+2  A: 

This should be a comment to kimsks answer, but for some reason commenting requires me to be vetted, so I have to post it in the wrong place.

A better way to handle an arbitrary number of query string parameters is to use an ActionFilter like so:

public class QueryStringFilterAttribute : ActionFilterAttribute
{
 public string ParameterName { get; private set; }

 public QueryStringFilterAttribute(string parameterName)
 {
  if(string.IsNullOrEmpty(parameterName))
   throw new ArgumentException("ParameterName is required.");
  ParameterName = parameterName;
 }

 public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
  var qs = new FormCollection(filterContext.HttpContext.Request.QueryString);

  filterContext.ActionParameters[ParameterName] = qs;

  base.OnActionExecuting(filterContext);
 }
}

Now you can add the an attribute to your action like so [QueryStringFilter("attributes")] and it will pass in the query string values as a FormCollection. This way your action is more easily tested, as it no longer depends on the Request singleton.

ungood
A: 

@ungood: nice solution.

With your code the querystring values can be easily extracted, like this:

    [QueryStringFilter("querystring")]
    public ActionResult Open(FormCollection querystring)
    {
        String a = querystring["test"];
        ...
Gerard Alberts