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

  • https://server/properties/search/beds/3/page/1/sort/price_desc
  • https://server/properties/search/page/2/lat/34.1/lon/-119.1
  • https://server/properties/search

  • 有很多类似的代码:
    $page = 1;
    if (!empty($params['page'])) {
        $page = (int)$params['page'];
    }
    

    我尝试过的两种ASP.NET Core 2.1技术似乎都太过复杂了,因此,我希望对更好的解决方案提供任何指导:

    1)具有包罗万象的常规路由:
    app.UseMvc(routes => {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Properties}/{action=Search}/{*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) {
    }
    



    不能更改这些端点的签名。

    最佳答案

    FromPath值提供者

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

    这是一个example FromPathValueProvider in GitHub,其结果如下:

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

    基本上,它绑定(bind)domain.com/controller/action/key/value/key/value/key/value。这与FromRouteFromQuery值提供程序所做的不同。

    使用FromPath值提供程序

    创建这样的路线:

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

    [FromPath]属性添加到您的操作中:
    public IActionResult Search([FromPath]BedsEtCetera model)
    {
        return Json(model);
    }
    

    神奇的是它将*path绑定(bind)到一个复杂的模型:
    public class BedsEtCetera
    {
        public int Beds { get; set; }
        public int Page { get; set; }
        public string Sort { get; set; }
    }
    

    创建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; }
    }
    

    RouteValueProviderFactory .上创建一个新的IValueProviderFactory
    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;
        }
    }
    

    RouteValueProvider 上创建一个新的IValueProvider。
    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);
        }
    }
    
    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;
        }
    }
    

    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}"
                );
            });
        }
    }
    

    这是a working sample on GitHub

    关于来自路径/路由的ASP.NET Core API搜索参数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/51092342/

    10-12 15:49