1

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.

8
  • The model binder is going to try an match value names in your return values to property names in your object. Commented May 12, 2010 at 12:04
  • It works but only if I select at least the first checkbox (name = SubDTO[0].Id)! The constructor sets SubDTOs = new List< SubDTO>(); and if the first one is selected the model binder is 'intelligent' enough to add the other n values. However, if the first checkbox is not selected the model binder sets SubDTOs to null - I do not know why and how this happens. The values are posted though as verified using firebug! Commented May 12, 2010 at 12:24
  • You don't have a property called "SubDTO[1].Id", the first is almost certainly coming back as a null array. I'm not sure why it's matching "SubDTO[0].Id" to SubDTO but it appears to be. I'm pretty sure that every checkbox name is going to be actually returning an array of values. Your DTO object has 1 property so it gets populated from the 1st array which when the first checkbox is unchecked is an empty array so your object isn't populated and the rest of the arrays (remember 1 for each checkbox name) are dropped as unused params. Commented May 12, 2010 at 12:40
  • That's exactly what I do not understand. Please note that as long as I post SubDTO[0].Id all the other n Id values are entered into SubDTOs e.g.: SubDTO[0].Id = 10, SubDTO[2].Id = 11 etc. Commented May 12, 2010 at 12:44
  • And what happens when you take my approach below? Commented May 12, 2010 at 13:07

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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.

1 Comment

Thanks Lester this explains it I guess. Is there an alternative to my checkboxes to achieve the above behaviour? Thanks!
1

Change the markup as follows:

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

What's being returned by your original markup is an unrelated set of parameters, i.e. like calling RedirectToRouteResult Create(SubDTO[0].id, SubDTO[1].id, ..., SubDTO[n].id) which is clearly not what you want, you want an array returned into your DTO object so by giving all the checkboxes the same name the return value to your function will be an array of ids.

EDIT

Try this:

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

You have to return something to make sure there is an element for each index, I suspect that any gap will cause a problem so I'd suggest using a 'null' ID, for example 0 or -1 and then process that out later in your code. Another answer would be a custom model binder.

There is always the alternate option of adding a property to your class that takes an array of strings and creates the SubDTO array from that.

public List<string> SubDTOIds 
{ 
    get { return SubDTO.Select(s=>s.Id).ToList(); }
    set
    {
        SubDTOs = new List< SubDTO>();
        foreach (string id in value) 
        {
            SubDTOs.Add(new SubDTO { Id = id });
        }
    }
}

or something like that

8 Comments

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.
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.
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!
Finally... the penny drops, the light goes on, and I understand... I can be dim sometimes! Another suggestion above.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.