Tuesday, November 22, 2011

ASP.NET MVC Comma Separated Values Model Binder

In some scenarios such as data filtering it becomes necessary to pass multiple values to a single collection-typed request parameter. In typical flows this is achieved by passing multiple pairs of key and value parameters to the request.

/path/to/foo?key=value1&key=value2&key=value3

While this works well, sometimes it is desirable to have something that is more human-readable. In an effort to satisfy that requirement, I have built a custom model binder to handle passing multiple values to a single parameter in the form of a single comma separated value.

/path/to/foo?key=value1,value2,value3

This CommaSeparatedValuesModelBinder supports both generic collections and standard array types so long as the underlying type of the collection inherits from IConvertible (int, string, etc). In addition to supporting a comma separated value assignment, the implementation also maintains support for the default format of multiple key and value pairs.

Here's the code:

public class CommaSeparatedValuesModelBinder : DefaultModelBinder
{
    private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray");

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, 
        PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType.GetInterface(typeof (IEnumerable).Name) != null)
        {
            var actualValue = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);

            if (actualValue != null && !String.IsNullOrWhiteSpace(actualValue.AttemptedValue) &&
                actualValue.AttemptedValue.Contains(","))
            {
                var valueType = propertyDescriptor.PropertyType.GetElementType() ??
                                propertyDescriptor.PropertyType.GetGenericArguments().FirstOrDefault();

                if (valueType != null && valueType.GetInterface(typeof (IConvertible).Name) != null)
                {
                    var list = (IList) Activator.CreateInstance(typeof (List<>).MakeGenericType(valueType));

                    foreach (var splitValue in actualValue.AttemptedValue.Split(new[] {','}))
                    {
                        if(valueType.IsEnum)
                        {
                            try
                            {
                                list.Add(Enum.Parse(valueType, splitValue));
                            }
                            catch { }
                        }
                        else
                        {
                            list.Add(Convert.ChangeType(splitValue, valueType));
                        }
                    }

                    if (propertyDescriptor.PropertyType.IsArray)
                    {
                        return ToArrayMethod.MakeGenericMethod(valueType).Invoke(this, new[] {list});
                    }
                    return list;
                }
            }
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

Thursday, January 13, 2011

How to Get the Fully Qualified Name of a .NET Assembly

In .NET, when referencing satellite assemblies it is generally best practice to use an assembly’s fully qualified name, which includes the name, version number, culture and public key token.

The easiest way I have found to determine the fully qualified name of an assembly is to simply drop it into Red Gate’s .NET Reflector, select it, and check the information displayed at the bottom of the window.

Besides the assembly name, the rest of the elements in a fully qualified name are optional, but including them can help to secure your applications.