Ant
1 note

Collection Editor - Part 2 - Selection, Binding and Validation

In part 1, I discussed what I would like out of a reusable collection editor, and dropped the code for the series.

Now I want to talk about how the user is able to add and remove items from the collection through the one form and how to action this request.

The Selector

This is a checkbox on every element of the collection displayed in the UI, the user can deselect an existing one to delete an item and select a new one to add.

This is provided through a helper and no property exists on the model in question.

<span class="selector">
    @Html.CollectionSelector(Model.Id>0)
</span>

This means that the model does not contain particular information about how to edit it, and this means that it can be used again for display. Sometimes you want a model to know specifics about the view its being used for, and some people advocate one model per view, but I didn't want the collection editor to be the reason why a model was tied to a particular view or a particular view mode.

So the mark-up produced will post a back the selected state for the item in the collection, for example.

<span class="selector">
	<input id="Addresses_1____selector" type="checkbox" 
	    value="true" name="Addresses[1].__selector">
	<input type="hidden" 
            value="false" name="Addresses[1].__selector">
</span>

The Binder and Validation

On post the binder now has this necessary information to remove the unselected items, and importantly, remove any validation errors caused by items to be removed - adding is done by the default binder anyway.

To achieve this you must create a new IModelBinderProvider which can check for models which are collections and apply the new CollectionBinder.

public IModelBinder GetBinder(Type modelType) {
    if (modelType.IsGenericType) {
        var modelGenericType = modelType.GetGenericTypeDefinition();
        if (modelGenericType == typeof(IEnumerable<>)
            || modelGenericType == typeof(ICollection<>)
            || modelGenericType == typeof(IList<>)) {
            return _collectionBinder;
        }
    }

    return null;
}

This is the CollectionBinder.BindModel method doing the required work

public override object BindModel(
    ControllerContext controllerContext,
    ModelBindingContext bindingContext) {
    // let the base do the majority of the work
    var model = base.BindModel(controllerContext, bindingContext);
    if(model!=null) {
        var list = (IList) model;
        var index = 0;

        // loop a copy of the list
        foreach (var item in list.Cast<object>().ToArray()) {
            var key = string.Format("{0}[{1}]", 
                        bindingContext.ModelName, index);
            // check for the selector being false
            if (controllerContext.HttpContext.Request.Form[
                string.Format("{0}.{1}", key, SELECTOR_NAME)] == "false") {

                // remove the item from the original list
                list.Remove(item);

                // remove any validation errors
                foreach (var stateKey in bindingContext.ModelState.Keys.ToArray()
                    .Where(k => k.StartsWith(key)))
                    bindingContext.ModelState.Remove(stateKey);
            }
            index++;
        }
    }

    return model;
}

Its worth mentioning, I wanted really to do the check before the model was added to the collection and particularly before it was validated, but there no current entry points to be able to do this, and after pouring over the MVC 3 source code, this was the best solution I could find.

Next Post ..

I will look at the Views and Templates used and show how the collection editor attempts to be as DRY as it can.

Attachments

sandbox-collectionEditor.zip c# asp.net mvc vs 2010

* please note the demo uses the SQL Server compact framework, you can change the web.config connection string to point at your SQL Server instance instead

Giolvani

great!!

thanks a lot...


Post a Note

(required)

(required never shown)

On Twitter Follow MrAntix on Twitter

15/05/2012
WindowsAzure
Announcing the MEET Windows Azure Event! Streamed online June 7th. Register at http://t.co/bObzTAuL  #MEETAzure #WindowsAzure

10/05/2012
kevinwhinnery
Comparing Titanium and PhoneGap - A common question I get asked at developer events and conferences is how... http://t.co/Zq2eND6B

09/05/2012
brianleroux
PhoneGap goals and philosophy: http://t.co/wkq8wI2T

just now
ChaseDiane
encrypting phonegap android by joseesfera: currently being developed for PhoneGap app, we need to encrypt the co... http://t.co/qCSGs9iX

just now
rgonv
#Tech Post: Bing Search API now available on Windows Azure Marketplace: The Bing Search API is now available on ... http://t.co/iBvXOjbC