Search code examples
c#asp.nettypeconverter

Passing int list as a parameter to a web user control


I want to pass an int list (List) as a declarative property to a web user control like this:

<UC:MyControl runat="server" ModuleIds="1,2,3" />

I created a TypeConverter to do this:

public class IntListConverter : System.ComponentModel.TypeConverter
{
    public override bool CanConvertFrom(
           System.ComponentModel.ITypeDescriptorContext context, 
           Type sourceType)
    {
        if (sourceType == typeof(string)) return true;
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(
      System.ComponentModel.ITypeDescriptorContext context, 
      System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            string[] v = ((string)value).Split(
                new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            List<int> list = new List<int>();
            foreach (string s in vals)
            {
                list.Add(Convert.ToInt32(s));
            }
            return list
        }
        return base.ConvertFrom(context, culture, value);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context,
      Type destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor)) return true;
        return base.CanConvertTo(context, destinationType);
    }
    public override object ConvertTo(ITypeDescriptorContext context,
      System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor) && value is List<int>)
        {
            List<int> list = (List<int>)value;
            ConstructorInfo construcor = typeof(List<int>).GetConstructor(new Type[] { typeof(IEnumerable<int>) });
            InstanceDescriptor id = new InstanceDescriptor(construcor, new object[] { list.ToArray() });
            return id;
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

And then added the attribute to my property:

[TypeConverter(typeof(IntListConverter))]
public List<int> ModuleIds
{
    get { ... }; set { ... };
}

But I get this error at runtime:

Unable to generate code for a value of type 'System.Collections.Generic.List'1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'. This error occurred while trying to generate the property value for ModuleIds.

My question is similar to one found here, but the solution does not solve my problem:

Update: I found a page which solved the first problem. I updated the code above to show my fixes. The added code is the CanConvertTo and ConvertTo methods. Now I get a different error.:

Object reference not set to an instance of an object.

This error seems to be indirectly caused by something in the ConvertTo method.


Solution

  • After hooking a debugger into Cassini, I see that the null ref is actually coming from System.Web.Compilation.CodeDomUtility.GenerateExpressionForValue, which is basically trying to get an expression for the int[] array you pass into the List constructor. Since there's no type descriptor for the int[] array, it fails (and throws a null ref in the process, instead of the "can't generate property set exception" that it should).

    I can't figure out a built in way of getting a serializable value into a List<int>, so I just used a static method:

    class IntListConverter : TypeConverter {
        public static List<int> FromString(string value) {
           return new List<int>(
              value
               .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
               .Select(s => Convert.ToInt32(s))
           );
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
            if (destinationType == typeof(InstanceDescriptor)) {
                List<int> list = (List<int>)value;
                return new InstanceDescriptor(this.GetType().GetMethod("FromString"),
                    new object[] { string.Join(",", list.Select(i => i.ToString()).ToArray()) }
                );
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }