Carlo Luisito
Carlo Luisito

Reputation: 229

API .Net - Validate query parameters

I have a scenario where it ignores the value passed on my query parameter if the value is a string.

Sample Route

v1/data?amount=foo

Here's the sample code

[HttpGet]
public async Task<IActionResult> GetData([FromQuery]decimal? amount)
{
}

So what I have tried so far is, I add a JsonConverter

public class DecimalConverter : JsonConverter
{
    public DecimalConverter()
    {
    }

    public override bool CanRead
    {
        get
        {
            return false;
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        value = CleanupDecimal(value);

        if (DecimalConverter.IsWholeValue(value))
        {
            writer.WriteRawValue(JsonConvert.ToString(Convert.ToInt64(value)));
        }
        else
        {
            writer.WriteRawValue(JsonConvert.ToString(value));
        }
    }

    private static object CleanupDecimal(object value)
    {
        if (value is null) return value;

        if (value is decimal || value is decimal?)
        {
            value = decimal.Parse($"{value:G0}");
        }
        else if (value is float || value is float?)
        {
            value = float.Parse($"{value:G0}");
        }
        else if (value is double || value is double?)
        {
            value = double.Parse($"{value:G0}");
        }

        return value;
    }

    private static bool IsWholeValue(object value)
    {
        if (value is decimal decimalValue)
        {
            int precision = (decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF;
            return precision == 0;
        }
        else if (value is float floatValue)
        {
            return floatValue == Math.Truncate(floatValue);
        }
        else if (value is double doubleValue)
        {
            return doubleValue == Math.Truncate(doubleValue);
        }

        return false;
    }
}

So based on my observation, this only works on [FromBody] parameters.

Is there a way to validate query parameters without changing the datatype of it? I know it is possible to just change the datatype from decimal to string and validate it if it is a valid number.

Updated:

I want to have a response like

{
    "message": "The given data was invalid.",
    "errors": {
       "amount": [
          "The amount must be a number."
        ]
    }
}

Upvotes: 1

Views: 1237

Answers (1)

Yiyi You
Yiyi You

Reputation: 18139

You can use custom model binding:

  [HttpGet]
    public async Task<IActionResult> GetData([ModelBinder(typeof(CustomBinder))]decimal? amount)
    {
    }

CustomBinder:

public class CustomBinder:IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
            //get amount data with the following code
            
            var data=bindingContext.ValueProvider.GetValue("amount").FirstValue;
            
            //put your logic code of DecimalConverter  here
        
            bindingContext.Result = ModelBindingResult.Success(data);
            return Task.CompletedTask;
        }
    }

Upvotes: 1

Related Questions