Tag Archives: .NET Core

“The value ‘NaN’ is not valid.” – The ModelBinder way

In my previous post I described how to solve “The value ‘NaN’ is not valid.” when an int parameter contains NaN. My colleauge then convinced me to solve this with a custom ModelBinder instead. So, here is the solution.

First we create a custom modelbinder.

public class IntNanModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (string.IsNullOrWhiteSpace(valueProviderResult.FirstValue) || valueProviderResult.FirstValue == "NaN") 
            {
                bindingContext.Result = ModelBindingResult.Failed();
            } else if (Int32.TryParse(valueProviderResult.FirstValue, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var intValue))
            {
                bindingContext.Result = ModelBindingResult.Success(intValue);
            } else
            {
                throw new Exception($"Parameter '{bindingContext.ModelName}' with value '{valueProviderResult.FirstValue}' is not an int.");
            }

            return Task.CompletedTask;
        }
    }

This can now be used directly in your endpoint by

[HttpGet]
        [Route("list")]
        public IActionResult List([ModelBinder(BinderType = typeof(IntNanModelBinder))] int skip = 0, int take = 20)
        {
            // Code
        }

But we want to use this on all our endpoints where we have int as parameters. So we need to create a ModelBinderProvider and add it in our startup.

public class IntNanModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.Metadata.ModelType == typeof(int))
            {
                return new BinderTypeModelBinder(typeof(IntNanModelBinder));
            }
            return null;
        }
    }

And in ConfigureServices in Startup.cs

services.AddMvc(c =>
            {
                c.ModelBinderProviders.Insert(0, new IntNanModelBinderProvider());
            });

And happy days, it works magically.

“The value ‘NaN’ is not valid.”

So, we’re in the progress if migrating a big project from .NET 4.5 to .NET 5 (.NET Core). In this process we’ve stumbled upon a few problems that could be solved by changing frontend and apps, but that would break older versions of them. So…

EDIT: I’ve added a new post with another solution which probably is a better solution. Check it out.

Problem

One of the problems was when frontend or app provided the value NaN in a querystring parameter. In the old solution this was ignored by the WebApi and the standard value from the endpoint was taken. But when testing around in the migrated project, the request failed with the error “The value ‘NaN’ is not valid.”.

Request: /list?skip=NaN&take=1
Endpoint:

[HttpGet]
        [Route("list")]
        public IActionResult List(int skip = 0, int take = 20)
        {
            // code
        }

Response:

Solution

Our solution was to preprocess the incoming Url and remove all NaN. This was done in Startup.cs in the Configure-method.

app.Use(async (context, next) =>
            {
                if (context.Request.QueryString.Value.Contains("=NaN"))
                {
                    var qs = QueryHelpers.ParseQuery(context.Request.QueryString.Value);
                    context.Request.QueryString = QueryString.Create(qs.Where(x => x.Value != "NaN"));
                }
                await next();
            });

Heads up

If you have endpoints that have doubles/floats etc that can be NaN you could probably solve this with a custom Modelbinder instead.