views:

321

answers:

2

All,

I've read through a lot of posts about Checkboxes and ASP.MVC but I'm not that much wiser.

My scenario:

I have a strongly typed View where I pass a collection of summary objects to the view for rendering in a for-each. This summary object contains label data based on a unique id. I also add a checkbox to the row so do so via:

<td>
    <%= Html.CheckBox("markedItem", Model.MarkedItem, new { TrackedItemId = Model.Id })%>
</td>

When I perform a POST to get the submitted results my action method takes the strongly typed ViewModel back but the original summary object that I used to create the list is not populated.

Ok, this is annoying, but I can understand why so I'll live with it.

What I then do is to add a new property to my ViewModel called "MarkedItem" which is a string collection.

On postback this marked item is filled with the before and after states if the checkbox has changed but nothing to tell me which key they were for. Just to clarify, if I send this

  • TrackedItemId = A, Value = false
  • TrackedItemId = B, Value = true
  • TrackedItemId = C, Value = false

and set the page to this:

  • TrackedItemId = A, Value = true
  • TrackedItemId = B, Value = true
  • TrackedItemId = C, Value = false

I will get back this:

  • MarkedItem[0] = true
  • MarkedItem[1] = false
  • MarkedItem[2] = true
  • MarkedItem[3] = false

in other words [0] is the new value and [1] is the old value, [2] and [3] represent values that haven't changed.

My questions are:

  1. Is this right - that I get before and after in this way? Is there any way to only send the latest values?
  2. How can I get hold of the custom attribute (TrackedItemId) that I've added so that I can add meaning to the string array that is returned?

So far I like MVC but it not handling simple stuff like this is really confusing. I'm also a javascript noob so I really hope that isn't the answer as I'd like to return the data in my custom viewmodel.

Please make any explanations/advice simple :)

A: 
Matt
Thanks - I'd seen this before but I was hoping for something a lot less "hacky". I'm not sure if this is generic enough to be used in a custom ModelBinder.It's a start though... I'm hoping for other suggestions though
Graham
A: 

ok, one hack I've come up with - I really hate that I have to do this but I don't see another way round it and I'm sure it will break at some point.

I've already implemented by own ModelBinder to get round some other issues (classes as properties for example) so have extended it to incorporate this code. We use Guid's for all our keys.

If there are any alternatives to the below then please let me know.

Html

<%= Html.CheckBox("markedItem" + Model.Id, false)%>

C#

(GuidLength is a const int = 36, Left and Right are our own string extensions)

//Correct checkbox values - pull all the values back from the context that might be from a checkbox. If we can parse a Guid then we assume
//its a checkbox value and attempt to match up the model. This assumes the model will be expecting a dictionary to receive the key and 
//boolean value and deals with several sets of checkboxes in the same page

//TODO: Model Validation - I don't think validation will be fired by this. Need to reapply model validation after properties have been set?    
Dictionary<string, Dictionary<Guid, bool>> checkBoxItems = new Dictionary<string, Dictionary<Guid, bool>>();

foreach (var item in bindingContext.ValueProvider.Where(k => k.Key.Length > GuidLength))
{
    Regex guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
        if (guidRegEx.IsMatch(item.Key.Right(GuidLength)))
        {
            Guid entityKey = new Guid(item.Key.Right(GuidLength));
            string modelKey = item.Key.Left(item.Key.Length - GuidLength);

            Dictionary<Guid, bool> checkedValues = null;
            if (!checkBoxItems.TryGetValue(modelKey, out checkedValues))
            {
                checkedValues = new Dictionary<Guid, bool>();
                checkBoxItems.Add(modelKey, checkedValues);
            }
        //The assumption is that we will always get 1 or 2 values. 1 means the contents have not changed, 2 means the contents have changed
        //and, so far, the first position has always contained the latest value
            checkedValues.Add(entityKey, Convert.ToBoolean(((string[])item.Value.RawValue).First()));
        }
}

foreach (var item in checkBoxItems)
{
    PropertyInfo info = model.GetType().GetProperty(item.Key,
            BindingFlags.IgnoreCase |
            BindingFlags.Public |
            BindingFlags.Instance);

        info.SetValue(model, item.Value, null); 
}
Graham