Parse appsetting value from string to array of strings - c#

In app.config I have custom section with custom element.
<BOBConfigurationGroup>
<BOBConfigurationSection>
<emails test="test1#test.com, test2#test.com"></emails>
</BOBConfigurationSection>
</BOBConfigurationGroup>
For emails element I have custom type :
public class EmailAddressConfigurationElement : ConfigurationElement, IEmailConfigurationElement
{
[ConfigurationProperty("test")]
public string[] Test
{
get { return base["test"].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); }
set { base["test"] = value.JoinStrings(); }
}
}
But when I run my webApp, I get error :
The value of the property 'test' cannot be parsed. The error is: Unable to find a converter that supports conversion to/from string for the property 'test' of type 'String[]'.
Is there any solution to split string in getter?
I can get string value and then split it "manually" when I need array, but in some cases I can forget about it, so better to receive array from start.
JoinStrings - is my custom extension method
public static string JoinStrings(this IEnumerable<string> strings, string separator = ", ")
{
return string.Join(separator, strings.Where(s => !string.IsNullOrEmpty(s)));
}

You can add a TypeConverter to convert between string and string[]:
[TypeConverter(typeof(StringArrayConverter))]
[ConfigurationProperty("test")]
public string[] Test
{
get { return (string[])base["test"]; }
set { base["test"] = value; }
}
public class StringArrayConverter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string[]);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return ((string)value).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return value.JoinStrings();
}
}

Consider an approach like:
[ConfigurationProperty("test")]
public string Test
{
get { return (string) base["test"]; }
set { base["test"] = value; }
}
public string[] TestSplit
{
get { return Test.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); }
}
Where TestSplit is the property you use within your code.

Related

.Net Core Enumeration problem using FromRoute and EnumMember

I'm having a somewhat similar issue to https://github.com/aspnet/AspNetCore/issues/4008 that I'd like help with it at all possible.
For the url http://localhost:51049/api/values/proof_of_address the route should evaluate proof_of_address as ProofOfAddress in the enum below.
I've attempted to follow the example in the link above but regardless of what I put in the url I get the first value in the enumeration. My code is as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddJsonOptions(opt =>
{
opt.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
opt.SerializerSettings.Converters.Add(new StringEnumConverter());
});
}
[HttpGet("{documenttype}")]
public ActionResult<string> Get([FromRoute] DocumentType documenttype)
{
return documenttype.ToString();
}
public class CustomEnumConverter<T> : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var s = value as string;
if (string.IsNullOrEmpty(s))
{
return null;
}
return JsonConvert.DeserializeObject<T>(#"""" + value.ToString() + #"""");
}
}
[TypeConverter(typeof(CustomEnumConverter<DocumentType>))]
public enum DocumentType
{
[EnumMember(Value = "passport")]
Passport,
[EnumMember(Value = "proof_of_address")]
ProofOfAddress
}
Any help would be appreciated, thanks.
You need to override CanConvertFrom to return true
public class CustomEnumConverter<T> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var s = value as string;
if (string.IsNullOrEmpty(s))
{
return null;
}
return JsonConvert.DeserializeObject<T>(#"""" + value.ToString() + #"""");
}
}

How to display an enum with duplicate values in a .NET PropertyGrid?

I have an enum that contains duplicate values. For example:
public enum DataVals : byte
{
C1_Route1to2 = 1,
C4_Route3to5 = 1,
C6_Route1to2 = 2,
C7_Route3to5 = 2
}
The values C# are just internal values within my application. Depending on which route is selected by the user, route is another property in the class, a 1 could mean use C1 or C4. The problem is I am using a PropertyGrid in my Winform and this property displays the duplicate values as having the same name. So C1_Route1to2 shows up twice instead of both C1_Route1to2 and C4_Route3to5.
How do I tell the PropertyGrid to display each unique name, rather than duplicating the values?
Although I agree with Gabriel, you could achieve what you need using the TypeConverter as I mentioned before. You might need to change the editor to allow selecting more than one enum if it has the FlagsAttribute...
Place the attribute:
[TypeConverter(typeof(ComplexEnumConverter ))]
public enum DataVals : byte
{
C1_Route1to2 = 1,
C4_Route3to5 = 1,
C6_Route1to2 = 2,
C7_Route3to5 = 2
}
And here is the converter:
public class ComplexEnumConverter : EnumConverter
{
public bool IsFlagged { get; }
public string[] EnumValues { get; }
public ComplexEnumConverter(Type type)
: base(type)
{
IsFlagged = TypeDescriptor.GetAttributes(type).OfType<FlagsAttribute>().Any();
EnumValues = Enum.GetNames(type);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var str = value as string;
if (!string.IsNullOrWhiteSpace(str))
{
var values = str.Split(',').Select(s => s.Trim());
var enumValue = Enum.Parse(EnumType, values.First());
if (IsFlagged)
{
var temp = (int)enumValue;
foreach (var item in values.Skip(1))
{
temp |= (int)Enum.Parse(EnumType, item);
}
enumValue = temp;
}
return enumValue;
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var type = value?.GetType();
if (type == EnumType)
{
var list = new List<string>();
int k = (int)value;
foreach (var item in Enum.GetNames(type))
{
var current = (int)Enum.Parse(type, item);
if ((k & current) == current)
{
list.Add(item);
}
}
return list.Aggregate((c, n) => $"{c}, {n}");
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return context.PropertyDescriptor.PropertyType.IsEnum;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return context.PropertyDescriptor.PropertyType.IsEnum;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(EnumValues);
}
}

How does DefaultValueAttriubte work for Encoding?

I've a property decorated with DefaultValueAttribute.
The code looks like this:
[DefaultValue(typeof(Encoding), "utf-8")]
public Encoding Encoding { get; set; }
There's a Reset-Method that restores all default values of all properties:
public void Reset()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(ILoggedChannelValueFileExportInfo)).Cast<PropertyDescriptor>())
{
property.ResetValue(this);
}
}
This works perfect for all standard properties including a property with ReturnType CultureInfo.
I tried to specify utf-8, utf8 and both in upper case but nothing works.
What do I have to specify to make it work?
I'm able to do this via reflection but I hope that there's a way to use the TypeDescriptor.
EDIT:
Due to answer of Hans Passant I wrote a TypeConverter. That's the whole code:
public class Foo
{
[TypeConverter(typeof(EncodingTypeConverter))]
[DefaultValue(typeof(Encoding), "UTF-8")]
public Encoding Encoding { get; set; }
public void Reset()
{
foreach (var property in TypeDescriptor.GetProperties(this).Cast<PropertyDescriptor>())
{
property.ResetValue(this);
}
}
}
public class EncodingTypeConverter : 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 value is string ? Encoding.GetEncoding((string)value) : base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return destinationType == typeof(string) ? value.ToString() : base.ConvertTo(context, culture, value, destinationType);
}
}
After creating an instance and call Reset Encoding is still null and no method is called from EncodingTypeConverter. What should I do?`
There is no UI involved - no property grid. The only thing is the TypeDescriptor that should reset the value.
EDIT:
It is nearly impossible to write my own TypeConverter-call because the DefaultValueAttribute doesn't store the specified string value. It hurts. Here's my implementation that doesn't work. Maybe there's someone who can use it to create a solution:
public virtual void ResetValues()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(TAttributedType)).Cast<PropertyDescriptor>())
{
if (property.CanResetValue(this))
{
property.ResetValue(this);
continue;
}
var defaultValueAttribute = (DefaultValueAttribute)property.Attributes[typeof(DefaultValueAttribute)];
var typeConverterAttribute = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
if (defaultValueAttribute == null || !(defaultValueAttribute.Value is string) ||
typeConverterAttribute == null || string.IsNullOrWhiteSpace(typeConverterAttribute.ConverterTypeName))
{
continue;
}
var typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
if (typeConverterType == null)
{
continue;
}
var typeConverter = (TypeConverter)Activator.CreateInstance(typeConverterType);
if (typeConverter.CanConvertFrom(typeof(string)))
{
var propertyValue = typeConverter.ConvertFrom(defaultValueAttribute.Value);
if (propertyValue != null)
{
property.SetValue(this, propertyValue);
}
}
}
}
Adding a TypeConverterAttribute and defining a DefaultValue(typeof(Encoding), "UTF-8") will not work. The constructor for DefaultValue tries to find a TypeConverter from the TypeDescriptor that doesn't use the TypeConverterAttribute.
Here is an implementation of the ResetValues-method that uses a string-value in DefaultValueAttributeand the TypeConverterAttribute to load a default value.
public virtual void ResetValues()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(TAttributedType)).Cast<PropertyDescriptor>())
{
var defaultValueAttribute = (DefaultValueAttribute)property.Attributes[typeof(DefaultValueAttribute)];
var typeConverterAttribute = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
if (defaultValueAttribute != null && defaultValueAttribute.Value is string &&
typeConverterAttribute != null && !string.IsNullOrWhiteSpace(typeConverterAttribute.ConverterTypeName))
{
var typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
if (typeConverterType != null)
{
var typeConverter = (TypeConverter)Activator.CreateInstance(typeConverterType);
if (typeConverter.CanConvertFrom(typeof(string)))
{
var propertyValue = typeConverter.ConvertFrom(defaultValueAttribute.Value);
if (propertyValue != null)
{
property.SetValue(this, propertyValue);
continue;
}
}
}
}
if (property.CanResetValue(this))
{
property.ResetValue(this);
}
}
}
The used TypeConverter is:
public class EncodingTypeConverter : 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 value is string ? Encoding.GetEncoding((string)value) : base.ConvertFrom(context, culture, value);
}
}
And the attibuted property is:
[TypeConverter(typeof(EncodingTypeConverter))]
[DefaultValue("UTF-8")]
public Encoding Encoding { get; set; }
Note that the ResetValues-method first tests for a TypeConverter and a specified DefaultValue. That's needed because the PropertyDescriptor.CanResetValue() returns true if a string is specified as default value. This is not convertable to Encoding.

TypeDescriptor.GetConverter(typeof(string)) cannot convert to my custom type

I'm using some third party code which uses TypeConverters to "cast" objects to types specified as generic parameters.
The 3rd party code gets the string type converter, and expects to do all conversions through that e.g.
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
I've written a custom type, and type converter for it (and registered it with the TypeDescriptor attribute) but it's not getting used by the 3rd party code, which fails on the call to typeConverter.CanConvertTo(MyCustomType)
Until today I'd only encountered TypeConverters in the abstract, I've seen mentions of them but never built or used one.
Has anyone any idea what I'm doing wrong here?
My - cut down - code
using System;
using System.ComponentModel;
namespace IoNoddy
{
[TypeConverter(typeof(TypeConverterForMyCustomType))]
public class MyCustomType
{
public Guid Guid { get; private set; }
public MyCustomType(Guid guid)
{
Guid = guid;
}
public static MyCustomType Parse(string value)
{
return new MyCustomType(Guid.Parse(value));
}
}
public class TypeConverterForMyCustomType
: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string strValue;
if ((strValue = value as string) != null)
try
{
return MyCustomType.Parse(strValue);
}
catch (FormatException ex)
{
throw new FormatException(string.Format("ConvertInvalidPrimitive: Could not convert {0} to MyCustomType", value), ex);
}
return base.ConvertFrom(context, culture, value);
}
}
}
static void Main(string[] args)
{
// Analogous to what 3rd party code is doing:
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
// writes "Am I convertible? false"
Console.WriteLine("Am I convertible? {0}", typeConverter.CanConvertTo(typeof(MyCustomType)));
}
You check CanConvertTo so add to yours converter:
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(MyCustomType)) || base.CanConvertTo(context, destinationType);
}
to some where:
public static void Register<T, TC>() where TC : TypeConverter
{
Attribute[] attr = new Attribute[1];
TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
attr[0] = vConv;
TypeDescriptor.AddAttributes(typeof(T), attr);
}
and to main:
Register<string, TypeConverterForMyCustomType>();
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
yours sample shuld work after that.

How can I use TypeConverters with a ConfigurationSection?

So I've got a ConfigurationSection/ConfigurationElementCollection that has a configuration like this:
<mimeFormats>
<add mimeFormat="text/html" />
</mimeFormats>
And here is how I handle the mimeFormats:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
And obviously I need a TypeConverter that actually does something (instead of this empty shell):
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
throw new NotImplementedException();
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
throw new NotImplementedException();
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
throw new NotImplementedException();
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
How do I set up a TypeConverter that will allow type conversion from/to string? I've tried using the MSDN examples but I keep getting error message:
TypeConverter cannot convert from System.String.
Essentially, how can it be set up so that it will just work with whatever ConfigurationSection is trying to do?
You can put TypeConverterAttribute on the property to tell the serializer how to handle it.
[TypeConverter(typeof(MimeFormatConverter))]
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
Try this:
TestSection.cs
public class TestSection : ConfigurationSection
{
private static readonly ConfigurationProperty sFooProperty = new ConfigurationProperty("Foo",
typeof(Foo),
null,
new FooTypeConverter(),
null,
ConfigurationPropertyOptions.None);
public static readonly ConfigurationPropertyCollection sProperties = new ConfigurationPropertyCollection();
static TestSection()
{
sProperties.Add(sFooProperty);
}
public Foo Foo
{
get { return (Foo)this[sFooProperty]; }
set { this[sFooProperty] = value; }
}
protected override ConfigurationPropertyCollection Properties
{
get { return sProperties; }
}
}
Foo.cs
public class Foo
{
public string First { get; set; }
public string Second { get; set; }
public override string ToString()
{
return First + ',' + Second;
}
}
FooTypeConverter.cs
public class FooTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string val = value as string;
if (val != null)
{
string[] parts = val.Split(',');
if (parts.Length != 2)
{
// Throw an exception
}
return new Foo { First = parts[0], Second = parts[1] };
}
return null;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
Foo val = value as Foo;
if (val != null)
return val.ToString();
return null;
}
}
I figured it out. Here is the solution:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
_properties = new ConfigurationPropertyCollection {
_mimeFormat, _enabled
};
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
/*******************************************/
[TypeConverter(typeof(MimeFormatConverter))]
/*******************************************/
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new MimeFormat((string)value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var val = (MimeFormat)value;
return val.Type + "/" + val.SubType;
}
}
From this point, you have to create the convert sections within the ConvertTo and ConvertFrom methods
public override object ConvertFrom( ITypeDescriptorContext context, CultureInfo culture, object value ) {
if ( value == null )
return null;
try {
if ( value is string ) {
string s = (string)value;
// here is where you look at the string to figure out the MimeFormat
// like so....
return new MimeFormat( s );
}
throw new NotSupportedException( NotSupportedException( value.GetType(), typeof(MimeFormat) );
}
public override object ConvertTo( ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType ) {
if ( value == null )
return null;
MimeFormat p = (MimeFormat)value;
if ( destinationType == typeof( String ) )
return p.ToString();
throw new NotSupportedException( NotSupportedException( typeof(MimeFormat), destinationType ) );
}
EDITED
You also need to override the CanConvert functions as well.
public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType ) {
if ( sourceType == typeof( string ) )
return true;
return false;
}
public override bool CanConvertTo( ITypeDescriptorContext context, Type destinationType ) {
if ( destinationType == typeof( string ) )
return true;
return false;
}

Categories