ASP。NET核心API搜索参数的路径/路径。

时间:2021-10-24 11:01:46

I am porting a PHP/CI API that uses $params = $this->uri->uri_to_assoc() so that it can accept GET requests with many combinations, such as:

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

With lots of code like:

有很多代码,比如:

$page = 1;
if (!empty($params['page'])) {
    $page = (int)$params['page'];
}

The two ASP.NET Core 2.1 techniques I've tried both seem like a kludge so I would appreciate any guidance on a better solution:

1) Conventional routing with catchall:

1)常规路由:

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

But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.

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

2) Attribute routing:

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) {
}

Obviously putting every combination of allowed search parameters and values is tedious.

显然,将所有允许的搜索参数和值组合在一起是很繁琐的。

Changing the signature of these endpoints is not an option.

3 个解决方案

#1


1  

FromPath value provider

What you are wanting is to bind a complex model to part of the url path. Unfortunately, ASP.NET Core does not have a built-in FromPath binder. Fortunately, though, we can build our own.

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

Here is an example FromPathValueProvider in GitHub that has the following result:

下面是GitHub上的pathvalueprovider示例,其结果如下:

ASP。NET核心API搜索参数的路径/路径。

Basically, it is binding domain.com/controller/action/key/value/key/value/key/value. This is different than what either the FromRoute or the FromQuery value providers do.

基本上,它是binding domain.com/controller/action/key/value/key/value/key/value。这与FromRoute或FromQuery值提供者所做的不同。

Use the FromPath value provider

Create a route like this:

创建这样的路线:

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

Add the [FromPath] attribute to your action:

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

public IActionResult Search([FromPath]BedsEtCetera model)
{
    return Json(model);
}

And magically it will bind the *path to a complex model:

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

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}

Create the FromPath value provider

Create a new attribute based on FromRoute.

基于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; }
}

Create a new IValueProviderFactory base on RouteValueProviderFactory.

在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;
    }
}

Create a new IValueProvider base on RouteValueProvider.

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

The PathValueProvider uses a ToDictionaryFromUriPath extension method.

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

Wire things together in your Startup class.

将启动类中的东西连接在一起。

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

Here is a working sample on GitHub.

这是GitHub上的一个工作示例。

#2


1  

Edit

My other answer is a better option.

我的另一个答案是更好的选择。

General Idea

$params = $this->uri->uri_to_assoc() turns a URI into an associative array, which is basically a .NET Dictionary<TKey, TValue>. We can do something similar in ASP.NET Core. Lets say we have the following routes.

$params = $this->uri->uri_to_assoc()将一个uri转换为一个关联数组,它基本上是一个。net字典 。我们可以在ASP中做一些类似的事情。净的核心。假设我们有以下的路径。 ,>

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

Bind Uri Path to Dictionary

Action

行动

public class PropertiesController : Controller
{
    public IActionResult Search(string slug)
    {
        var dictionary = slug.ToDictionaryFromUriPath();
         return Json(dictionary);
    }
}

Extension Method

扩展方法

public static class UrlToAssocExtensions
{
    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];
            var value = parts[i + 1];
            dictionary.Add(key, value);
        }

        return dictionary;
    }
}

The result is an associative array based on the URI path.

结果是一个基于URI路径的关联数组。

{
   "beds": "3",
   "page": "1",
   "sort": "price_desc"
}

But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.

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

Bind Uri Path to Model

If you want model binding for this, then we need to go a step further.

如果您想要为此进行模型绑定,那么我们需要更进一步。

Model

模型

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}

Action

行动

public IActionResult Search(string slug)
{
    BedsEtCetera model = slug.BindFromUriPath<BedsEtCetera>();
    return Json(model);
}

Additional Extension Method

额外的扩展方法

public static TResult BindFromUriPath<TResult>(this string path)
{
    var dictionary = path.ToDictionaryFromUriPath();
    var json = JsonConvert.SerializeObject(dictionary);
    return JsonConvert.DeserializeObject<TResult>(json);
}

#3


0  

IMHO you are looking at this from the wrong perspective.

我想你是从错误的角度看这件事的。

Create a model:

创建一个模型:

public class FiltersViewModel
    {
        public int Page { get; set; } = 0;
        public int ItemsPerPage { get; set; } = 20;
        public string SearchString { get; set; }
        public string[] Platforms { get; set; }
    }

