Search code examples
asp.net-core-2.0typeconverter

ASP.NET Core type converter methods not called


I'm trying to configure ForwardedHeadersOptions which is part of Microsoft.AspNetCore.HttpOverrides. I've added options to my appsettings.json

"ForwardedHeadersOptions": {
        "ForwardedHeaders": 5,
        "ForwardLimit": 1,
        "KnownProxies": [
            "111.111.111.111"
        ]
    },

First two properties (ForwardedHeaders and ForwardLimit) are mapped properly, while KnownProxies is not. It is expected, because the type of KnownProxies is IList<IPAddress>. So, in order to map this property, I've created type converter:

public class IPAddressTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        return base.ConvertFrom(context, culture, value);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(IPAddress) || base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (value != null && value is string)
        {
            return IPAddress.Parse(value.ToString());
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }
}

Then, I've registered the IPAddressTypeConverter in the Startup.cs like this:

public void ConfigureServices(IServiceCollection services)
{
    TypeDescriptor.AddAttributes(typeof(ForwardedHeadersOptions), new TypeConverterAttribute(typeof(IPAddressTypeConverter)));
    services.Configure<ForwardedHeadersOptions>(Configuration.GetSection(nameof(ForwardedHeadersOptions)));
 }

But when I launch the app type converter methods are never called. Any idea what is wrong here?


Solution

  • When the configuration framework tries to convert a string in your appsettings file, it converts FROM string. Your TypeConverter is registered to handle a certain type (IPAddress) which it will convert to. But it doesn't need to test for this type.

    The [Can]ConvertTo-methods are for serializing, i.e. converting from the registered type to another type like string.

    So, your type converter should look something like:

    public class IPAddressTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string) || sourceType == typeof(String))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value != null && (value is string || value is String))
            {
                return IPAddress.Parse((string)value);
            }
            return base.ConvertFrom(context, culture, value);
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is IPAddress)
            {
                return ((IPAddress)value).ToString();
            }
    
            return base.ConvertTo(context, culture, value, destinationType);
        }
    

    Then, in Startup.cs, ConfigureServices(), with several lines for clarity and debugging breakpoint opportunities:

            TypeDescriptor.AddAttributes(typeof(IPAddress), new TypeConverterAttribute(typeof(IPAddressTypeConverter)));
    
            var forwardedHeadersOptions = new ForwardedHeadersOptions();
            var forwardedHeadersSection = Configuration.GetSection("ForwardedHeadersOptions");
            forwardedHeadersSection.Bind(forwardedHeadersOptions);
            services.Configure<ForwardedHeadersOptions>(o =>
            {
                o.ForwardedHeaders = ForwardedHeaders.All;
                o.ForwardLimit = forwardedHeadersOptions.ForwardLimit;
                foreach(var knownProxy in forwardedHeadersOptions.KnownProxies)
                {
                    o.KnownProxies.Add(knownProxy);
                }
                foreach(var knownNetwork in forwardedHeadersOptions.KnownNetworks)
                {
                    o.KnownNetworks.Add(knownNetwork);
                }
            });
    

    I tried making a similar type converter for KnownNetworks but it didn's stick, perhaps because of the typing (string / int). So I ended up creating a separate IPNetworkSettingClass where the Prefix-field was a string rather than an object of the IPAdress class. Then I could get the networks from appsettings using:

     var knownNetworks = Configuration.GetSection("ForwardedHeadersOptions").GetSection("KnownNetworks").Get<List<IPNetworkSetting>>();
    

    Finally I set the KnownNetworks programmatically in the bound ForwardedHeadersOptions-instance.

    Hope this helps