views:

1236

answers:

8

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.

A: 

pass the list from the code behind...

aspx:

<UC:MyControl id="uc" runat="server" />

code-behind:

List<int> list = new List<int>();
list.add(1);
list.add(2);
list.add(3);

uc.ModuleIds = list;
craigmoliver
This is the workaround I've been using, but definitely less than optimal.
Kevin Albrecht
To me, this looks way better than the Type converter class. Which one is more readable?
Rismo
The problem with this is it requires modifications to the code behind, which breaks the separation of declarative and non-declarative parts of the code.
Kevin Albrecht
A: 

The way I normally do this is to make the property wrap the ViewState collection. Your way looks better if it can be made to work, but this will get the job done:

public IList<int> ModuleIds
{
   get
   {
       string moduleIds = Convert.ToString(ViewState["ModuleIds"])

       IList<int> list = new Collection<int>();

       foreach(string moduleId in moduleIds.split(","))
       {
          list.Add(Convert.ToInt32(moduleId));
       }

      return list;
   }
}
Simon Johnson
Interesting idea, but still not optimal, as it would not allow declarative markup to work.
Kevin Albrecht
A: 

I believe the problem is the set{}. The type converter want to change the List<int> back into a string, but CanConvertFrom() fails for List<int>.

James Curran
No, that case is handled by a ConvertTo method, which is not shown, for clarity.
Kevin Albrecht
A: 

You can pass it into a string and split on comma to populate a private variable. Does not have the nicety of attribution, but will work.

private List<int> modules;
public string ModuleIds
{
  set{
    if (!string.IsNullOrEmpty(value))
    {
    if (modules == null) modules = new List<int>();
    var ids = value.Split(new []{','});
    if (ids.Length>0)
      foreach (var id in ids)
        modules.Add((int.Parse(id)));
    }
}
jkind
This is possible, but does not solve the problem. What I am trying to do should be possible, shouldn't it?
Kevin Albrecht
A: 

WHile I can't say I have any particular experience with this error, other sources indicate that you need to add a conversion to the type InstanceDescriptor. check out:

http://weblogs.asp.net/bleroy/archive/2005/04/28/405013.aspx

Which provides an explanation of the reasons or alternatively:

http://forums.asp.net/p/1191839/2052438.aspx#2052438

Which provides example code similar to yours.

Brian B.
This seems to be the right direction, but I'm still getting an error. See my update.
Kevin Albrecht
A: 

I think that you're best option is to make your usercontrol have a DataSource-style property.

You take the property as an object and then do some type checking against IList/ IEnumerable/ etc to make sure that it is correct.

Slace
+3  A: 

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);
    }
}
Mark Brackett
Looks good, thanks! Crazy reflection.
Kevin Albrecht
+1 Thanks for your answer, saved me a lot of time
Andreas Grech
A: 

I solved something simular by creating 2 properties:

public List<int> ModuleIDs { get .... set ... }
public string ModuleIDstring { get ... set ... }

The ModuleIDstring converts its value set to a list and sets the ModuleIDs property.

This will also make the ModuleIDs usable from a PropertyGrid etc.

Ok, not the best, typesafe solution, but for me it works.

GvS
This is probably the solution we will end up going with, but it is still strange that we can't figure out how to fix the actual error.
Kevin Albrecht