tags:

views:

86

answers:

3

I have some problems with ASP.NET MVC’s default model binder. The View contains HTML like this:

<input name="SubDTO[0].Id" value="1" type="checkbox">
<input name="SubDTO[1].Id" value="2" type="checkbox">

This is my simplified ‘model’:

public class SubDTO
{
    public virtual string Id { get; set; }
}

public class DTO
{
    public List<SubDTO> SubDTOs { get; set; }

    public DTO()
{
    SubDTOs = new List< SubDTO>();
}
}

All this works fine if the user selects at least the first checkbox (SubDTO[0].Id). The controller ‘receives’ a nicely initialised/bound DTO. However, if the first check box is not selected but only, for example, SubDTO[1].Id the object SubDTOs is null. Can someone please explain this ‘strange’ behaviour and how to overcome it? Thanks.

Best wishes,

Christian

PS:

The controller looks like this:

     [Transaction]
        [AcceptVerbs(HttpVerbs.Post)]
        public RedirectToRouteResult Create(DTO DTO)
        {
...
}

PPS:

My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id SubDTOs is initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! This must be a bug in the default model binder or might be missing something.

A: 

This behavior is "by design" in html. If a check-box is checked its value is sent to the server, if it is not checked nothing is sent. That's why you get null in your action and you'll not find value in the posted form either. The way to workaround this is to add a hidden field with the same name and some value AFTER the check-box like this:

<input name="SubDTO[0].Id" value="true" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="true" type="checkbox">    
<input name="SubDTO[1].Id" value="false" type="hidden">

In this way if you check the check-box both values will be sent but the model binder will take only the first. If the check-box is not checked only the hidden field value will be sent and you\ll get it in the action instead of null.

Branislav Abadjimarinov
My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id things are initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! Hence this cannot be 'by design' but must be a bug in the default model binder.
csetzkorn
You seem to have edited your answer so I accepted it. Thanks!
csetzkorn
+1  A: 
Lazarus
I could have:List<string> SubDTOs in my DTO and use:<input name="SubDTOs" value="1" type="checkbox"><input name="SubDTOs" value="2" type="checkbox">no problem! But I need the access path:SubDTO.Id if you know what I mean. Please note that the value is an actual (dynamic) pimary key and my check boxes are generated via a html helper.
csetzkorn
No, I don't know what you mean. You are using the string literal "SubDTO[0].Id" as a name for a checkbox where what I think you want is as I have given above, the checkbox to return the *value* of SubDTO[0].Id". Using the code above you'll get an array of the "checked" SubDTO Ids passed to the model binder.
Lazarus
The idea is that SubDTO[0].Id would assign the value of the check box to the Id of the first object in (List<SubDTO>) SubDTOs. This seems to work fine as long as I post at least the name SubDTO[0].Id and its value!
csetzkorn
Finally... the penny drops, the light goes on, and I understand... I can be dim sometimes! Another suggestion above.
Lazarus
Hi,Thanks. This does not seem to work. It posts all the hidden values but does not send the checkbox 'values' even when they are selected. Thanks anyway.
csetzkorn
I figured it. Please replace <input name="SubDTO[0].Id" value="0" type="hidden"> with <input name="SubDTO[0].Id" value="false" type="hidden"> (see Branislav Abadjimarinov below - although his answer was not complete). This will send all the values but replace false with an integer (e.g. primary key) if checkbox selected. I think I can live with this. Still do not understand this strange model binding behaviour - reminds me a bit of excel which decides upon the datatype by looking at the first n cells - microsoft i guess.
csetzkorn
Indeed but if you think about it another way you were asking the framework to create a sparse array, i.e. starting at element 1 in a 0-based environment. Regardless, glad you got there and I learnt a little something along the way as well. I would suggest looking at the alternate initialisation approach above, then you can my first approach just changing the checkbox's id to match the string[] property name.
Lazarus
Thanks Lazarus. Your are right regarding the sparse array bit. Maybe I should write a dedicated model binder for this situation. I will accept Branislav Abadjimarinov's answer as it led to the 'solution'. Thanks for all your help.
csetzkorn
+1  A: 

I think this post on Scott Hanselman's blog will explain why. The relevant line is:

The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.

So, in your case because the first element is not returned (as explained by others as the default behaviour for checkboxes) the entire collection is not being initialized.

Lester
Thank you for verifying my assumption! +1
Lazarus
Thanks Lester this explains it I guess. Is there an alternative to my checkboxes to achieve the above behaviour? Thanks!
csetzkorn