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.
Related
I'm trying to design a solution in MVC in which a string representation of a class is passed to the controller which should then build a grid with all the data belonging to that class in the DB. (I'm using an ORM to map classes to tables).
//A method in the Model that populates the Item Property
foreach (MethodInfo method in sDRMethods)
{
if (method.Name.Contains(_domainTable))
{
Items = method.Invoke(repositoryObject, null);
break;
}
}
//View uses this Items property of the Model to populate the grid.
public object Items;
//_domainTable is the name of the table/class (in string format).
//repositoryObject is the object that has methods to return IEnumerable<class> collection object of each type.
The problem I have is that I do not know how to cast the "Items" property in my view to iterate through it and build a grid.
I have tried using the "http://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/" but the generic extension method is expecting to know the specific type that it should work with.
I would prefer to use MVC but it looks like I cannot easily have this working(which is very hard to believe).
I really don't like the sound of what you are trying to do. Why convert the table to a string?
The only time you would convert to a string, is when the view gets rendered. And that, in most cases, should be left to the MVC framework.
The code you mentioned uses an HtmlTextWriter which is fine, because it will render straight to the response.
However, it sounds as if you are trying to reinvent the wheel by rendering everything to a string, rather than leaving that to the framework.
Note that in MVC the views are just templates for rendering strings, which is, if I have understood you, exactly what you need.
So, if I have remotely understood what you are trying to do, and it is a big if because your post is not clear, you should pass your class to view as part of the strongly typed model, and then write some basic design logic into the view.
If I am right, which is not certain, I think you have misunderstood how MVC works.
Have a look at a few examples of how to use views to render the data in a model. The model can be any class, it can be an IEnumerable, a list, whatever, and you can use foreach loops in the view to render out what you want, how you want it.
In this sense, MVC is very different to writing custom controls in plain vanilla ASP.NET.
Thanks for your reply awrigley.
The requirement is quite simple. I perhaps made it sound awfully complex in my post.
On an Index view, I have to populate a dropdownlist with all the tables of the application that are system lookup. The "Admin" of the app, selects an item from the dropdownlist which should show the contects of that table in a grid so that the admin can perform CRUD operations using that grid.
What I am trying to do is, pass the selected item (which is the name of the table) to the controller which in turn passes it to the ViewModel class. This class uses reflection to invoke (code shown in my original question) the right method of a repository which has got methods like:
public IEnumerable GetAllTable1Data()
{
.....
}
The problem I have is that when I invoke the method, it returns a type "object" which I cannot cast to anything specific because I don't know the specific type that it should be cast to. When this object is passed to the view, the grid is expecting an IEnumerable or IEnumerable but I do not know this information. I am not able to do this:
(IEnumerable)method.Invoke(repositoryObject, null)
I get: cannot cast IEnumerable to IEnumerable
I (kind of) have the grid now displaying but I am using a Switch statement in the view that goes:
Switch(SLU_Type)
{
case "SLU_Table1": Html.Grid((IEnumerable)Model.Items);
case "SLU_Table2": Html.Grid((IEnumerable)Model.Items);
.....
}
I don't like this at all, it feels wrong but I just cannot find a decent way!
I could have partial views for each of the system look up tables but for that I'll have to add around 30 partial views with almost exactly same code for the Action & View. This does not seem right either!
Hopefully, this gives you a better understanding of what I'm trying to achieve.
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.
I've got a View where I use a naming-convention on my text-fields, to indicate what should be done with the content once it is posted back to my controller.
The format is similar to:
<input type="text" name="RegistrationLine#ID" />
for updates
<input type="text" name="CreateRegistrationLine#LineNumber" /> for create
Now since I'm using this Naming-convention, regular model-binding isn't possible. But I've been reading up a bit on the subject and did find a bit of an indication that it would be possible to write a custom model binder, that should be able to help parse and bind these form elements and instantiate the objects correctly.
Please read: Bind formValue to property of different name, ASP.NET MVC
This is a bit similar to what I am doing except, I have the additional complexity of having appended information in the formelement-name that I am trying to bind to.
Am I way off base here? and if not, can any of you drop a few lines of code to show how you would start this model-binder off..
If this is a very bad approach to what I am really trying to achieve, I would love to hear suggestions for better approaches. Just note that what I want to be able to do is post back both updates and creates in one go.
I kinda have to agree with #jfar and #omar. I don't think a custom model binder is where you want to be in this instance.
You can pass a complex type to your view and simply use the full stop like #jfar mentioned.
id="Model.Person.Name.FirstName" will happily bind to an object named Person that has a class in it called Name that has a property called FirstName.
Now if you want to do some special checks on the data you could implement a partial class which would do the validations etc and populate the ModelState errors.
public partial class Name
{
public void Validate(){ }
public int CreateRegistrationLine(){ }
public bool DoSpecialActions(){ }
}
It's a little unclear what your special actions are doing so my example above may not be what you want.
I'm facing a really strange problem that has me smoked.
I have a fairly simple scenario where I have a strongly typed view that is correctly populated from the controller on the GET, but then when it POSTS the form to the controller, the Reqeust is full of all the right values and right key names for the default model binder to correctly populate one of my model objects, and the DMB creates the correct opject, but it never populates any of the properties, they're all in their default state.
This was working before, the only changes I can think of were that I tried a custom modelbinder (then removed it; double checked to make sure I'm not still using that), and I refactored the model to have a base class with some of the props.
Any thoughts?
A very similar scenario - that the DefaultModelBinder is - essentially - not binding to your model, arrise if you would give your bound model object the same name as one of its properties:
Model
Public Property ArbitraryName1 As Integer
Public Property Answer As String
Public Property ArbitraryName2 As Boolean
View
<p/> ... #Model.ArbitraryName1
<p/> Answer: #Html.TextBoxFor(Function(model) model.Answer)
<p/> ... #Html.CheckBoxFor(Function(model) model.ArbitraryName2)
Controller
<HttpPost()>
Function Index(answer As Model) As ActionResult
' answer is Nothing
End Function
(Using ASP.NET MVC 3)
Got it. The model had been refactored in a way which naturally affected the ability of the mdoel binder to populate it.
The name of your input param do not have to be equal to some property name of the object. Remember that all data coming as an array of name -> value and the default binding use the names for make the relation work.
I had this behaviour arise by moving two properties from the top of the class to further down. I still can't work out why this stopped the binding of a third property from working (so this isn't a solution so much as a 'watch out for') but I repeated the change multiple times and each time the binding went from working to not working.
I also found that after making this change I sometimes had to 'Clean' the solution for the binding to start working again.
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.