Reputation: 3841
I have a simple TypeConverter
to convert a comma-separated string into an IEnumerable<T>
to shorten the url when calling my API-endpoints.
Therefore I have an request-object which is set on client and passed to the server. So, on server it's the same object.
This is what the type-converter looks like:
public class EnumerableTypeConverter : TypeConverter
{
private readonly Type _innerType;
private readonly MethodInfo _enumerableCastMethodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast));
public IEnumerableTypeConverter(Type type)
{
// check if the type is somewhat ienumerable-like
if (type.BaseType != null && type.BaseType.IsGenericType && typeof(IEnumerable<>).MakeGenericType(type.BaseType.GetGenericArguments()[0]).IsAssignableFrom(type.BaseType) && type.BaseType.GetGenericArguments().Length == 1)
{
_innerType = type.BaseType.GetGenericArguments()[0];
}
else
{
throw new ArgumentException("Incompatible type", nameof(type));
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => (destinationType.BaseType != null && typeof(IEnumerable<>).MakeGenericType(destinationType.BaseType.GetGenericArguments()[0]).IsAssignableFrom(destinationType.BaseType)) || base.CanConvertFrom(context, destinationType);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var source = value as string;
if (source == null)
return base.ConvertFrom(context, culture, value);
var temp = source.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => TypeDescriptor.GetConverter(_innerType).ConvertFromInvariantString(s));
var castMethod = _enumerableCastEmthMethodInfo.MakeGenericMethod(_innerType);
var casted = castMethod.Invoke(null, new object[] {temp});
return casted;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var s = value as IEnumerable<string>;
return s != null ? string.Join(",", s) : base.ConvertFrom(context, culture, value);
}
}
A sample request could look something like
public class MyRequest
{
[TypeConverter(typeof(EnumerableTypeConverter))]
public IEnumerable<string> Names {get;set;}
[TypeConverter(typeof(EnumerableTypeConverter))]
public IEnumerable<Guid> Ids {get;set;}
}
I've decorated the properties with [TypeConverter(typeof(EnumerableTypeConverter))]
-attribute. However, the ConvertFrom
-method is never called.
When I add the attribute on a class instead of a property it is working.
Upvotes: 4
Views: 2393
Reputation: 630
You can't attach a TypeConverter to a view model property in ASP.NET Core as of now. Here is the issue tracking this problem https://github.com/dotnet/aspnetcore/issues/8857. I believe only XAML frameworks support type converters on properties.
Also note that since ASP.NET Core moved JSON serialization from Newtonsoft.JSON to System.Text.Json TypeConverter is not supported on json payloads (e.g. POST requests) even if it's attached to a class. It only works when a value to be bound is provided as a simple string by the model binding. You can use custom JsonConverter to work around this.
I know this is late answer, but the problem still exists after 5 years.
Upvotes: 0
Reputation: 11
For those who are still interested in possible workaround look at this thread. Issue could be because of converter being defined in an assembly different than your app domain.
Upvotes: 1
Reputation: 16232
the TypeConverterAttribute
(used in [TypeConverter]
) is different from the base class TypeConverter
.
For the EnumerableTypeConverter
to be invoked, it needs to be instantiated, and it can only be automagically instantiated if a parameterless constructor exists.
In your case, you might want to have 2 converters, a EnumerableStringConverter and a EnumerableGuidConverter, or use a generic one:
public class EnumerableTypeConverter<T> : TypeConverter
{
}
and you can decorate your properties like this:
public class MyRequest
{
[TypeConverter(typeof(EnumerableTypeConverter<string>))]
public IEnumerable<string> Names {get;set;}
[TypeConverter(typeof(EnumerableTypeConverter<Guid>))]
public IEnumerable<Guid> Ids {get;set;}
}
Upvotes: 0