Improved version of ASP.NET checkbox list values collecting method
In my last post about ASP.NET MVC checkbox list I came up with solution that makes it easy to update values in collection based on what was selected by user. I made some more progress meanwhile and here you can find improved version of collection update that breaks base class dependencies intoduced by previous solution.
Problem with previous solution
Signature to collection update method in my last solution was this:
public static void UpdateCollectionFromModel<T>(
this ICollection<T> domainCollection,
IQueryable<T> objects,
int[] newValues) where T : BaseEntity
If you take quick look at this method signature it seems like okay but when digging deeper we will find some bad dependencies here:
- we can use this extension method only with classes that inherit from BaseEntity,
- We can use only integer keys,
- Method forces also BaseEntity to use integer keys.
If you take a look at method body in previous checkbox list solution you will find one more requirement that solution forces to BaseEntity – it must define property called Id.
Main problems are:
- not all business classes have identity property of type integer (don’t be surprised if you also meet types like long and Guid),
- not all business classes define their identity with Id property.
If we can solve these problems then we have really generic solution that doesn’t depend on identity types and properties anymore.
Breaking dependencies
All problems above lead us away from Separation of Concerns (SoC) principle. Our domain layer implementation sneaks in even if we use model classes and we require all parts of our solution to accept the restricting knowledge. Simplest way to get base class and identity types away from this method is to move some responsibilities away from method.
My solution is actually simple:
- we turn identities from integer to generic so we can accept all types of identities and not only integers.
- we move code that finds object by id away from our method using Func that takes generic id and returns object of type T.
Here is the solution:
public static void UpdateCollectionFromModel<T, U>(this ICollection<T> domainCollection,
Func<U, T> objects,
U[] newValues) where T : class
{
var newObjects = new List<T>();
foreach (var newValue in newValues)
{
var newObject = objects(newValue);
if (newObject != null)
newObjects.Add(newObject);
}
for (var i = domainCollection.Count - 1; i >= 0; i--)
{
var domainObject = domainCollection.ElementAt(i);
if (!newObjects.Contains(domainObject))
domainCollection.Remove(domainObject);
}
foreach (var newObject in newObjects)
{
if (domainCollection.Contains(newObject))
continue;
domainCollection.Add(newObject);
}
}
This is how I call this method when I update tags collection of event in my events application:
evt.Tags.UpdateCollectionFromModel(
i => Context.Tags.FirstOrDefault(t => t.Id == i),
model.TagIds);
It is a little bit more code but this code is free of problems I listed in the beginning of this posting.
Conclusion
As previous version of checkbox list value collecting code forced restrictions to classes and identity types I worked out better solution that is more generic and doesn’t force code user to change his or her current classes. We got rid of integer id-s by using generic id-s and moving object finding out from our method gave us code that uses only collections and objects. Although calling the new version of method means some more code we don’t have bad dependencies anymore and we can use the same method also in other applications.
A couple of suggestions:
The newValues parameter could be of type IEnumerable{U} instead of U[], which would allow a wider range of inputs.
The first foreach block can be replaced with a relatively simple LINQ statement:
var newObjects = newValues.Select(objects).Where(newObject => newObject != null).ToList();
Thanks for goo suggestions, Richard!
Gunnar, I really like your approach. How does this change affect the ToCheckBoxListSource extension method which also relied on the BaseEntity?
They are separate methods that doesn’t affect each other directly.
ToCheckBoxListSource() just turns collection of domain objects to list items collection. The same type of collection is also supported by other HTML helper methods that need selection of items (@Html.DropDownListFor() by example.
UpdateCollectionFromModel() is more general method than previous one. It operates on arbitrary collection of valued from form and updates given collection