Search code examples
.net.net-corelocalizationglobalization

Localized query string parameters in ASP core


I'm having issues with a controller which gets as parameters in the query string a pair of coordinates.

My controller looks like:

[HttpGet("/location")]
public IActionResult GetForLocation(double lat, double lon)
{
// Do stuff
}

When clients send coordinates with different decimal separators (e.g. /location?lat=4,4&lon=45,5) the framework's parsed them to 44 and 455. I've used app.UseRequestLocalization() in my startup class and reviewing the current thread culture I get the correct values for the decimal separators, but the model bindings are wrong.


Solution

  • Minutes after asking this I found the answer, https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0#globalization-behavior-of-model-binding-route-data-and-query-strings

    In short Query string parameters and Paths are treated by default as Invariant Culture unless you replace the QueryStringValueProviderFactory and RouteValueProviderFactory with the following:

        public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
        {
            /// <inheritdoc />
            public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                var query = context.ActionContext.HttpContext.Request.Query;
    
                if (query != null && query.Count > 0)
                {
                    var valueProvider = new QueryStringValueProvider(
                        BindingSource.Query,
                        query,
                        CultureInfo.CurrentCulture);
    
                    context.ValueProviders.Add(valueProvider);
                }
    
                return Task.CompletedTask;
            }
        }
    
        public class CulturedRouteValueProviderFactory : IValueProviderFactory
        {
            /// <inheritdoc />
            public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                var valueProvider = new RouteValueProvider(
                    BindingSource.Path,
                    context.ActionContext.RouteData.Values,
                    CultureInfo.CurrentCulture);
    
                context.ValueProviders.Add(valueProvider);
    
                return Task.CompletedTask;
            }
        }