Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active December 5, 2025 14:01
Show Gist options
  • Select an option

  • Save sunmeat/e6f606dfca30c9bddf1e0361a28443e4 to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/e6f606dfca30c9bddf1e0361a28443e4 to your computer and use it in GitHub Desktop.
request processing pipeline example ASP.NET Core
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