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)
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")?
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/