13

I have Amount stored in the database as decimal. I want to show that value on UI with thousand separator. I can add [DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)] attribute on amount property and that would display number with thousand separator however when i POST the value back to server, the MVC model binding would not work because of commas.

I have created a custom type converter that converts from decimal to string and then string to decimal

public class NumberConverter : TypeConverter
{
    public override bool CanConvertFrom(
        ITypeDescriptorContext context,
        Type sourceType)
    {
        if (sourceType == typeof(decimal))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context,
        CultureInfo culture, object value)
    {
        if (value is decimal)
        {
            return string.Format("{0:N2}", value);
        }
        return base.ConvertFrom(context, culture, value);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context,
        Type destinationType)
    {
        if (destinationType == typeof(decimal))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }
    public override object ConvertTo(ITypeDescriptorContext context,
        CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(decimal) && value is string)
        {
            return Scrub(value.ToString());
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

    private decimal Scrub(string modelValue)
    {
        NumberStyles _currencyStyle = NumberStyles.Currency;
        CultureInfo _culture = new CultureInfo("en-US");

        var modelDecimal = 0M;
        decimal.TryParse(
           modelValue,
           _currencyStyle,
           _culture,
           out modelDecimal
       );

        return modelDecimal;
    }
}

and then i applied it on one of the model property. Note that model may have other decimal properties which may not required this conversion.

public class MyModel
{

    [TypeConverter(typeof(NumberConverter))]
    [Display(Name = "Enter Amount")]
    public decimal Amount { get; set;}

    public string Name { get; set; }
}

Index.cshtml

<form asp-action="submit" asp-controller="home">
    @Html.EditorFor(m => m.Amount)
    <button type="submit" class="btn btn-primary">Save</button>
</form>

However the converter code never gets fired. When i put break point in NumberConverter none of the break point hit. Do i need to register type converter anywhere? I am using asp.net core.

Jonas Marty
  • 348
  • 3
  • 13
LP13
  • 30,567
  • 53
  • 217
  • 400
  • I noticed that `TypeConverter` works with `Json.net` serialization but not with `System.Text.Json`, which is default in ASP.NET Core. We use a custom `Date` struct as a field in data contract and expect to receive it in the body of the POST method. Default .NET Core serializer failed to bind the contents of the request, so the data contract class was `null`. Switching to Json.net solved the problem. – Krrkrkrkr Feb 19 '20 at 10:01

1 Answers1

9

Based on my observations asp.net core doesn't take into account properties decorated by TypeConverters.

So it only supports TypeConverters that are decorating actual type class declaration.

Works

[TypeConverter(typeof(MyModelStringTypeConverter))]
public class MyModel
{

}

Doesn't work

public class OtherModel
{
    [TypeConverter(typeof(MyModelStringTypeConverter))]
    public MyModel MyModel { get;set; }
}
bot_insane
  • 2,545
  • 18
  • 40
  • 3
    I hit the same issue - inside model I have collection/set of values which I want to parse from string - so far I came up with writing simple converter and "registering" it from `Startup` this way: `TypeDescriptor.AddAttributes( typeof(IReadOnlyCollection),new TypeConverterAttribute( typeof(FooTypeConverter)));` ... but I don't quite like this approach :( – Zdeněk Aug 23 '18 at 12:25
  • @Zdeněk do you know if there is a way to register (at Startup) a class TypeConverter instead of having to decorate that class with the TypeConverter attribute? – diegosasw Jul 20 '22 at 08:10
  • @diegosasw Hi, unfortunately can't help here. I haven't used [`TypeDescriptor`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.typedescriptor.addattributes?view=net-6.0#system-componentmodel-typedescriptor-addattributes(system-type-system-attribute())) for years now. These days, I would look towards `ActionFilterAttribute` or `ValidationAttribute` based on scenario, or simply treat DTOs as stupid objects and then do proper mapping into domain object later "manually". – Zdeněk Jul 21 '22 at 19:53