Passing int list as a parameter to a web user control
-
05-07-2019 - |
Question
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);
}
}
OTHER TIPS
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.
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.
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;
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;
}
}
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>
.
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)));
}
}
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.