Создание конвейера запросов в ASP.NET Core. Метод Map

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

Метод Map

Метод Map позволяет создать ветку конвейера, которая будет обрабатываться по определенному пути. Этот метод является методом расширения для IApplicationBuilder и имеет ряд версий, среди которых наиболее часто используется следующая:

Map(IApplicationBuilder, String, Action<IApplicationBuilder>)
  • pathMatch — путь запроса с которым будет сопоставляться ветка конвейера.
  • configuration — делегат, в котором будет создаваться ветка конвейера

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

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        var app = builder.Build();
        app.Run(async context =>
        {
            //добавляем заголовок, чтобы корректно отображать кириллицу
            if (context.Response.HasStarted == false)
                context.Response.Headers.ContentType = "text/plain; charset=utf-8";
            //анализируем путь запроса
            switch (context.Request.Path)
            {
                case "/":
                    {
                        await context.Response.WriteAsync("Hello, Anonymous");
                        break;
                    }
                case "/date":
                    {
                        //получаем значение параметра format
                        var format = context.Request.Query["format"].ToString();
                        if ((format == "") || (format == "short"))
                            await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}");
                        else
                            if (format == "full")
                            await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}");
                        else
                            await context.Response.WriteAsync($"Error: Wrong format");
                        break;
                    }
                case "/time":
                    {
                        await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}");
                        break;
                    }
                default:
                    {
                        await context.Response.WriteAsync($"Error: Wrong path");
                        break;
                    }
            }
        });
        app.Run();
    }
}

С использованием метода Map это приложение можно переписать следующим образом:

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

        //передаем в ответ кодировку текста
        app.Use(async (context, next) =>
        {
            context.Response.OnStarting(
                () => {
                    context.Response.ContentType = "text/plain; charset=utf-8";
                    return Task.CompletedTask;  
                });
            
            await next.Invoke();
        });

        //ветка для date
        app.Map("/date", (app) => 
        {
            app.Run
            (
                async context =>
                {
                    //получаем значение параметра format
                    var format = context.Request.Query["format"].ToString();
                    if ((format == "") || (format == "short"))
                        await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}");
                    else
                        if (format == "full")
                        await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}");
                    else
                        await context.Response.WriteAsync($"Error: Wrong format");
                }
            );
        });

        //ветка для time
        app.Map("/time", (app) =>
        {
            app.Run
            (
                async context =>
                {
                    await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}");
                }
            );
        });

        //обрабатываем корень и все прочие пути
        app.Run(async context =>
               {
                   if (context.Request.Path == "/")
                       await context.Response.WriteAsync("Hello, Anonymous");
                   else
                       await context.Response.WriteAsync($"Error: Wrong path");
               });

        app.Run();
    }
}

Здесь стоит обратить внимание на то, что в метод Map нельзя передавать корневой путь, то есть "/" так как это вызовет ошибку, поэтому корень и все прочие пути по которым приложение должно выдать пользователю ошибку были вынесены в метод Run(). В остальном же, если сравнить код, использующий метод Map() и предыдущую версию приложения, то можно увидеть, что мы, по сути, для каждой ветки из switch создали ветку конвейера в методе Map(). При этом, мы можем выносить создание ветки в отдельные методы. Например, так:

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

        app.Map("/date", Date);
        app.Map("/time", Time);

        //обрабатываем корень и все прочие пути
        app.Run(async context =>
               {
                   if (context.Request.Path == "/")
                       await context.Response.WriteAsync("Hello, Anonymous");
                   else
                       await context.Response.WriteAsync($"Error: Wrong path");
               });

        app.Run();
    }

     static void Date(IApplicationBuilder app) 
     {
        app.Run
            (
                async context =>
                {
                    //получаем значение параметра format
                    var format = context.Request.Query["format"].ToString();
                    if ((format == "") || (format == "short"))
                        await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}");
                    else
                        if (format == "full")
                        await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}");
                    else
                        await context.Response.WriteAsync($"Error: Wrong format");
                }
            );
     }

    static void Time(IApplicationBuilder app)
    {
        app.Run
           (
               async context =>
               {
                   await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}");
               }
           );
    }
}

