ASP.NET Core API параметры поиска из пути / маршрута - PullRequest
0 голосов
/ 29 июня 2018

Я портирую PHP / CI API, который использует $params = $this->uri->uri_to_assoc(), чтобы он мог принимать запросы GET со многими комбинациями, такими как:

С большим количеством кода, как:

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

Но теперь я должен проанализировать строку 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) {
}

Очевидно, что вводить все комбинации разрешенных параметров и значений поиска утомительно.

Изменение подписи этих конечных точек не вариант.

Ответы [ 3 ]

0 голосов
/ 30 июня 2018

ИМХО, вы смотрите на это с неправильной точки зрения.

Создать модель:

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:

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

Объект результата (динамический)

public class ListViewModel
{
    public object[] items;
    public int totalCount = 0;
    public int filteredCount = 0;
}
0 голосов
/ 30 июня 2018

FromPath значение поставщика

То, что вы хотите, это связать сложную модель с частью пути URL. К сожалению, ASP.NET Core не имеет встроенного связывателя FromPath. К счастью, однако, мы можем построить свой собственный.

Вот пример FromPathValueProvider в GitHub , который имеет следующий результат:

enter image description here

По сути, это обязательный domain.com/controller/action/key/value/key/value/key/value. Это отличается от того, что делают провайдеры значений FromRoute или FromQuery.

Использовать FromPath значение поставщика

Создайте такой маршрут:

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

Добавьте атрибут [FromPath] к своему действию:

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

И волшебным образом он свяжет *path со сложной моделью:

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

Создайте новую базу 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;
    }
}

Создание новой базы 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);
    }
}

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

Вот рабочий образец на GitHub .

0 голосов
/ 30 июня 2018

Редактировать

Мой другой ответ - лучший вариант.

Общая идея

$params = $this->uri->uri_to_assoc() превращает URI в ассоциативный массив, который по сути является .NET Dictionary<TKey, TValue>. Мы можем сделать что-то подобное в ASP.NET Core. Допустим, у нас есть следующие маршруты.

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

Привязать путь Uri к словарю

Действие

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

Метод расширения

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

Результатом является ассоциативный массив, основанный на пути URI.

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

Но теперь я должен разобрать строку параметров для пар ключ / значение и не могу воспользоваться преимуществами привязки модели.

Привязать путь Ури к модели

Если вы хотите связать модель для этого, тогда мы должны пойти еще дальше.

Модель

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

Действие

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

Дополнительный метод расширения

public static TResult BindFromUriPath<TResult>(this string path)
{
    var dictionary = path.ToDictionaryFromUriPath();
    var json = JsonConvert.SerializeObject(dictionary);
    return JsonConvert.DeserializeObject<TResult>(json);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...