Маршрутизация в ASP.NET Core. Параметры маршрутов

Для сопоставления пути запроса и делегата, который должен выполнится, в системе маршрутизации ASP.NET Core используются шаблоны маршрутов. До сих пор мы использовали простейшие шаблоны маршрутов типа /home, /index и так далее. Вместе с тем, мы можем задавать шаблоны маршрутов, содержащих параметры и различные ограничения эти параметров. Сегодня более подробно разберемся с шаблонами маршрутов в ASP.NET Core.

Параметры маршрута

При указании маршрута для конечной точки мы можем использоваться параметры. Каждый параметр имеет имя и заключается в фигурные скобки, например, {id}. Рассмотрим следующий пример:

namespace ToDo
{

    public class ToDoTask
    {
        public int Id { get; set; } //id задачи
        public string Name { get; set; } //имя задачи
        public DateTime Start { get; set; } = DateTime.Now; //дата создания задачи
        public DateTime End { get; set; } //дата окончания задачи
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();

            List<ToDoTask> toDoList = new()
            {
                new ToDoTask { Id = 1, Name = "Разобраться с шаблонами маршрутов в ASP.NET Core", End = DateTime.Now.AddDays(1)},
                new ToDoTask { Id = 2, Name = "Написать пример приложения", End = DateTime.Now.AddDays(2)},
                new ToDoTask { Id = 3, Name = "Написать статью про ASP.NET Core", End = DateTime.Now.AddDays(3)},
            };


            app.MapGet("/", async (context) =>
            {
                context.Response.ContentType = "text/html; charset=utf-8";

                StringBuilder sb = new StringBuilder();
                sb.AppendLine("<h1>Список задач</h1>");
                sb.AppendLine("<table>");
                sb.AppendLine("<tr><td>Id</td><td>Задача</td><td>Начало</td><td>Окончание</td></tr>");
                foreach (ToDoTask task in toDoList)
                {
                    sb.AppendLine($"<tr><td>{task.Id}</td><td>{task.Name}</td><td>{task.Start: dd:mm:yyyy}</td><td>{task.End: dd:mm:yyyy}</td></tr>");
                }

                await context.Response.WriteAsync(sb.ToString());
            });

            app.Run();
        }
    }
}

Здесь определен класс ToDoTask, описывающий одну задачу, список таких задач List<ToDoTask> и одна конечная точка в которой все задачи выводятся в виде таблицы. То есть ничего особенного, всё это мы уже делали и не раз.

Теперь, допустим, нам необходимо сделать так, чтобы пользователь мог получить каждую задачу по отдельности, например, чтобы в дальнейшем её редактировать. Самый простой способ это сделать — сделать так, чтобы путь вида /id, где id — это идентификатор задачи, приводил нас на страничку с задачей (или же наше приложение возвращало по этому пути, например JSON-объект). То есть, применительно к нашей задаче, это должны быть такие пути:

  • /1
  • /2
  • /3
  • и т.д.

Сделать это можно, используя в шаблоне маршрута параметр. Например следующим образом:

app.MapGet("/{id}", (int id) =>
{
    ToDoTask task = toDoList.Where(t=>t.Id==id).FirstOrDefault();
    StringBuilder sb = new StringBuilder();
    if (task != null)
    {

        sb.AppendLine("<h1>Задача</h1>");
        sb.AppendLine($"<p>ID: {task.Id}</p>");
        sb.AppendLine($"<p>Название: {task.Name}</p>");
        sb.AppendLine($"<p>Начало: {task.Start}</p>");
        sb.AppendLine($"<p>Окончание: {task.End}</p>");
    }
    else
    {
        sb.AppendLine($"Задача с ID {id} не найдена");
    }
    return sb.ToString();
});

здесь мы, при создании конечной точки, определили параметр с именем id и передали его в делегат:

"/{id}", (int id) => ...

Причем, мы также указали, что ожидаем получить в параметре именно значение int. В самом делегате мы использовали значение параметра для поиска необходимой задачи и вывели её пользователю. Результат в приложении выглядит следующим образом:

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

  1. Если пользователь задает путь, содержащий id задачи, то сработает конечная точка, которая выводит конкретную задачу
  2. Если ID не задан, то выводится список задач в виде таблицы.

Указание нескольких параметров

При необходимости, мы можем указывать в шаблоне маршрута не один, а несколько параметров. Например, так:

app.MapGet("/{id}/{format}", (int id, string format) =>
{
    ToDoTask task = toDoList.Where(t=>t.Id==id).FirstOrDefault();
    StringBuilder sb = new StringBuilder();
    if (task != null)
    {
        if (format.ToLower() == "text")
        {
            sb.AppendLine($"ID: {task.Id}; Имя: {task.Name} Начало: {task.Start} Окончание: {task.End}");
        }
        else
        {
            sb.AppendLine("<h1>Задача</h1>");
            sb.AppendLine($"<p>ID: {task.Id}</p>");
            sb.AppendLine($"<p>Название: {task.Name}</p>");
            sb.AppendLine($"<p>Начало: {task.Start}</p>");
            sb.AppendLine($"<p>Окончание: {task.End}</p>");
        }
    }
    else
    {
        sb.AppendLine($"Задача с ID {id} не найдена");
    }
    return sb.ToString();
});

В этом случае, если пользователь перейдет по пути /1/text, то задача вернется в виде обычного текста

, а во всех остальных случаях — в виде html.

Параметры могут указываться в одном сегменте пути подряд, но, при условии, что между ними будет находиться како-либо разделитель, например, такой шаблон маршрута не сработает:

/{id}{format}

так как параметры не разделены. Зато сработает такой шаблон:

/{id}-{format}

и такой шаблон может соответствовать, например, такому пути /1-text.

Необязательные параметры и параметры со значением по умолчанию

Параметры могут иметь значение по умолчанию или же быть необязательными. Вернемся к нашему примеру. На данный момент у нас определен следующий шаблон:

"/{id}/{format}"

это означает, что и параметр id и параметр format должны быть заданы. То, есть, например, путь /2 уже не соответствует ни одной из конечных точек приложения. Чтобы этот путь работал как уже определенная в приложении конечная точка, мы можем поступить следующими способами:

задать параметр format как необязательный — указав после имени параметра знак вопроса:

app.MapGet("/{id}/{format?}", (int id, string? format) =>

здесь необходимо также отметить, что в делегат мы передаем параметр как допускающий значение null. Если этого не сделать, то при попытке опустить необязательный параметр в пути мы получим ошибку:

указать для параметра format значение по умолчанию. Значение параметра по умолчанию указывается после знака =, например

app.MapGet("/{id}/{format=text}", (int id, string format) =>

в этом случае, если параметр format будет опущен, то в делегат будет передано его значение по умолчанию, то есть строка »

Обработчик конечной точки как отдельный метод

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

app.MapGet("/{id}/{format=text}", (int id, string format) => EndpointHandler);

сам метод EndpointHandler должен принимать параметры маршрута:

public static string EndpointHandler(int id, string format)

Итого

Сегодня мы рассмотрели применение параметров маршрута для формирования шаблона. Параметры маршрута могут быть необязательными, а также иметь значения по умолчанию. Что касается нашего примера приложения, то, применительно к теме шаблонов маршрута и их параметров, в примере есть уязвимость — например, если в качестве параметра id указать не число, а строку, то это приведет к исключению. Решаются подобные проблемы, в том числе, с использованием ограничения маршрутов, о которых мы поговорим далее.

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