0

How to create a model binder for decimal numbers which will throw exception if users are sending it in a wrong format?

I need something like this:

2 = OK
2.123 = OK
2,123 = throw invalid format exception
Robert
  • 3,353
  • 4
  • 32
  • 50
  • 1
    Exception will be thrown any way if binding to `decimal` type will not succeed. – Fabio Jul 08 '16 at 07:22
  • yeah, this would make sense the other way around but you want the default. Which it should just do. Have you tested this? – Liam Jul 08 '16 at 07:24
  • 1
    @fabio this is not true. I just tested it. It is culture dependent. I have sent 10.5 to my api, and it binded well, but when i sent 10,5, it did not threw any exception. It just set my decimal value in my model as 0, which is default decimal value. – Robert Jul 08 '16 at 07:26
  • I think will be better if you show a request data you send and model you bounded to – Fabio Jul 08 '16 at 07:30
  • If binding fail your model will be passed to controller's method as `null` – Fabio Jul 08 '16 at 08:49

1 Answers1

1

Look at this article http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx/

You can just use standard binder with simple check like this

public class DecimalModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName);
        ModelState modelState = new ModelState { Value = valueResult };
        object actualValue = null;

        if (valueResult.AttemptedValue.Contains(","))
        {
            throw new Exception("Some exception");
        }
        actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
            CultureInfo.CurrentCulture);


        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
        bindingContext.Model = actualValue;
        return true;
    }
}

EDIT: According to @Liam suggestion you have to add this binder to your configuration first

ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());

Above code throws exception in the case of bad decimal separater, but you should use model validation to detect that kind of errors. It is more flexible way.

public class DecimalModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName);
        ModelState modelState = new ModelState { Value = valueResult };
        object actualValue = null;
        try
        {
            if (valueResult.AttemptedValue.Contains(","))
            {
                throw new Exception("Some exception");
            }
            actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
                CultureInfo.CurrentCulture);
        }
        catch (FormatException e)
        {
            modelState.Errors.Add(e);
            return false;
        }

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
        bindingContext.Model = actualValue;
        return true;
    }
}

you don't throw exception but just add validation error. You can check it in your controller later

if (ModelState.IsValid)
{
}
suvroc
  • 3,058
  • 1
  • 15
  • 29
  • You need to add about how to actually bind this too, i.e. `ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());` – Liam Jul 08 '16 at 07:42
  • For WebAPI2 the `BindModel` method returns `bool`. We don't use MVC here – suvroc Jul 08 '16 at 07:42
  • yes, sorry, I did notice this. Hence my deleted comment. I didn't realise microsoft had two interfaces with the same name but different signatures.....Seems like a bad idea to me. Interestingly that Phill Haacked article also seems to use the wrong one... – Liam Jul 08 '16 at 07:44
  • It was probably changed between some older versions of WebAPI. Current version have many differences from MVC – suvroc Jul 08 '16 at 07:48
  • `ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());` requires IModelBinder Interface from `System.Web.Mvc`, but one used here is from `System.Web.Http.ModelBinding` – Robert Jul 08 '16 at 10:13