将整型列表作为参数传递给Web用户控件

12
我希望能将一个整数列表(List)作为声明性属性传递给 Web 用户控件,如下所示:
<UC:MyControl runat="server" ModuleIds="1,2,3" />

我创建了一个TypeConverter来完成这个任务:
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);
    }
}

然后将属性添加到我的属性中:

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

但我在运行时遇到了这个错误:

无法为类型为 'System.Collections.Generic.List'1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' 的值生成代码。 在尝试为 ModuleIds 生成属性值时发生此错误。

我的问题类似于这个,但是解决方案并没有解决我的问题:

更新: 我找到了一个解决第一个问题的页面。 我更新了上面的代码以显示我的修复。 添加的代码是 CanConvertToConvertTo 方法。 现在我得到了不同的错误:

对象引用未设置为对象的实例。

这个错误似乎是由 ConvertTo 方法中的某些内容间接引起的。


你肯定没有在类名中写IntListConverter,在属性中写IntegerListConverter,对吧? - Alan
哈,不...我会修复的。 - Kevin Albrecht
谢谢您提出这个问题。我也遇到了几乎相同的问题。 - Ashraf Sabry
8个回答

10
在将调试器连接到Cassini后,我发现空引用实际上来自System.Web.Compilation.CodeDomUtility.GenerateExpressionForValue,它基本上试图获取您传递到List构造函数中的int []数组的表达式。由于int[]数组没有类型描述符,因此失败了(在过程中抛出空引用异常,而不是应该抛出的“无法生成属性设置异常”)。
我找不到内置的方法将可序列化的值添加到List中,所以我只使用了一个静态方法:
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);
    }
}

1
同意这个答案加分。这是一个解决非常模糊的问题的有效方法。除了使用List<int>而不是List<string>之外,我遇到了与OP相同的问题。解决方案通过对字符串与整数进行一些小修改就完美地解决了这个问题。 - KP.
谢谢您的回答。现在我明白了:页面构建器能够发出“1,2,3”表达式,但无法发出新的List<string>{"1","2","3"}表达式。有没有一种方法可以指示构建器发出这样的表达式? - Ashraf Sabry
我不喜欢在生成的控件类文件中将值写成字符串形式,比如“1,2,3”,因为它会在每个请求时进行反序列化。所以我为我的表达式编写了一个ControlBuilder来初始化集合,然后通过调用Add方法添加每个值。 - Ashraf Sabry

1

我通过创建2个属性解决了类似的问题:

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

ModuleIDstring将其值集转换为列表,并设置ModuleIDs属性。

这也使得ModuleIDs可以从PropertyGrid等中使用。

好的,虽然不是最好的类型安全解决方案,但对我来说它很有效。


这可能是我们最终采用的解决方案,但仍然奇怪的是我们无法找出如何修复实际错误。 - Kevin Albrecht

1

这似乎是正确的方向,但我仍然遇到了错误。请查看我的更新。 - Kevin Albrecht

0

从代码后台传递列表...

aspx:

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

代码后台:

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

uc.ModuleIds = list;

这是我一直在使用的解决方法,但肯定不是最佳选择。 - Kevin Albrecht
对我来说,这比类型转换器类好得多。哪一个更易读? - Rismo
这个问题在于它需要对代码进行修改,这会破坏代码声明性和非声明性部分之间的分离。 - Kevin Albrecht

0
通常我做这件事的方法是让属性包装 ViewState 集合。如果你的方法能够成功地实现,那么它看起来更好,但是这种方法也能够完成工作:
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;
   }
}

有趣的想法,但仍不是最佳选择,因为它将无法让声明性标记起作用。 - Kevin Albrecht

0
你可以将它传入一个字符串并在逗号上拆分以填充私有变量。虽然没有属性的美观,但是可以工作。
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)));
    }
}

这是可能的,但并没有解决问题。我正在尝试做的应该是可能的,不是吗? - Kevin Albrecht

0

我认为问题出在set{}上。类型转换器想要将List<int>转换回字符串,但是CanConvertFrom()对于List<int>失败了。


不,那种情况由一个ConvertTo方法处理,为了清晰起见没有显示出来。 - Kevin Albrecht

0

我认为你最好的选择是让你的用户控件拥有一个类似于DataSource的属性。

你可以将该属性作为一个对象,并进行一些类型检查,例如IList/ IEnumerable等,以确保它是正确的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接