问题在于 QueryParams 对象不是蛇形案例
public async Task<IActionResult> Search (
[FromQuery] ProjectFilterDto filterParams,
[FromQuery(Name = "page_size")] int pageSize = 10,
[FromQuery(Name = "page_index")] int pageIndex = 0)
所有其他内容,FromBody 和 Response 均采用蛇形语法
启动;
options.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy(true, true)
};
有什么方法可以在不使用 FromQuery(Name="x") 的情况下使像 ProjectFilterDto 这样的查询参数成为蛇形案例吗?
首先创建一个扩展方法,将给定的字符串转换为其蛇形版本。我们在下一步中将需要这个方法。
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();
}
现在我们可以创建一个自定义值提供程序,使用我们上面定义的
ToSnakeCase()
扩展方法来查找蛇形查询参数。
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());
}
}
我们还必须为我们的价值提供者实现一个工厂:
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;
}
}
剩下要做的唯一一件事就是在 Startup 类的
ConfigureServices
方法中注册值提供者。
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options =>
{
options.ValueProviderFactories.Add(new SnakeCaseQueryValueProviderFactory());
})
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
});
}
希望这有帮助!
我在这里写了一篇关于这一切的博客文章:http://www.sorting.se/enable-snake-cased-query-parameters-in-your-restfull-asp-net-core-web-api/
只需将参数命名为蛇形案例,例如:
public ActionResult MyMethod([FromQuery] string page_size){
...
}
与使用蛇形外壳或分页器创建过滤器的方法相同:
public class PaginatedResponse<TItem>
{
public int Page { get; set; }
public int PageSize { get; set; }
public long TotalCount { get; set; }
public List<TItem> Results { get; set; }
}
public class Paginator
{
[Range(1, int.MaxValue)]
public int Page { get; set; } = 1;
[Range(1, 100)]
public virtual int Page_Size { get; set; } = 20;
public async Task<PaginatedResponse<TItem>> PaginateAsync<TItem>(IQueryable<TItem> queryable)
{
var totalCount = await queryable.LongCountAsync();
var results = await queryable.Skip((Page - 1) * Page_Size).Take(Page_Size).ToListAsync();
return new PaginatedResponse<TItem>()
{
Page = Page,
PageSize = Page_Size,
TotalCount = totalCount,
Results = results,
};
}
}
然后你可以像这样使用它:
public async Task<ICollection<MyModel>> GetAllAsync([FromQuery] Paginator paginator){
...
return await paginator.PaginateAsync(myQueriable)
}