Unable to generate code for a value of type... Can you tell what's wrong?

I recently got this error when trying to run a WebControl that had a property that is a collection of objects that have a property that uses a type converter to convert its string persistence format to its real type (still following me?). Here's what the persisted format looks like:

<sample:CallbackProxy runat=server ID="AddProxy" EventTriggerID="AddButton" ClientEventName="onclick">
 
<Parameters>
   
<sample:CallbackParameter ControlID="Number1" ClientType="integer" />
 
</Parameters>
</sample:CallbackProxy>

Here's what the ClientType property declaration looks like:

[TypeConverter(typeof(ClientTypeConverter))]
public IClientType ClientType {

This error is very puzzling because if you debug into the type converter, everything seems to be working fine: the string in the persisted format gets correctly converted into an IClientType. The error is difficult to track because it doesn't happen technically at run-time but just before that, at parse-time. To find out what's wrong, you've got to attach a debugger to the web server and break on exceptions. It's quite surprising to see that the problem is not with the type converter converting from string, but converting from IClientType to InstanceDescriptor.

Why is the parser trying to do that with our type?

The parser's job is to construct C# or VB source code for a procedural version of your declarative aspx file. Once it's done, the page is a program that constructs the control tree and sets control properties. To do that efficiently, type-converting from string is out of the question because that would have an unacceptable performance hit every time the page runs. What the parser does is get a much more efficient way to get the right instance from each property type. It does have an instance that it got by type-converting from string (which is okay: parsing happens only once), but it can get some factory code (the InstanceDescriptor). In essence, it's asking the type convertor "how do I get code that will recreate this instance at run-time". That's what the InstanceDescriptor does. You build that InstanceDescriptor usually from a ConstructorInfo, but it can also be some static member's MemberInfo (some kind static factory is typically what you're looking for here if a constructor is impractical).

In our case, the type convertor must be able to convert from and to string, but also to InstanceDescriptor. Here, we're building the InstanceConvertor from the parameterless constructor of the concrete type of the instance:

if (destinationType == typeof(InstanceDescriptor)) {
 
return new InstanceDescriptor(valueType.GetConstructor(new Type[] { }), new object[] { });
}

Once it has this information, the parser is able to CodeGen something like this:

CallbackParameter1.ClientType = new IntegerClientType();

which is exactly what you would write if you were writing the page procedurally.

6 Comments

  • using System;
    using System.Data;
    using System.ComponentModel;
    using System.ComponentModel.Design.Serialization;
    using System.Globalization;
    using System.Reflection;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;

    namespace OmaAvaruus {
    public class Oma : WebControl {
    private Toinen m_Testi;
    [TypeConverter(typeof(Toinen.Converter))]
    public Toinen Testi {
    get { return m_Testi; }
    set { m_Testi = value; }
    }
    }

    [TypeConverter(typeof(Toinen.Converter))]
    public class Toinen {
    private string m_Nimi;
    public Toinen(string nimi) {
    m_Nimi = nimi;
    }
    public Toinen() {
    }

    internal class Converter : TypeConverter {
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
    if (destinationType == typeof(InstanceDescriptor)) {
    return true;
    }
    // Always call the base to see if it can perform the conversion.
    return base.CanConvertTo(context, destinationType);
    }
    // This code performs the actual conversion from a Triangle to an InstanceDescriptor.
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
    if (destinationType == typeof(InstanceDescriptor)) {
    ConstructorInfo ci = typeof(Toinen).GetConstructor(new Type[]{typeof(string)});
    Toinen t = (Toinen)value;
    return new InstanceDescriptor(ci, new object[] { t.ToString() });
    }

    // Always call base, even if you can't convert.
    return base.ConvertTo(context, culture, value, destinationType);
    }

    public override bool CanConvertFrom(
    ITypeDescriptorContext context, Type sourceType) {
    if (sourceType == typeof(string))
    return true;

    return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context,
    System.Globalization.CultureInfo culture, object name) {
    if (name != null)
    if (name is string)
    return new Toinen((string)name);

    return base.ConvertFrom(context, culture, name);
    }
    }
    }

    }

  • Alternatively, debug it is using Sysinternals DebugView and lots of Debug.WriteLine() statements...

  • Thanks, this little insight helped me solve a tough problem in ASP.NET when I was using a TypeConverter.

    Regards,
    Brian

  • Excellent post! thanks

  • what about if property is of type object? In medium trust type converter fails just before convertto because instancedescriptor requires full trurst. Any comment on this? Thanks!

  • @Rusev: not sure what you're asking. Can you give precisions and maybe a repro. You can contact me at bleroy at you know where.

Comments have been disabled for this content.