来自路径/路由的 ASP.NET Core API 搜索参数

Cce*_*e32 6 asp.net rest asp.net-core

我正在移植一个使用的 PHP/ CI API,$params = $this->uri->uri_to_assoc()以便它可以接受具有多种组合的 GET 请求,例如:

有很多代码,如:

$page = 1;
if (!empty($params['page'])) {
    $page = (int)$params['page'];
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的两种 ASP.NET Core 2.1 技术看起来都是杂乱无章的,所以我希望得到有关更好解决方案的任何指导:

1)catchall的常规路由:

app.UseMvc(routes => {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Properties}/{action=Search}/{*params}"
                );
            });
Run Code Online (Sandbox Code Playgroud)

但是现在我必须解析params键/值对的字符串,并且无法利用模型绑定。

2)属性路由:

    [HttpGet("properties/search")]
    [HttpGet("properties/search/beds/{beds}")]
    [HttpGet("properties/search/beds/{beds}/page/{page}")]
    [HttpGet("properties/search/page/{page}/beds/{beds}")]
    public IActionResult Search(int beds, double lat, double lon, int page = 1, int limit = 10) {
}
Run Code Online (Sandbox Code Playgroud)

显然,将允许的搜索参数和值的每个组合都放在一起是乏味的。

更改这些端点的签名不是一种选择。

Sha*_*tin 2

FromPath价值提供者

您想要的是将复杂模型绑定到 url 路径的一部分。不幸的是,ASP.NET Core 没有内置的FromPath绑定器。不过幸运的是,我们可以构建自己的。

这是GitHub 中的一个示例FromPathValueProvider,其结果如下:

在此输入图像描述

基本上,它是有约束力的domain.com/controller/action/key/value/key/value/key/valueFromRoute这与价值提供者或价值提供者所做的不同FromQuery

使用价值FromPath提供者

创建这样的路线:

routes.MapRoute(
    name: "properties-search",
    template: "{controller=Properties}/{action=Search}/{*path}"
);
Run Code Online (Sandbox Code Playgroud)

[FromPath]属性添加到您的操作中:

public IActionResult Search([FromPath]BedsEtCetera model)
{
    return Json(model);
}
Run Code Online (Sandbox Code Playgroud)

神奇的是,它将绑定*path到一个复杂的模型:

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

创造FromPath价值提供者

基于 . 创建一个新属性FromRoute

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, 
    AllowMultiple = false, Inherited = true)]
public class FromPath : Attribute, IBindingSourceMetadata, IModelNameProvider
{
    /// <inheritdoc />
    public BindingSource BindingSource => BindingSource.Custom;

    /// <inheritdoc />
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

创建一个新的 IValueProviderFactory 基于RouteValueProviderFactory.

public class PathValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        var provider = new PathValueProvider(
            BindingSource.Custom, 
            context.ActionContext.RouteData.Values);

        context.ValueProviders.Add(provider);

        return Task.CompletedTask;
    }
}
Run Code Online (Sandbox Code Playgroud)

基于 . 创建一个新的 IValueProvider RouteValueProvider

public class PathValueProvider : IValueProvider
{
    public Dictionary<string, string> _values { get; }

    public PathValueProvider(BindingSource bindingSource, RouteValueDictionary values)
    {
        if(!values.TryGetValue("path", out var path)) 
        {
            var msg = "Route value 'path' was not present in the route.";
            throw new InvalidOperationException(msg);
        }

        _values = (path as string).ToDictionaryFromUriPath();
    }

    public bool ContainsPrefix(string prefix) => _values.ContainsKey(prefix);

    public ValueProviderResult GetValue(string key)
    {
        key = key.ToLower(); // case insensitive model binding
        if(!_values.TryGetValue(key, out var value)) {
            return ValueProviderResult.None;
        }

        return new ValueProviderResult(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用扩展PathValueProvider方法ToDictionaryFromUriPath

public static class StringExtensions {
    public static Dictionary<string, string> ToDictionaryFromUriPath(this string path) {
        var parts = path.Split('/');
        var dictionary = new Dictionary<string, string>();
        for(var i = 0; i < parts.Length; i++)
        {
            if(i % 2 != 0) continue;
            var key = parts[i].ToLower(); // case insensitive model binding
            var value = parts[i + 1];
            dictionary.Add(key, value);
        }

        return dictionary;
    }
}
Run Code Online (Sandbox Code Playgroud)

将课堂上的事物连接在一起Startup

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .AddMvcOptions(options => 
                options.ValueProviderFactories.Add(new PathValueProviderFactory()));
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMvc(routes => {
            routes.MapRoute(
                name: "properties-search",
                template: "{controller=Properties}/{action=Search}/{*path}"
            );
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这是GitHub 上的工作示例