Felipe Oriani
Felipe Oriani

Reputation: 38638

Custom Model Binder for Decimal in Asp.Net Web API

I have a web api application using asp.net mvc web api that recieve some decimal numbers in viewmodels. I would like to create a custom model binder for decimal type and get it working for all decimals numbers. I have a viewModel like this:

public class ViewModel
{
   public decimal Factor { get; set; }
   // other properties
}

And the front-end application can send a json with a invalid decimal number like: 457945789654987654897654987.79746579651326549876541326879854

I would like to response with a 400 - Bad Request error and a custom message. I tried create a custom model binder implementing the System.Web.Http.ModelBinding.IModelBinder and registring on the global.asax but does not work. I would like to get it working for all decimals in my code, look what I tried:

public class DecimalValidatorModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
        {
            if (bindingContext.ModelType == typeof(decimal))
            {
                decimal result;
                if (!decimal.TryParse(input.AttemptedValue, NumberStyles.Number, Thread.CurrentThread.CurrentCulture, out result))
                {
                    actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, ErrorHelper.GetInternalErrorList("Invalid decimal number"));
                    return false;
                }
            }
        }

        return true; //base.BindModel(controllerContext, bindingContext);
    }
}

Adding on the Application_Start:

GlobalConfiguration.Configuration.BindParameter(typeof(decimal), new DecimalValidatorModelBinder());

What can I do? Thank you.

Upvotes: 4

Views: 4451

Answers (2)

pajics
pajics

Reputation: 3058

for JSON you can create JsonConverter (in case you are sticking by default with JSON.NET:

public class DoubleConverter : JsonConverter
{
    public override bool CanWrite
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(double) || objectType == typeof(double?));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
        {
            return token.ToObject<double>();
        }
        if (token.Type == JTokenType.String)
        {
            // customize this to suit your needs
            var wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
            var alternateSeparator = wantedSeperator == "," ? "." : ",";
            double actualValue;
            if (double.TryParse(token.ToString().Replace(alternateSeparator, wantedSeperator), NumberStyles.Any,
                CultureInfo.CurrentCulture, out actualValue))
            {
                return actualValue;
            }
            else
            {
                throw new JsonSerializationException("Unexpected token value: " + token.ToString());
            }

        }
        if (token.Type == JTokenType.Null && objectType == typeof(double?))
        {
            return null;
        }
        if (token.Type == JTokenType.Boolean)
        {
            return token.ToObject<bool>() ? 1 : 0;
        }
        throw new JsonSerializationException("Unexpected token type: " + token.Type.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanWrite is false. The type will skip the converter.");
    }
}

Upvotes: 0

Mike Wasson
Mike Wasson

Reputation: 6622

By default Web API reads a complex type from the request body using a media-type formatter. So it doesn't go through a model binder in this case.

Upvotes: 5

Related Questions