API Endpoint:

API端点:

[HttpGet]
public async Task<IActionResult> GetResults([FromRoute] ViewModels.FiltersViewModel filters)
{
    // process the filters here
}

Result Object (dynamic)

结果对象(动态)

public class ListViewModel
{
    public object[] items;
    public int totalCount = 0;
    public int filteredCount = 0;
}

#1


1  

FromPath value provider

What you are wanting is to bind a complex model to part of the url path. Unfortunately, ASP.NET Core does not have a built-in FromPath binder. Fortunately, though, we can build our own.

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

Here is an example FromPathValueProvider in GitHub that has the following result:

下面是GitHub上的pathvalueprovider示例,其结果如下:

ASP。NET核心API搜索参数的路径/路径。

Basically, it is binding domain.com/controller/action/key/value/key/value/key/value. This is different than what either the FromRoute or the FromQuery value providers do.

基本上,它是binding domain.com/controller/action/key/value/key/value/key/value。这与FromRoute或FromQuery值提供者所做的不同。

Use the FromPath value provider

Create a route like this:

创建这样的路线:

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

Add the [FromPath] attribute to your action:

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

public IActionResult Search([FromPath]BedsEtCetera model)
{
    return Json(model);
}

And magically it will bind the *path to a complex model:

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

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}

Create the FromPath value provider

Create a new attribute based on FromRoute.

基于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; }
}

Create a new IValueProviderFactory base on RouteValueProviderFactory.

在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;
    }
}

Create a new IValueProvider base on RouteValueProvider.

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

The PathValueProvider uses a ToDictionaryFromUriPath extension method.

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

Wire things together in your Startup class.

将启动类中的东西连接在一起。

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

Here is a working sample on GitHub.

这是GitHub上的一个工作示例。

#2


1  

Edit

My other answer is a better option.

我的另一个答案是更好的选择。

General Idea

$params = $this->uri->uri_to_assoc() turns a URI into an associative array, which is basically a .NET Dictionary<TKey, TValue>. We can do something similar in ASP.NET Core. Lets say we have the following routes.

$params = $this->uri->uri_to_assoc()将一个uri转换为一个关联数组,它基本上是一个。net字典 。我们可以在ASP中做一些类似的事情。净的核心。假设我们有以下的路径。 ,>

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

Bind Uri Path to Dictionary

Action

行动

public class PropertiesController : Controller
{
    public IActionResult Search(string slug)
    {
        var dictionary = slug.ToDictionaryFromUriPath();
         return Json(dictionary);
    }
}

Extension Method

扩展方法

public static class UrlToAssocExtensions
{
    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];
            var value = parts[i + 1];
            dictionary.Add(key, value);
        }

        return dictionary;
    }
}

The result is an associative array based on the URI path.

结果是一个基于URI路径的关联数组。

{
   "beds": "3",
   "page": "1",
   "sort": "price_desc"
}

But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.

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

Bind Uri Path to Model

If you want model binding for this, then we need to go a step further.

如果您想要为此进行模型绑定,那么我们需要更进一步。

Model

模型

public class BedsEtCetera 
{
    public int Beds { get; set; }
    public int Page { get; set; }
    public string Sort { get; set; }
}

Action

行动

public IActionResult Search(string slug)
{
    BedsEtCetera model = slug.BindFromUriPath<BedsEtCetera>();
    return Json(model);
}

Additional Extension Method

额外的扩展方法

public static TResult BindFromUriPath<TResult>(this string path)
{
    var dictionary = path.ToDictionaryFromUriPath();
    var json = JsonConvert.SerializeObject(dictionary);
    return JsonConvert.DeserializeObject<TResult>(json);
}

#3


0  

IMHO you are looking at this from the wrong perspective.

我想你是从错误的角度看这件事的。

Create a model:

创建一个模型:

public class FiltersViewModel
    {
        public int Page { get; set; } = 0;
        public int ItemsPerPage { get; set; } = 20;
        public string SearchString { get; set; }
        public string[] Platforms { get; set; }
    }

API Endpoint:

API端点:

[HttpGet]
public async Task<IActionResult> GetResults([FromRoute] ViewModels.FiltersViewModel filters)
{
    // process the filters here
}

Result Object (dynamic)

结果对象(动态)

public class ListViewModel
{
    public object[] items;
    public int totalCount = 0;
    public int filteredCount = 0;
}