Last active
December 5, 2025 14:01
-
-
Save sunmeat/e6f606dfca30c9bddf1e0361a28443e4 to your computer and use it in GitHub Desktop.
request processing pipeline example ASP.NET Core
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Program.cs: | |
| namespace RequestProcessingPipeline | |
| { | |
| public class Program | |
| { | |
| public static void Main(string[] args) // параметр args обов’язковий для WebApplication.CreateBuilder, інакше не працюватиме конфігурація додатка з appsettings.json, логування тощо | |
| { | |
| // 1. створюємо builder з args (інакше він не знає про appsettings, logging, Kestrel тощо) | |
| var builder = WebApplication.CreateBuilder(args); | |
| // 2. додаємо потрібний для сесії сервіс, сесія потребує кешування для зберігання даних сесії | |
| // загалом будь-який сервіс треба сприймати як постачальника функціональності для додатка, який можна використовувати в middleware або контролерах | |
| // наприклад, сервіс може надавати доступ до бази даних, логування, автентифікацію, авторизацію тощо | |
| builder.Services.AddDistributedMemoryCache(); | |
| // сесія - це набір даних, прив'язаних до конкретного користувача на певний час | |
| // сесія дозволяє зберігати інформацію між запитами від одного користувача | |
| // дуже спрощено, сесія - це словник, де можна зберігати пари ключ-значення, така собі глобальна змінна для користувача | |
| builder.Services.AddSession(options => | |
| { | |
| options.IdleTimeout = TimeSpan.FromMinutes(30); // час життя сесії, через 30 хвилин неактивності сесія буде видалена, і всі дані в ній втрачено | |
| options.Cookie.HttpOnly = true; // захищає cookie сесії від доступу зі сторони клієнтського коду (JavaScript) | |
| options.Cookie.IsEssential = true; // робить cookie сесії "необхідним" для роботи додатка, навіть якщо користувач відмовився від cookie | |
| }); // є варіант і без параметрів, але тоді сесія житиме 20 хвилин за замовчуванням | |
| // builder.Services.AddSession(); | |
| // докладніше про сесію: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-10#session-state | |
| // 3. будуємо додаток | |
| var app = builder.Build(); | |
| // 4. підключаємо middleware у правильному порядку | |
| app.UseSession(); // обов’язково перед тими, що читають/пишуть у сесію | |
| app.UseFromTwentyToHundred(); // 20–99 | |
| app.UseFromElevenToNineteen(); // 11–19 | |
| app.UseFromOneToTen(); // 1–10 | |
| // 5. запускаємо | |
| app.Run(); | |
| // 6. перевірка роботи: | |
| // https://aspnet.dev.localhost:7046/?number=21 | |
| } | |
| } | |
| } | |
| ==================================================================================================================== | |
| FromTwentyToHundredMiddleware.cs: | |
| namespace RequestProcessingPipeline | |
| { | |
| // розширювальний метод для підключення middleware у конвеєр обробки запитів | |
| // цей клас має бути у тій же області імен, що й middleware, щоб розширювальний метод був видимий | |
| // !!! зазвичай такі класи розміщують у окремих файлах, але для простоти хай буде тут | |
| // цей клас підключає middleware для обробки чисел від 20 до 100 | |
| // саме він дозволяє використовувати метод UseFromTwentyToHundred у Program.cs | |
| // без нього довелось би писати app.UseMiddleware<...>() - нудно і громіздко | |
| public static class FromTwentyToHundredExtensions | |
| { | |
| // розширювальний метод для IApplicationBuilder, який підключає наш middleware у конвеєр | |
| // якщо підзабули, що таке розширювальний метод - гляньте https://gist.github.com/sunmeat/75d1693cb6e23e7979c8701b116718c1 | |
| public static IApplicationBuilder UseFromTwentyToHundred(this IApplicationBuilder builder) | |
| { | |
| return builder.UseMiddleware<FromTwentyToHundredMiddleware>(); | |
| } | |
| } | |
| // middleware для обробки чисел від 20 до 100, запускається першим у конвеєрі | |
| public class FromTwentyToHundredMiddleware | |
| { | |
| private readonly RequestDelegate _next; // посилання на наступний middleware у конвеєрі | |
| // конструктор: отримує наступний компонент конвеєра, параметр цей передається автоматично асп нет кором | |
| public FromTwentyToHundredMiddleware(RequestDelegate next) | |
| { | |
| _next = next; // зберігаємо посилання на наступний middleware | |
| } | |
| // основний метод middleware, викликається при кожному HTTP-запиті | |
| public async Task InvokeAsync(HttpContext context) | |
| { | |
| // явно вказуємо, що відповідь у кодуванні UTF-8 і тип - звичайний текст | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| // отримуємо значення параметра "number" з рядка запиту (наприклад, ?number=45) | |
| string? token = context.Request.Query["number"]; | |
| // намагаємося перетворити рядок у ціле число | |
| if (!int.TryParse(token, out int number)) | |
| { | |
| // якщо параметр не є числом - повертаємо повідомлення про помилку | |
| await context.Response.WriteAsync("Некоректний параметр!"); | |
| return; // !!! завершуємо обробку запиту, наступний middleware не викликається !!! | |
| } | |
| // беремо модуль числа, щоб коректно обробляти від'ємні значення | |
| number = Math.Abs(number); | |
| // якщо число менше 20 - передаємо запит далі (наприклад, до middleware для 1–19) | |
| if (number < 20) | |
| { | |
| await _next(context); // !!! передаємо обробку наступному middleware !!! | |
| return; // коли ми тут, значить наступний middleware вже відпрацював, і ми просто завершуємо обробку | |
| } | |
| // якщо число більше 100 - є готова відповідь, запит не передається далі | |
| if (number > 100) | |
| { | |
| await context.Response.WriteAsync("Число більше ста, я поки що вмію рахувати лише до ста :)"); | |
| return; | |
| } | |
| // спеціальний випадок: рівно 100 | |
| if (number == 100) | |
| { | |
| await context.Response.WriteAsync("Ваше число - сто"); | |
| return; | |
| } | |
| // масив назв десятків (індекс 0 - 20, індекс 1 - 30 тощо) | |
| string[] tens = { "двадцять", "тридцять", "сорок", "п'ятдесят", "шістдесят", "сімдесят", "вісімдесят", "дев'яносто" }; | |
| // якщо число кратне 10 (20, 30, ..., 90) | |
| if (number % 10 == 0) | |
| { | |
| // виводимо тільки десятки, наприклад: "Ваше число - сорок" | |
| await context.Response.WriteAsync($"Ваше число - {tens[number / 10 - 2]}"); | |
| } | |
| else | |
| { | |
| // для чисел типу 21, 35, 47 тощо - спочатку викликаємо наступний middleware | |
| await _next(context); | |
| // а після повернення з наступного middleware продовжуємо обробку тут: | |
| // отримуємо назву одиниць, яку має покласти третій middleware у сесію | |
| string? units = context.Session.GetString("number"); // !!! важливо запам'ятати назву ключа :) | |
| // формуємо повну назву: "двадцять п'ять", "п'ятдесят вісім" тощо | |
| string result = units is not null | |
| ? tens[number / 10 - 2] + " " + units | |
| : tens[number / 10 - 2]; | |
| // виводимо остаточний результат | |
| await context.Response.WriteAsync("Ваше число - " + result); | |
| } | |
| } | |
| } | |
| } | |
| ==================================================================================================================== | |
| FromElevenToNineteenMiddleware.cs: | |
| namespace RequestProcessingPipeline | |
| { | |
| // розширення для підключення middleware (обробка чисел 11–19) | |
| public static class FromElevenToNineteenExtensions | |
| { | |
| public static IApplicationBuilder UseFromElevenToNineteen(this IApplicationBuilder builder) | |
| { | |
| return builder.UseMiddleware<FromElevenToNineteenMiddleware>(); | |
| } | |
| } | |
| // middleware повертає назву числа для діапазону 11–19 | |
| public class FromElevenToNineteenMiddleware | |
| { | |
| private readonly RequestDelegate _next; // посилання на наступний middleware у конвеєрі | |
| public FromElevenToNineteenMiddleware(RequestDelegate next) // значення next передається автоматично | |
| { | |
| _next = next; // зберігаємо посилання на наступний middleware | |
| } | |
| public async Task InvokeAsync(HttpContext context) | |
| { | |
| string? token = context.Request.Query["number"]; | |
| // назви чисел від 11 до 19 (індекс 0 = одинадцять) | |
| string[] numbers = | |
| { | |
| "одинадцять", "дванадцять", "тринадцять", "чотирнадцять", | |
| "п'ятнадцять", "шістнадцять", "сімнадцять", "вісімнадцять", "дев'ятнадцять" | |
| }; | |
| if (int.TryParse(token, out int number)) | |
| { | |
| number = Math.Abs(number); | |
| if (number >= 11 && number <= 19) | |
| { | |
| // якщо число в межах 11–19 - формуємо готову відповідь | |
| string result = $"Ваше число — {numbers[number - 11]}"; | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| await context.Response.WriteAsync(result); | |
| return; | |
| } | |
| } | |
| else if (!string.IsNullOrEmpty(token)) | |
| { | |
| // параметр передано, але він не є валідним цілим числом | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| await context.Response.WriteAsync("Некоректний параметр!"); | |
| return; | |
| } | |
| // якщо число поза діапазоном або параметр відсутній — передаємо далі | |
| await _next(context); | |
| } | |
| } | |
| } | |
| ==================================================================================================================== | |
| FromOneToTenMiddleware.cs: | |
| namespace RequestProcessingPipeline | |
| { | |
| public static class FromOneToTenExtensions | |
| { | |
| public static IApplicationBuilder UseFromOneToTen(this IApplicationBuilder builder) | |
| { | |
| return builder.UseMiddleware<FromOneToTenMiddleware>(); | |
| } | |
| } | |
| // middleware обробляє числа від 1 до 10 та частину логіки для більших чисел | |
| public class FromOneToTenMiddleware | |
| { | |
| private readonly RequestDelegate _next; | |
| public FromOneToTenMiddleware(RequestDelegate next) | |
| { | |
| _next = next; | |
| } | |
| public async Task InvokeAsync(HttpContext context) | |
| { | |
| string? token = context.Request.Query["number"]; | |
| // назви чисел від 1 до 9 | |
| string[] ones = | |
| { | |
| "один", "два", "три", "чотири", "п'ять", | |
| "шість", "сім", "вісім", "дев'ять" | |
| }; | |
| if (int.TryParse(token, out int number)) | |
| { | |
| number = Math.Abs(number); | |
| if (number == 10) | |
| { | |
| // спеціальний випадок для десяти | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| await context.Response.WriteAsync("Ваше число — десять"); | |
| return; | |
| } | |
| if (number >= 1 && number <= 9) | |
| { | |
| // числа від 1 до 9 — видаємо відповідь відразу | |
| string result = $"Ваше число — {ones[number - 1]}"; | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| await context.Response.WriteAsync(result); | |
| return; | |
| } | |
| if (number > 20) | |
| { | |
| // для чисел >20 зберігаємо останню цифру для наступного middleware | |
| context.Session.SetString("number", ones[number % 10 - 1]); | |
| } | |
| } | |
| else if (!string.IsNullOrEmpty(token)) | |
| { | |
| // параметр передано, але він не є цілим числом | |
| context.Response.ContentType = "text/plain; charset=utf-8"; | |
| await context.Response.WriteAsync("Некоректний параметр!"); | |
| return; | |
| } | |
| // передаємо управління далі по конвеєру | |
| await _next(context); | |
| } | |
| } | |
| } | |
| // доречі, число 0 видає помилку 404, бо жоден з middleware не дав відповідь — | |
| // всі просто передали управління далі (await _next(context)), | |
| // а в кінці конвеєра нічого немає, тому ASP.NET Core повертає стандартний 404 | |
| // ПРАКТИКА: | |
| // спробуйте додати обробку числа 0, будь-яким способом на ваш вибір |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment