Контроллеры ASP.NET Core Web API. Маршрутизация с использованием атрибута Route

В нашем случае, под понятием «маршрут» понимается URI на который пользователь отправляет запрос и ASP.NET Core сопоставляет этот маршрут с конкретным действием контроллера. В проектах ASP.NET Core Web API используется маршрутизация на основе атрибутов. Этот механизм маршрутизации включается в обязательном порядке, если контроллер использует атрибут [ApiController]. В этой части мы рассмотрим один из вариантов настройки маршрутизации в проекте ASP.NET Core Web API — с использованием атрибута Route.

Пример использования атрибута Route

Создадим новое приложение ASP.NET Core Web API и посмотрим на описание контроллера WeatherForecastController, который создается по умолчанию в папке Controllers:

    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        
        public IEnumerable<WeatherForecast> Get()
        {
            
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

В этом контроллере атрибут Route применен непосредственно к классу:

[Route("[controller]")]

В параметрах атрибута указано одно из зарезервированных имен системы маршрутизации ASP.NET Core — controller. Это означает, что для все действия контроллера будут сопоставляться URI https://localhost[:port]/WeatherForecast , то есть на место слова controller будет подставляться имя контроллера без суффикса Controller.

При использовании такого подхода к настройке маршрутизации в контроллере (когда атрибут Route указывается только для класса котроллера) следует обратить внимание на следующий момент — действия в таком контроллере должны обрабатывать различные HTTP-методы. Это означает, что в контроллере не должно быть двух действий, обрабатывающих, например, GET-запросы. Для демонстрации, добавим в контроллер вот такое действие:

[HttpGet]
public string GetNext()
{
    return string.Empty;
}

Теперь у нас в контроллере два действия — Get() и GetNext() обрабатывающих GET-запросы. Теперь попробуем выполнить запрос из HTTP-файла проекта и вместо ответа получим вот такую ошибку:

Два действия контроллера обрабатывают один и тот же HTTP-метод GET и имеют один и тот же путь из-за чего и возникает конфликт. Чтобы разрешить эту проблему мы должны либо переопределить метод HTTP, который будет обрабатываться действием, например, указать атрибут [HttpPost], что не всегда является возможным, либо указать для действия другой путь. Как разрешается такая проблема с помощью атрибута Route разберемся далее.

Класс RouteAttribute

Класс RouteAttribute является наследником класса Attribute и предоставляет следующие свойства:

Name Имя маршрута
Order Порядок маршрута. Сначала выполняются маршруты с более низким порядком. Если ни действие, ни контроллер не определяют порядок, используется значение по умолчанию 0.
Template Шаблон маршрута.

Чаще всего используется только третье свойство этого класса — Template, отвечающее за шаблон маршрута. При этом, атрибут Route может применяться как к классу в целом, так и к отдельным его методам, а также использоваться для одной и той же цели несколько раз, о чем свидетельствует атрибут AttributeUsage класса:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RouteAttribute : Attribute, IRouteTemplateProvider

Так, например, для контроллера WeatherForecastController мы могли бы применить атрибут Route следующим образом:

[Route("[controller]")]
[Route("Example")]
public class WeatherForecastController : ControllerBase

В этом случае, наше приложение будет сопоставлять с одним и тем же действием два маршрута:

  1. /weatherforecast
  2. /example

Теперь, мы можем выполнить запрос, например, GET-запрос или по пути /WeatherForecast или по пути /Example и получить один и тот же результат. Возможность множественного использования атрибута для одной и той же цели (класса или метода) позволяет нам создавать гибкую систему маршрутизации в приложении.

Указание маршрутов для действия контроллера

Вернемся к примеру с ошибкой. Чтобы разместить в контроллере несколько действий, обрабатывающих одни и те же HTTP-запросы необходимо, чтобы у этих методов были различные пути. Попробуем изменить метод GetNext() следующим образом:

[HttpGet]
[Route("/next")] //новый путь 
public string GetNext()
{
    return string.Empty;
}

Теперь, чтобы выполнить запрос ко второму действию контроллера, мы должны составить вот такой запрос в http-файле проекта:

@WebApplication3_HostAddress = http://localhost:5225

GET {{WebApplication3_HostAddress}}/next/

Теперь можно снова запустить приложение и убедиться, что запросы к обоим действиям не вызывают ошибок. Таким образом, атрибут Route, определенный для класса, будет содержать путь (или пути) к методам API по умолчанию, а атрибуты указанные для действий контроллера — переопределять путь по умолчанию.  Также, мы могли бы использовать зарезервированное имя для атрибута Route, примененного к действию:

[HttpGet]
[Route("[action]")] //новый путь 
public string GetNext()
{
    return string.Empty;
}

Здесь action — зарезервированное имя. В этом случае, путь будет складываться из пути, указанного в атрибуте для класса контроллера и имени метода. Не обязательно комбинировать атрибуты Route для класса и метода. Мы можем, например, указать Route только для класса (как это сделано в контроллере по умолчанию), а можем указать атрибуты только для действий контроллера:

  [ApiController]
//  [Route("[controller]")]
//  [Route("example")]
  public class WeatherForecastController : ControllerBase
  {
      private static readonly string[] Summaries = new[]
      {
          "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
      };

      private readonly ILogger<WeatherForecastController> _logger;

      public WeatherForecastController(ILogger<WeatherForecastController> logger)
      {
          _logger = logger;
      }

      [HttpGet]
      [Route("[action]")]
      public IEnumerable<WeatherForecast> Get()
      {

          return Enumerable.Range(1, 5).Select(index => new WeatherForecast
          {
              Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
              TemperatureC = Random.Shared.Next(-20, 55),
              Summary = Summaries[Random.Shared.Next(Summaries.Length)]
          })
          .ToArray();
      }

      [HttpGet]
      [Route("[action]")] //новый путь 
      public string GetNext()
      {
          return string.Empty;
      }

      [HttpPost]
      [Route("[action]")]
      public string Post()
      {
          return string.Empty;
      }
  }

Здесь стоит обратить внимание на то, что для всех трех действий контроллера необходимо указывать атрибут Route. Методы API будут теперь выглядеть следующим образом:

  1. /Get
  2. /GetNext
  3. /Post

Если для действия контроллера API не указан путь, то при запуске приложения мы получим ошибку. Например, уберем путь для действия Get():

    [HttpGet]
//    [Route("[action]")] отсутствие маршрута приведет к ошибке при запуске приложения
    public IEnumerable<WeatherForecast> Get()
    {

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }

При попытке запуска приложения мы увидим следующую ошибку:

System.InvalidOperationException: «Action ‘ApiControllers.Controllers.WeatherForecastController.Get (ApiControllers)’ does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed

Так как Get() является действием контроллера (у метода класса определен атрибут [HttpGet]), то у него обязательно должен быть определен маршрут.

Каким образом будет выводиться маршрут для действия?

Здесь следует различать два варианта работы ASP.NET Core:

  1. если шаблон маршрута для действия не содержит в начале символ «/», то шаблоны маршрута контроллера и действия складываются. Например, в нашем случае, шаблон маршрута не содержит лидирующего «/», следовательно, шаблоны маршрута для действия будут такими: [controller]/[action] или api/[controller]/[action]. В рабочем приложении пути запроса будут такими: tasks/get или api/tasks/get.
  2. если шаблон маршрута для действия содержит в начале символ «/», то шаблоном маршрута для действия будет только шаблон, указанный для действия контроллера.

Итого

В этой части мы рассмотрели основные моменты настройки системы маршрутизации в приложениях ASP.NET Core Web API на основе контроллеров. Для настройки маршрутов использовался атрибут Route, который может применяться как к классам, так и отдельным методам (действиям) контроллера многократно. Приложения ASP.NET Core Web API используют маршрутизацию на основе атрибутов, поэтому для каждого действия контроллера указывается своя уникальная пара значений «путь/http-метод». Используя атрибут Route мы можем задавать путь к действиям контроллера по умолчанию или же указывать для одного и того же действия несколько путей. На этом, тема маршрутизации в контроллерах Web API не завершается — в следующей части мы рассмотрим ещё один способ настройки системы маршрутизации с использованием атрибутов.

Подписаться
Уведомить о
guest
2 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии