In my model class, I have this property:
[TypeConverter(typeof(CommaDecimalConverter))]
public decimal? Foo { get; set; }
My converter is implemented as follows:
public class CommaDecimalConverter : DecimalConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
var text = (string)value;
return decimal.Parse(text, new CultureInfo("cs-CZ"));
}
return base.ConvertFrom(context, culture, value);
}
}
However, a breakpoint inside CommaDecimalConverter
is never triggered, and the property gets filled with the default converter.
I want my decimal property to accept comma as a separator, without changing the model binder or adding a second property to convert from.
I got it working by implementing a model binder just for decimals. It changes model binding for every decimal, which is not as good as [TypeConverter(...)] would have been, but it works for my case.
First you need to register a new IModelBinderProvider implementation in your Program.cs
builder.Services.AddControllersWithViews(opts =>
opts.ModelBinderProviders.Insert(0, new CommaDecimalModelBinderProvider())
);
IModelBinderProvider implementation looks like this:
public class CommaDecimalModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(decimal) || context.Metadata.ModelType == typeof(decimal?))
{
return new BinderTypeModelBinder(typeof(CommaDecimalModelBinder));
}
return null;
}
}
Since it is registered first, it checks whether the property is a decimal. If it is not, default binding is used.
If it is a decimal, this IModelBinder implementation is used:
public class CommaDecimalModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelType != typeof(decimal) && bindingContext.ModelType != typeof(decimal?))
{
return Task.CompletedTask;
}
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
var value = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(value))
{
bindingContext.Result = ModelBindingResult.Success(null);
}
else
{
var decimalValue = decimal.Parse(value, new CultureInfo("cs-CZ"));
bindingContext.Result = ModelBindingResult.Success(decimalValue);
}
return Task.CompletedTask;
}
}
In it, you specify how you want your decimal to be converted.