views:

43

answers:

1

In one of the applications I am building I have constructed a very flexible attribute-based system for describing products in my database wherein each product can have an indeterminate number of attributes assigned to it with each attribute having a single "type". So, for example, one attribute type might be "Category" and the value assigned to a single attribute would be something like "Trucks". There are no restrictions on the number of attributes assigned to a given product and because the attributes and attribute types are stored in the database alongside the products, my application does not know ahead of time what any of them will be.

One of the features of the options for a given attribute type is whether or not it is "searchable". In the event of an attribute being searchable I can then use its value paired with its type name to search/filter my products. So, for example, a user might want to return all products having the attribute type "Category" equal "Trucks" and attribute type "Color" equal "Red". Nothing too unique there.

The trouble I am dealing with is that because my system does not know ahead of time what my attribute type names are, I cannot easily create an action method accepting parameters in a readable format like string category or string color. As a solution I have made use of the DefaultModelBinder's support for binding to a dictionary. With this approach I need only format my field names in the correct structure, and then my action method can accept an IDictionary<string,string> parameters. This all works fairly well, but it makes for some really nasty URLs when the user is performing a link-based filter by a single parameter, i.e. "See more Products in Category Trucks". With the DefaultModelBinder binding to a dictionary requires that your field naming pattern resembles the following:

<input type="hidden" name="parameters[0].Key" value="Category" />
    <select name="parameters[0].Value">
    <option value="Trucks">Trucks</option>
    <option value="Compacts">Compacts</option>
    <option value="SUVs">SUVs</option>
</select>
<input type="hidden" name="parameters[1].Key" value="Manufacturer" />
<select name="parameters[1].Value">
    <option value="Ford">Ford</option>
    <option value="Toyota">Toyota</option>
    <option value="Honda">Honda</option>
</select>

Not only is this incredibly verbose, but it also somewhat frustrating due to the fact that each Key/Value must contain an ordinal index in the field name. Although this is acceptable for a POST form, it is not particularly ideal in a GET URL because we end up with URLS resembling ?parameters[0].Key=Category&parameters[0].Value=Trucks&parameters[1].Key=Manufacturer&parameters[1].Value=Ford. Not only is this ugly, it is very limited in its implementation because any modification to the URL could potential destroy the entire result set (if the user wanted to just search by the second parameter via modifying the URL they would have to remove the first parameter and renumber the whole collection appropriately).

What I am looking for is a better way to handle this kind of situation. Ideally I'd like to simply have a querystring value ?Category=Red and filter accordingly, but then my action method doesn't know if there actually is a "Category" parameter to bind to. Is there any in between that would allow me to have cleaner querystring parameters that wouldn't make for such awful URL structures?

I was thinking about possibly building my own custom ModelBinder, but I'd like to avoid that if there's another way.

+1  A: 

I much prefer your "clean" URIs: ?Category=Red. So let's start there and see how it could work.

You can load up all the categories at runtime, right? Off the top of my head:

IEnumerable<string> allCategories = Categories.GetAll();
var usedCategories = Request.QueryString.AllKeys.Intersect(allCategories);
var search = from c in usedCategories
             select new 
             {
                 Key = c,
                 Value = Request.QueryString[c]
             };

You could use this as-is, or make a custom model binder. In either case, it's not a lot of code.

Craig Stuntz
Nice. I like this solution a lot. I'll report back when I see how it works out.
Nathan Taylor
Bravo sir. Truly.
Nathan Taylor