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¶meters[0].Value=Trucks¶meters[1].Key=Manufacturer¶meters[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.
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.
Related
I have a very large model for user profiles. There are about 15 different new user categories (e.g., young traveler, experienced traveler, captain, helmsman, ...). Depending on the user category, some data fields of the model are not applicable and remain null in the database.
I'm trying to populate the model through one big sign-up form. JavaScript determines the user category based on the user's previous answers and shows/hides applicable/non-applicable input fields in the form.
The following problem arose:
Depending on the user category, I need to show some input fields in a slightly different order in the form. I have solved this by creating multiple input fields for the same data field in the view (e.g., #Html.TextBoxFor(x => x.Age)) and then using JavaScript to show only one depending on the user category.
My plan was to submit only the visible input fields so that the server is not confused about which input fields to use to create the model.
Unfortunately, Visual Studio doesn't quite play along: First, there's there problem of having multiple input field with the same id; I could maybe live with that. But then, validation attributes in the HTML-source are generated only for the first occurrence of each data field in the view (e.g., #Html.TextBoxFor(x => x.Age)), not for any later occurrences.
E.g., the first occurrence generates
<input data-val="true" data-val-number="The field Age must be a number."
data-val-range="The field Age must be between 10 and 99." data-val-range-max="99"
data-val-range-min="10" id="Age" name="Age" type="text" value="">
The second occurrence generates merely
<input id="Age" name="Age" type="text" value="">
How can I solve this? My thinking:
Create all input fields hidden once at the beginning of the view and use Javascript to generate at the form dynamically. Downside: A lot of work, all page structure is now in JavaScript instead of view.
Use partial views for different user categories. Downside: When the form page is opened (in the controller), I retrieve several lists from the database and put them in the model in order to populate the form DropDown input fields and RadioButton input lists in the view. Is there any way to reuse this information in the partial views so that there are not many repetitive database queries?
Any thoughts on the situation are very much appreciated. I'm somewhat new to ASP.NET MVC.
Your view model should include a property (pehaps an enum) that defines you user category,
then in your view just render the html applicable to that category
/// Some common html
#if (model.UserCategory == "captain")
{
// controls specific to captain
}
else if (model.UserCategory = "helmsman")
{
// controls specific to helmsman
}
If you need to include this html on the same page you are determining the 'previous answers', put this in a partial view and all it using ajax (passing the user category).
Another option would be to create only one control for each property and then in the javascript that hides the control, also disable the control - disabled controls don't post back so the property will remain null
$('#MyProperty').prop('disabled', true);
Honestly, I would drop the idea of building such a beast in server technology in favor of using plain JS, KnockoutJS or AngularJS which seem to be built exact for this needs.
However, answering your questions:
You have to remember, that browser submits to the server only those fileds which are enabled. If you don wan't to "confuse" server just disable unneeded fields before submit.
Model binder of asp.net mvc finds values by name attribute not by id. You can have form with couple fields with different id attributes and one name for all of them. It will be just a bit more complex to declare them in the Razor View, but it should work.
I have a small problem.
I want to have dropdown list with some objects. After clicking on one I want to add it to list with textfield for naming it. I don't want to limit quantity of this fields. I want to receive in controller ID (stored in dropdown list) and name (given by user) for each selected item. How can I do it?
I was thinking about storing it in some fields as a text, and parsing in cotroller but I think it's not elegant.
EDIT.
Ok, Thansk for your help, but it's not working for me correctly.
I generate html like this:
<input type="hidden" value="96" name="Inputs[0].Key">
<input type="text" name="Inputs[0].Value">
In my controller I'm receiving this dictionary. The problem is that quantity of elements is correct, but all values are null. What is wrong here?
The best way to go about this is by using array-style model binding.
So, for each element you wish to name you create a hidden field to store the drop down value plus a text field to store the user-given name. You name them as follows:
<input type="hidden" name="element[0].Key" /><input type="text" name="name[0].Value" />
increasing the index value each time. This is easily achieved with a bit of JavaScript. You then create an action method which takes a KeyValuePair<string, string>[] as a parameter. You will then be able to parse through your values no problem with a loop or LINQ expression.
Use IEnumerable<KeyPairValue<string,string>> MySelectedItem = new List<KeyPairValue<string,string>>(); on model, and when adding it to the list, name it like an array:
MySelectedItem[1].Key, MySelectedItem[1].Value, MySelectedItem[2].Key...
(I haven't tested this, but it should work)
Edit: check out this blog post with better explanation on how to do it: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Having just spent two amazingly frustrating hours formatting some hidden fields to bind properly to action method parameters -- the second time I've had this experience in the last week -- I'm now very curious as to why the MVC architects chose the particular binding convention that they did for lists (and dictionaries) of objects.
Here's my painfully gathered understanding of the format expected by the default binding engine. In my example I want to bind to a List, where the CustomClass type exposes a public property called PropertyName:
<input type="hidden" name="prefix[idx].PropertyName" value="PropertyName[idx] Value" />
prefix is the ViewDataDictionary.TemplateInfo.HtmlPrefix, if one has been defined.
I find it deeply counter-intuitive to start the reference to something with the indexing information (i.e., the [idx] piece). I also find it disturbing that nowhere in this construct do I make reference to the name of the action method parameter to which I'm binding. That seems to be in direct contrast to what happens with, say, a primitive model property:
<input type="hidden" name="Text" value="something" />
public ActionResult SomeActionMethod( string Text )...
I understand I can roll my own model binder in MVC. That doesn't seem like a profitable use of my time, although spending hours trying to puzzle out the correct format for hidden fields isn't profitable either :), so maybe I'll try that. I also know that I can let MVC do all the heavy lifting by creating a type template using #Html.HiddenFor(), and then outputting instances of CustomClass though a simple partial view that has CustomClass as a model and the single line #Html.DisplayForModel(). But that seems like going the long way around the barn, too. Besides, there are limitations to using the #Html.Hidden helpers (e.g., they "helpfully" raid the cache after postback to fill in values, so writing #Html.Hidden("fieldname", value) doesn't guarantee value will end up being output -- you may get the older value in the cache instead, which was another hour-long annoyance today).
But I'm mostly just curious why this format was chosen. Why not something more C/C++/C#/VB like:
<input name="prefix.ParameterName.PropertyName[idx]" />
Edit:
Good point on where I put the index parameter in my example. You're right, the property isn't indexed, the containing class is.
However, that doesn't change the basic situation. Why isn't the syntax something like:
<input name="prefix.ParameterName[0].PropertyName" />
The standard syntax ignores the parameter name with collections of custom types, and "guesses" the custom type from the property names. That's bizarre...so there must be a story or choice behind it :).
Actually, it makes perfect sense. The index is into the collection, not the property. You don't have a collection of PropertyName, you have a Collection of CollectionName[] that has a PropertyName.
To put this another way:
public class Foo { public string Bar { get; set; } }
var foos = List<Foo>();
for (var i = 0; i < foos.Length; i++)
{
var prop = foos[i].Bar; // This is the important bit
}
That's exactly what happens in the model binder.
When the model binder deserializes the post values, it has to know which collection to insert the values into, and it has to know what index each item is for. So it has to know to create a Collection of Foos, of x number of items, and which indexes each Bar is associated with.
In ASP.NET MVC a checkbox is generated by the HtmlHelper code here:
<%= Html.CheckBox("List_" + mailingList.Key, true) %>
as this HTML:
<input id="List_NEW_PRODUCTS" name="List_NEW_PRODUCTS" type="checkbox" value="true" />
<input name="List_NEW_PRODUCTS" type="hidden" value="false" />
In case you're wondering why is there an extra hidden field? - then read this. Its definitely a solution that makes you first think 'hmmmmm' but then you realize its a pretty elegant one.
The problem I have is when I'm trying to parse the data on the backend. Well its not so much of a problem as a concern if anything in future were to change in the framework.
If I'm using the built in binding everything is great - its all done for me. But in my case I'm dynamically generating checkboxes with unknown names and no corresponding properties in my model.
So i end up having to write code like this :
if (forms["List_RETAIL_NOTIFICATION"] == "true,false") {
}
or this:
if (forms.GetValues("List_RETAIL_NOTIFICATION")[0] == "true") {
}
Both of which i still look at and cringe - especially since theres no guarantee this will always be the return value. I'm wondering if theres a way to access the layer of abstraction used by the model binders - or if I'm stuck with my controller 'knowing' this much about HTTP POST hacks.
Yes I'm maybe being a little picky - but perhaps theres a better clever way using the model binders that I can employ to read dynamically created checkbox parameters.
In addition i was hoping this this post might help others searcheing for : "true,false". Even though I knew why it does this I just forgot and it took me a little while to realize 'duh'.
FYI: I tried another few things, and this is what I found :
forms["List_RETAIL_NOTIFICATION"] evaluates to "true,false"
forms.GetValues("List_RETAIL_NOTIFICATION")[0] evaluates to "true"
(forms.GetValue("List_RETAIL_NOTIFICATION").RawValue as string[])[0] evaluates to "true"
forms.GetValues("List_RETAIL_NOTIFICATION").FirstOrDefault() evaluates to "true"
The default model binder handles this fine. So if you have:
public ActionResult Save(bool List_RETAIL_NOTIFICATION)
or
public ActionResult Save(MyObjectWithABoolPropertyToBeBoundFromACheckBox myObject)
is should work fine.
If you:
Create a model with a list or something for the "custom" property values
Inherent from DefaultModelBinder
Override BindProperty, and that the custom property values in some appropriate place, such as a list within the model type.
Then I think this does pretty much what you want. You're using the default binding for Boolean fields, but adding your own custom properties, without relying on reflection of your model type. See the release notes for the Release Candidate for more details on creating a custom model binder.
i have a simple form for an ASP.NET MVC application. I have a form property that is named differently (for whatever reason) to the real property name.
I know there's [Bind(Exlcude="", Include="")] attribute, but that doesn't help me in this case.
I also don't want to have a (FormsCollection formsCollection) argument in the Action method signature.
is there another way I can define the mapping?
eg.
<%= Html.ValidationMessage("GameServer", "*")%>
results in ..
<select id="GameServer" name="GameServer">
<option value="2">PewPew</option>
</select>
this needs to map to..
myGameServer.GameServerId = 2; // PewPew.
cheers!
i believe you will need to define it in your controller arguments or else it wouldnt have any clue what to accept.
public ActionResult GameServer(string GameServer){
GServer myGameServer = new GServer();
myGameServer.GameServerId.ToString() = GameServer;
return View("GameServer");
}
you can pass in the name/id of the parameter your trying to go for on your view page, it will automagically know the value to recieve based on the id on your view.
What's wrong with having a FormCollection passed as argument? I had to do the same thing as you and I just excluded the GameServer property in the BindAttribute.
Another thing you have to note is that Html.ValidationMessage("GameServer", "*") won't work because the underlying model doesn't contain a GameServer property. You have to add it to the model. I don't know if there is a better way to do it, I didn't find but it was required to make ValidationMessage works
You can create you own ModelBinder (see how to) to do the custom mapping.
An overkill IMO, but you might have your reasons...