Однако, этот код не до конца показывает суть использования Map(). Дело в том, что внутри Map() мы также можем «разветвлять» наш конвейер самыми разными способами. Например, добавим в наше приложение ещё два пути:

  1. date/description — будет возвращать дополнительную информацию по текущей дате — день в году, день недели и т.д.
  2. date/yesterday — будет возвращать предыдущую дату

Вот как может выглядеть вызов Map() для пути /date:

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

        //ветка для date
        app.Map("/date", (appBuilder) =>
             {
                 appBuilder.Map("/description", DateDescription); //путь - /date/description
                 appBuilder.Map("/yesterday"  , DateYesterday);   //путь - /date/yesterday

                 appBuilder.Run(async context => 
                 {
                     if (context.Request.Path=="")
                        await DateRootPath(context);
                     else
                        await context.Response.WriteAsync($"Error: Wrong path");
                 });
             }
        );

        //тут другие вызовы Map(), Use(), Run()

        app.Run();

        void DateYesterday(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                var date = DateTime.Now.AddDays(-1);
                await context.Response.WriteAsync($"Вчера было: {date.ToShortDateString()} \r\n");
                await context.Response.WriteAsync($"   День в году: {date.DayOfYear} \r\n");
                await context.Response.WriteAsync($"   День в месяце: {date.Day} \r\n");
                await context.Response.WriteAsync($"   День недели: {(int)date.DayOfWeek}");
            });
        }

        void DateDescription(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                var date = DateTime.Now;
                await context.Response.WriteAsync($"Текущая дата: {date.ToShortDateString()} \r\n");
                await context.Response.WriteAsync($"   День в году: {date.DayOfYear} \r\n");
                await context.Response.WriteAsync($"   День в месяце: {date.Day} \r\n");
                await context.Response.WriteAsync($"   День недели: {(int)date.DayOfWeek}");
            });
        }

        async Task DateRootPath(HttpContext context)
        {
            var format = context.Request.Query["format"].ToString();
            if ((format == "") || (format == "short"))
                await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}");
            else
                if (format == "full")
                await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}");
            else
                await context.Response.WriteAsync($"Error: Wrong format");
        }
    }
}

Результат работы приложение представлен на рисунках ниже.

Обработка пути /date

обработка пути /date/description

обработка пути /date/yesterday

обработка всех прочих путей /date/xxxx

Ветка в конвейере создается один раз (пример)

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

//ветка для time
app.Map("/time", Time);

void Time(IApplicationBuilder app)
{
    app.Run
       (
           async context =>
           {
               await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}");
           }
       );
}

Здесь в middleware производится простая операция — клиенту возвращается текущее время. Изменим делегат таким образом, чтобы чтение значения времени осуществлялось до middleware, например, так:

void Time(IApplicationBuilder app)
{
    var time = DateTime.Now;//это действие выполнится немедленно и один раз
    app.Run
       (
           async context =>
           {
               await context.Response.WriteAsync($"Time: {time.ToShortTimeString()}");
           }
       );
}

Теперь запустим приложение, подождем немного и попробуем перейти по пути /time. Вопреки ожиданиям, что мы увидим текущее время, мы увидим время, когда была создана ветка конвейера, т.е., по сути, время запуска приложения:

Итого

Сегодня мы разобрались с третьим методом встраивания различных middleware в конвейер запросов ASP.NET Core — Map(). С использованием метода Map() мы можем создавать ветки конвейера, каждая из которых будет обрабатывать запросы по определенному пути. При этом, действия, указанные в делегате для метода Map() выполняются немедленно и один раз, в то время, как действия во встраиваемом в ветку middleware выполняются каждый раз.

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