我目前正在实现RESTFUL API,该API提供端点以与数据库接口。
我想在我的API中实现过滤,但是我需要提供一个端点,该端点可以提供一种使用表的所有列对表应用过滤的方法。
我发现了一些模式,例如:
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2 ... param N是我的表列和值。
我还发现了另一个模式,该模式包括发送代表查询的JSON对象。
要过滤某个字段,只需将该字段及其值添加到查询中即可:
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
我正在寻找实现这一目标的最佳实践。
我将EF Core与ASP.NET Core结合使用来实现此目的。
答案 0 :(得分:1)
首先要谨慎过滤所有内容。可用的过滤器基于用户的需求,并根据需求进行扩展。更少的代码编写,更少的复杂性,更少的数据库端索引,更好的性能。
也就是说,我对具有大量过滤器的页面使用的方法是使用枚举服务器端,将我的标准字段传递回其枚举值(数字)以提供给请求。因此,过滤器字段将包含名称,默认值或适用的值,以及在将输入或选定的值传递回搜索时使用的枚举值。请求代码使用创建的过滤器创建一个JSON对象,并使用Base64将其发送到请求中:
即
{
p1: "Jake",
p2: "8"
}
查询字符串如下所示:
.../api/customer/search?filters=XHgde0023GRw....
在服务器端,我提取了Base64,然后将其解析为Dictionary<string,string>
,以供过滤器解析。例如,假设条件是使用姓名和年龄来搜索孩子:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
您可以使用字符串和常量来避免枚举解析,但是我发现枚举是可读的,并使发送的有效负载更加紧凑。上面是一个简化的示例,显然需要进行错误检查。适用于复杂过滤条件(例如上述年龄到出生日期)的实现代码更适合作为单独的方法,但是它应该给您一些想法。例如,您可以按姓名和/或年龄和/或父母的姓名搜索孩子。
答案 1 :(得分:0)
我已经发明并发现将一些过滤器组合为一种类型(例如CommonFilters)并使该类型可从字符串中解析是有用的:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
请求看起来像这样:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
通过这种方式,您减少了输入请求参数的数量,尤其是当某些查询不关心所有过滤器时
如果您使用swagger map,则此类型为字符串:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}