asp.net-core-webapi

SnakeCase QueryParams .Net Core


The problem is that QueryParams Objects are not snake case

        public async Task<IActionResult> Search (
        [FromQuery] ProjectFilterDto filterParams,
        [FromQuery(Name = "page_size")] int pageSize = 10,
        [FromQuery(Name = "page_index")] int pageIndex = 0)

enter image description here

All other things, FromBody and Response are in snake case syntax

StartUp;

options.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
  NamingStrategy = new SnakeCaseNamingStrategy(true, true)
};

is there any way to make query params like ProjectFilterDto to be snake case without using FromQuery(Name="x")?


Solution

  • Start out by creating an extension method that converts a given string to a snake cased version of it. We will need this method in the next step.

    public static string ToSnakeCase(this string s)
    {
        if (string.IsNullOrWhiteSpace(s))
        {
            return s;
        }
    
        s = s.Trim();
    
        var length = s.Length;
        var addedByLower = false;
        var stringBuilder = new StringBuilder();
    
        for (var i = 0; i < length; i++)
        {
            var currentChar = s[i];
    
            if (char.IsWhiteSpace(currentChar))
            {
                continue;
            }
    
            if (currentChar.Equals('_'))
            {
                stringBuilder.Append('_');
                continue;
            }
    
            bool isLastChar = i + 1 == length,
                    isFirstChar = i == 0,
                    nextIsUpper = false,
                    nextIsLower = false;
    
            if (!isLastChar)
            {
                nextIsUpper = char.IsUpper(s[i + 1]);                    
                nextIsLower = !nextIsUpper && !s[i + 1].Equals('_');
            }
    
            if (!char.IsUpper(currentChar))
            {
                stringBuilder.Append(char.ToLowerInvariant(currentChar));
    
                if (nextIsUpper)
                {
                    stringBuilder.Append('_');
                    addedByLower = true;
                }
    
                continue;
            }
    
            if (nextIsLower && !addedByLower && !isFirstChar)
            {
                stringBuilder.Append('_');
            }
    
            addedByLower = false;
    
            stringBuilder.Append(char.ToLowerInvariant(currentChar));
        }
    
        return stringBuilder.ToString();
    }
    

    Now we can create a custom value provider that looks for snake cased query parameters using the ToSnakeCase() extension method we defined above.

    public class SnakeCaseQueryValueProvider : QueryStringValueProvider, IValueProvider
    {
        public SnakeCaseQueryValueProvider(
            BindingSource bindingSource, 
            IQueryCollection values, 
            CultureInfo culture) 
            : base(bindingSource, values, culture)
        {
        }
    
        public override bool ContainsPrefix(string prefix)
        {
            return base.ContainsPrefix(prefix.ToSnakeCase());
        }
    
        public override ValueProviderResult GetValue(string key)
        {
            return base.GetValue(key.ToSnakeCase());
        }
    }
    

    We also have to implement a factory for our value provider:

    public class SnakeCaseQueryValueProviderFactory : IValueProviderFactory
    {
        public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            var valueProvider = new SnakeCaseQueryValueProvider(
                BindingSource.Query,
                context.ActionContext.HttpContext.Request.Query,
                CultureInfo.CurrentCulture);
    
            context.ValueProviders.Add(valueProvider);
    
            return Task.CompletedTask;
        }
    }
    

    The only thing left to do is to register the value provider in the ConfigureServices method in the Startup class.

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddMvc(options =>
            {
                options.ValueProviderFactories.Add(new SnakeCaseQueryValueProviderFactory());
            }) 
            .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new SnakeCaseNamingStrategy()
            });
    }
    

    Hope this helps!

    I've written a blog post about it all here: http://www.sorting.se/enable-snake-cased-query-parameters-in-your-restfull-asp-net-core-web-api/