REST API — как реализовать несколько методов с одинаковым именем, но с разными допустимыми типами параметров

Допустим, я хочу реализовать контроллер пользовательского API. Клиенту нужно получить пользователей по целочисленному идентификатору, поэтому я создаю метод GET - /api/User/{id:int}.

Затем по какой-то причине я хочу реализовать получение пользователя по его имени. Наиболее очевидным решением является создание GET - /api/User/{name:string}, но этот метод будет конфликтовать с предыдущим. Присвоение имени методу /api/UserByName/{name:string} нарушает правила сущностей REST.

Как я могу решить эту проблему, не нарушая правила REST?

upd: я только что написал следующий код для создания нескольких маршрутов:

    [HttpGet]
    public async Task<IActionResult> Get([FromQuery] int id)
    {
        return Ok();
    }
    
    [HttpGet]
    public async Task<IActionResult> Get([FromQuery] string name)
    {
        return Ok();
    }

Этот код не может быть переведен swagger и выдает ошибку Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "GET WeatherForecast" for actions - WebApiSkeleton.API.Contro llers.WeatherForecastController.Get (WebApiSkeleton.API),WebApiSkeleton.API.Controllers.WeatherForecastController.Get (WebApiSkeleton.API). Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround

Вызов метода вызывает еще одно исключение: Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches: WebApiSkeleton.API.Controllers.WeatherForecastController.Get (WebApiSkeleton.API) WebApiSkeleton.API.Controllers.WeatherForecastController.Get (WebApiSkeleton.API)

Я не вижу, чтобы это работало. Допустимое число также является допустимой строкой. Итак, на каком основании должен оцениваться маршрут, чтобы добраться до правильной конечной точки? Вы можете попробовать просто сделать конечную точку строки. И затем, если это также допустимый номер, рассматривайте его как идентификатор. Так что делайте различие в коде, а не по маршруту.

Ralf 20.06.2023 10:27

Со стороны С# это должно работать без проблем, ведь это два разных метода с двумя разными типами...

LiefLayer 20.06.2023 10:29

Я бы взял {id_name:string}, а затем проанализировал его внутри как id 1) если id всегда int и 2) у вас есть правила, чтобы имя пользователя не содержало только чисел

İsmail Kök 20.06.2023 10:29

Я бы добавил [FromQuery] на маршруте

Camadas 20.06.2023 10:30

Например, взгляните на эту конечную точку API Microsoft Graph (получить пользователя): Learn.Microsoft.com/en-us/graph/api/…. Вы можете передать либо идентификатор пользователя, либо upn (электронную почту), поэтому они, вероятно, различают в бэкэнде, что делать. Мне такой подход кажется правильным.

thehennyy 20.06.2023 10:34

Возможно, вы захотите взглянуть на параметр Uri, чтобы получить что-то более красноречивое, например /api/User?id=1 или /api/User?name=ralf.

Ralf 20.06.2023 10:36

обновил тело вопроса с кодом и исключениями

WingiM 20.06.2023 10:42

Что касается ответа Ральфа, я не думаю, что это сработает, поскольку по какой-то причине у пользователя может быть числовое имя. Как мне относиться к этому таким образом?

WingiM 20.06.2023 10:44

Тогда мы в UriParameter, как вы можете сделать, тогда /api/User?id=1 против /api/User?name=1. Вы получаете явное различие.

Ralf 20.06.2023 10:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
77
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Основная маршрутизация ASP.Net использует приоритет при выборе маршрутов, обычно от более конкретного к общему. Следовательно, если у вас есть два маршрута, один с {id:int}, а другой без какого-либо квалификатора или квалификатора :alpha, тогда, если вы отправите вам int, {id:int} будет удален, иначе другой более общий маршрут будет быть пораженным. Это означает, что эти два маршрута могут сосуществовать и будут делать то, что вы хотите:

[HttpGet("something/{id:int}")]
public IActionResult Get1(int id)
{
    return Ok();
}

[HttpGet("something/{id}")]
public IActionResult Get2(string id)
{
    return Ok();
}

Вы можете проверить вызов этих двух точек с помощью почтальона или любого http-клиента. Ошибка, с которой вы столкнулись, вероятно, связана с тем, что swagger не может генерировать документацию, в которой говорится что-то вроде этого: Conflicting method/path combination Это не имеет ничего общего с ядром asp.net, а swaggerм.

Что вы можете сделать, так это игнорировать тот, который берет {id:int} из чванства, поскольку он менее общий. Вы можете использовать : [ApiExplorerSettings(IgnoreApi = true)] для этого. Как только вы это сделаете, если вы откроете swagger, вы увидите одну конечную точку, но если вы попробуете это с int, вы попадете в конечную точку {id:int}, в противном случае вы попадете в конечную точку {id}.

Не помогает напрямую здесь, когда одно и то же значение может иметь 2 разных значения, но полезно знать, что есть шанс разрешить этот конфликт по типу для несколько похожих проблем.

Ralf 21.06.2023 11:38

@Ralf Я согласен, но это можно задокументировать в чванстве как хороший компромисс.

Abdelkrim 21.06.2023 11:49
Ответ принят как подходящий

Я имел в виду, используя [FromQuery], вот что. Вам потребуется класс с идентификатором и именем

public class UserFilter
{
    public int? Id { get; set; }
    public string Name { get; set; }
}

Тогда проложите такой маршрут

[HttpGet]
public async Task<IActionResult> Get([FromQuery] UserFilter filter)
{
    if (filter.Id.HasValue)
    {
        // Search by id
    }
    else if (!string.IsNullOrWhiteSpace(filter.Name))
    {
        // Searh by name
    }
    return BadRequest();
}

Это также позволит выполнить запрос вообще без параметров.

Abdelkrim 20.06.2023 12:09

Я просто копирую прошлый код, я могу просто заменить его Ok(); с помощью BadRequest(); поскольку на этом маршруте он хочет получить пользователя по идентификатору или по имени, если ничего не указано, вернется BadRequest. Но это единственный способ, которым я это вижу, чтобы это работало, и swagger не жаловалось бы на это.

Camadas 20.06.2023 12:51
[HttpGet]
[Route("api/controller")]
public async Task<IHttpActionResult> Get([FromQuery]string parameter)
{
    if (!string.IsNullOrEmpty(parameter))
    {
        if (int.TryParse(parameter, out int id))
        {
            // Logic to retrieve data based on the ID asynchronously
            // Example: var data = await repository.GetDataByIdAsync(id);
            // return Ok(data);
        }
        else
        {
            // Logic to retrieve data based on the name asynchronously
            // Example: var data = await repository.GetDataByNameAsync(parameter);
            // return Ok(data);
        }
    }

    // Handle invalid or missing parameter
    return BadRequest("Invalid parameter.");
}

В этом обновленном примере метод GetData принимает один параметр параметра строкового типа. Он проверяет, не является ли параметр нулевым или пустым. Если он не пуст, он пытается проанализировать его как целое число, используя int.TryParse(). Если синтаксический анализ прошел успешно, он обрабатывает параметр как идентификатор и выполняет логику для извлечения данных на основе идентификатора. Если синтаксический анализ не удается, он обрабатывает параметр как имя и выполняет логику для извлечения данных на основе имени.

Примеры конечных точек:

По целому числу:

GET /api/controller?parameter=123

По строке:

GET /api/controller?parameter=SOMENAME

Другие вопросы по теме