[{"data":1,"prerenderedAt":19924},["ShallowReactive",2],{"navigation_docs":3,"-csharp-aspnet-web-api-filters-for-api":2949,"-csharp-aspnet-web-api-filters-for-api-surround":19919},[4,1640,1765,2219,2352,2559,2641,2691,2748,2782,2908,2945],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","/csharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1346,1636],{"title":11,"path":7,"stem":12},"C# Roadmap","01.csharp/index",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","/csharp/fundamentals","01.csharp/01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","/csharp/fundamentals/introduction-to-ecosystem","01.csharp/01.fundamentals/01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","/csharp/fundamentals/program-structure","01.csharp/01.fundamentals/02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","/csharp/fundamentals/variables-data-types","01.csharp/01.fundamentals/03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","/csharp/fundamentals/arrays","01.csharp/01.fundamentals/04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","/csharp/fundamentals/strings-text-handling","01.csharp/01.fundamentals/05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","/csharp/fundamentals/dates-time-handling","01.csharp/01.fundamentals/06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","/csharp/fundamentals/control-flow","01.csharp/01.fundamentals/07.control-flow",{"title":48,"path":49,"stem":50},"Методи","/csharp/fundamentals/methods","01.csharp/01.fundamentals/08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","/csharp/fundamentals/debugging-basics","01.csharp/01.fundamentals/09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","/csharp/fundamentals/interactive-console","01.csharp/01.fundamentals/10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","/csharp/oop","01.csharp/02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","/csharp/oop/package-management","01.csharp/02.oop/01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","/csharp/oop/classes-objects","01.csharp/02.oop/02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","/csharp/oop/properties-fields","01.csharp/02.oop/03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","/csharp/oop/oop-pillars","01.csharp/02.oop/04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","/csharp/oop/advanced-types","01.csharp/02.oop/05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","/csharp/oop/namespaces","01.csharp/02.oop/06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","/csharp/advanced-core","01.csharp/03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","/csharp/advanced-core/generics","01.csharp/03.advanced-core/01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","/csharp/advanced-core/delegates-events-lambdas","01.csharp/03.advanced-core/02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","/csharp/advanced-core/interfaces-deep-dive","01.csharp/03.advanced-core/03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","/csharp/advanced-core/exception-handling","01.csharp/03.advanced-core/04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","/csharp/advanced-core/pattern-matching","01.csharp/03.advanced-core/05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","/csharp/advanced-core/additional-features","01.csharp/03.advanced-core/06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","/csharp/architecture-best-practices","01.csharp/04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","/csharp/architecture-best-practices/software-design-principles","01.csharp/04.architecture-best-practices/01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","/csharp/architecture-best-practices/design-patterns","01.csharp/04.architecture-best-practices/02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","/csharp/architecture-best-practices/design-patterns/creational","01.csharp/04.architecture-best-practices/02.design-patterns/creational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","/csharp/architecture-best-practices/design-patterns/creational/singleton","01.csharp/04.architecture-best-practices/02.design-patterns/creational/01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","/csharp/architecture-best-practices/design-patterns/creational/builder","01.csharp/04.architecture-best-practices/02.design-patterns/creational/02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","/csharp/architecture-best-practices/building-professional-clis","01.csharp/04.architecture-best-practices/03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","/csharp/architecture-best-practices/validation-flow-control","01.csharp/04.architecture-best-practices/04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","/csharp/architecture-best-practices/modern-dotnet-host","01.csharp/04.architecture-best-practices/05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","/csharp/architecture-best-practices/data-mapper-part1","01.csharp/04.architecture-best-practices/06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","/csharp/architecture-best-practices/data-mapper-part2","01.csharp/04.architecture-best-practices/07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","/csharp/architecture-best-practices/di-ioc","01.csharp/04.architecture-best-practices/08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","/csharp/architecture-best-practices/di-ioc/the-dependency-problem","01.csharp/04.architecture-best-practices/08.di-ioc/01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","/csharp/architecture-best-practices/di-ioc/build-your-own-container","01.csharp/04.architecture-best-practices/08.di-ioc/02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","/csharp/architecture-best-practices/di-ioc/service-locator-pattern","01.csharp/04.architecture-best-practices/08.di-ioc/03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","/csharp/architecture-best-practices/di-ioc/dependency-injection-patterns","01.csharp/04.architecture-best-practices/08.di-ioc/04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","/csharp/architecture-best-practices/di-ioc/microsoft-di-deep-dive","01.csharp/04.architecture-best-practices/08.di-ioc/05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","/csharp/architecture-best-practices/di-ioc/service-lifetimes-and-scopes","01.csharp/04.architecture-best-practices/08.di-ioc/06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","/csharp/architecture-best-practices/di-ioc/di-anti-patterns-and-best-practices","01.csharp/04.architecture-best-practices/08.di-ioc/07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","/csharp/standard-library","01.csharp/05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","/csharp/standard-library/collections","01.csharp/05.standard-library/01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","/csharp/standard-library/high-performance-types","01.csharp/05.standard-library/02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","/csharp/standard-library/linq","01.csharp/05.standard-library/03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","/csharp/system-internals-concurrency","01.csharp/06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","/csharp/system-internals-concurrency/memory-management","01.csharp/06.system-internals-concurrency/01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","/csharp/system-internals-concurrency/reflection-fundamentals","01.csharp/06.system-internals-concurrency/02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","/csharp/system-internals-concurrency/attributes-dynamic","01.csharp/06.system-internals-concurrency/03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","/csharp/system-internals-concurrency/expression-trees-compiled","01.csharp/06.system-internals-concurrency/04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","/csharp/system-internals-concurrency/source-generators","01.csharp/06.system-internals-concurrency/05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","/csharp/system-internals-concurrency/multithreading-fundamentals","01.csharp/06.system-internals-concurrency/06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","/csharp/system-internals-concurrency/synchronization-primitives","01.csharp/06.system-internals-concurrency/07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","/csharp/system-programming-windows","01.csharp/07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","/csharp/system-programming-windows/how-os-works","01.csharp/07.system-programming-windows/01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","/csharp/system-programming-windows/processes-in-dotnet","01.csharp/07.system-programming-windows/02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","/csharp/system-programming-windows/02a.processes-ipc","01.csharp/07.system-programming-windows/02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","/csharp/system-programming-windows/appdomains-assemblies","01.csharp/07.system-programming-windows/03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","/csharp/system-programming-windows/03a.appdomains-plugin-system","01.csharp/07.system-programming-windows/03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","/csharp/system-programming-windows/thread-fundamentals","01.csharp/07.system-programming-windows/04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","/csharp/system-programming-windows/04a.thread-lifecycle-priorities","01.csharp/07.system-programming-windows/04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","/csharp/system-programming-windows/shared-state-problems","01.csharp/07.system-programming-windows/05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","/csharp/system-programming-windows/05a.shared-state-memory-model","01.csharp/07.system-programming-windows/05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","/csharp/system-programming-windows/synchronization-fundamentals","01.csharp/07.system-programming-windows/06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","/csharp/system-programming-windows/06a.synchronization-walkthrough","01.csharp/07.system-programming-windows/06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","/csharp/system-programming-windows/synchronization-advanced","01.csharp/07.system-programming-windows/07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","/csharp/system-programming-windows/07a.synchronization-advanced-walkthrough","01.csharp/07.system-programming-windows/07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","/csharp/system-programming-windows/interlocked-cas-lockfree","01.csharp/07.system-programming-windows/08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","/csharp/system-programming-windows/08a.volatile-memory-model","01.csharp/07.system-programming-windows/08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","/csharp/system-programming-windows/thread-pool","01.csharp/07.system-programming-windows/09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","/csharp/system-programming-windows/09a.thread-pool-advanced","01.csharp/07.system-programming-windows/09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","/csharp/system-programming-windows/concurrent-collections","01.csharp/07.system-programming-windows/10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","/csharp/system-programming-windows/tpl-parallel-plinq","01.csharp/07.system-programming-windows/11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","/csharp/system-programming-windows/11a.tpl-parallel-plinq-advanced","01.csharp/07.system-programming-windows/11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async/Await — Фундамент Асинхронного Програмування","/csharp/system-programming-windows/async-fundamentals","01.csharp/07.system-programming-windows/12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","/csharp/system-programming-windows/async-context-configureawait","01.csharp/07.system-programming-windows/13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","/csharp/system-programming-windows/async-advanced","01.csharp/07.system-programming-windows/14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","/csharp/system-programming-windows/channels","01.csharp/07.system-programming-windows/15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","/csharp/system-programming-windows/async-synchronization","01.csharp/07.system-programming-windows/16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","/csharp/system-programming-windows/unsafe-code","01.csharp/07.system-programming-windows/17.unsafe-code",{"title":364,"path":365,"stem":366},"P/Invoke та Windows API — Міст між .NET та Native Code","/csharp/system-programming-windows/pinvoke-winapi","01.csharp/07.system-programming-windows/18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","/csharp/system-programming-windows/windows-registry","01.csharp/07.system-programming-windows/19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","/csharp/system-programming-windows/windows-hooks-services","01.csharp/07.system-programming-windows/20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","/csharp/system-programming-windows/implementation_plan","01.csharp/07.system-programming-windows/implementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","/csharp/io","01.csharp/08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","/csharp/io/file-system-basics","01.csharp/08.io/01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","/csharp/io/streams-serialization","01.csharp/08.io/02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","/csharp/io/json-serialization","01.csharp/08.io/03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","/csharp/io/xml-serialization","01.csharp/08.io/04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","/csharp/io/binary-serialization","01.csharp/08.io/05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","/csharp/ado-net","01.csharp/09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","/csharp/ado-net/introduction-to-adonet","01.csharp/09.ado-net/01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","/csharp/ado-net/connection","01.csharp/09.ado-net/02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","/csharp/ado-net/command-and-queries","01.csharp/09.ado-net/03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","/csharp/ado-net/datareader","01.csharp/09.ado-net/04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","/csharp/ado-net/parameters-and-sql-injection","01.csharp/09.ado-net/05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","/csharp/ado-net/transactions","01.csharp/09.ado-net/06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","/csharp/ado-net/provider-factory","01.csharp/09.ado-net/07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","/csharp/ado-net/async-data-access","01.csharp/09.ado-net/08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","/csharp/ado-net/disconnected-mode-dataset","01.csharp/09.ado-net/09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","/csharp/ado-net/data-adapter","01.csharp/09.ado-net/10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","/csharp/ado-net/data-mapper-repository","01.csharp/09.ado-net/11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","/csharp/ado-net/advanced-patterns","01.csharp/09.ado-net/12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","/csharp/ef-core","01.csharp/10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","/csharp/ef-core/what-is-orm","01.csharp/10.ef-core/01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","/csharp/ef-core/first-project","01.csharp/10.ef-core/02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","/csharp/ef-core/dbcontext-deep-dive","01.csharp/10.ef-core/03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","/csharp/ef-core/database-providers","01.csharp/10.ef-core/04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","/csharp/ef-core/conventions","01.csharp/10.ef-core/05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","/csharp/ef-core/fluent-api-vs-annotations","01.csharp/10.ef-core/06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","/csharp/ef-core/relationships-basics","01.csharp/10.ef-core/07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","/csharp/ef-core/relationships-advanced","01.csharp/10.ef-core/08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","/csharp/ef-core/property-configuration-part1","01.csharp/10.ef-core/09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","/csharp/ef-core/property-configuration-part2","01.csharp/10.ef-core/09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","/csharp/ef-core/complex-types-owned-part1","01.csharp/10.ef-core/10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","/csharp/ef-core/complex-types-owned-part2","01.csharp/10.ef-core/10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","/csharp/ef-core/json-columns-part1","01.csharp/10.ef-core/11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","/csharp/ef-core/json-columns-part2","01.csharp/10.ef-core/11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","/csharp/ef-core/inheritance-part1","01.csharp/10.ef-core/12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","/csharp/ef-core/inheritance-part2","01.csharp/10.ef-core/12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","/csharp/ef-core/indexes-constraints-part1","01.csharp/10.ef-core/13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","/csharp/ef-core/indexes-constraints-part2","01.csharp/10.ef-core/13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","/csharp/ef-core/seeding-part1","01.csharp/10.ef-core/14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","/csharp/ef-core/seeding-part2","01.csharp/10.ef-core/14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","/csharp/ef-core/global-query-filters-part1","01.csharp/10.ef-core/15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","/csharp/ef-core/global-query-filters-part2","01.csharp/10.ef-core/15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","/csharp/ef-core/linq-queries-part1","01.csharp/10.ef-core/16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","/csharp/ef-core/linq-queries-part2","01.csharp/10.ef-core/16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","/csharp/ef-core/loading-related-data-part1","01.csharp/10.ef-core/17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","/csharp/ef-core/loading-related-data-part2","01.csharp/10.ef-core/17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","/csharp/ef-core/raw-sql-part1","01.csharp/10.ef-core/18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","/csharp/ef-core/raw-sql-part2","01.csharp/10.ef-core/18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","/csharp/ef-core/advanced-queries-part1","01.csharp/10.ef-core/19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","/csharp/ef-core/advanced-queries-part2","01.csharp/10.ef-core/19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","/csharp/ef-core/change-tracking-part1","01.csharp/10.ef-core/20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","/csharp/ef-core/change-tracking-part2","01.csharp/10.ef-core/20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","/csharp/ef-core/saving-data-part1","01.csharp/10.ef-core/21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","/csharp/ef-core/saving-data-part2","01.csharp/10.ef-core/21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","/csharp/ef-core/concurrency-part1","01.csharp/10.ef-core/22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","/csharp/ef-core/concurrency-part2","01.csharp/10.ef-core/22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","/csharp/ef-core/migrations-basics-part1","01.csharp/10.ef-core/23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","/csharp/ef-core/migrations-basics-part2","01.csharp/10.ef-core/23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","/csharp/ef-core/migrations-advanced-part1","01.csharp/10.ef-core/24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","/csharp/ef-core/migrations-advanced-part2","01.csharp/10.ef-core/24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","/csharp/ef-core/schema-management-part1","01.csharp/10.ef-core/25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","/csharp/ef-core/schema-management-part2","01.csharp/10.ef-core/25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","/csharp/ef-core/performance-fundamentals-part1","01.csharp/10.ef-core/26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","/csharp/ef-core/interceptors-part1","01.csharp/10.ef-core/29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","/csharp/ef-core/interceptors-part2","01.csharp/10.ef-core/29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","/csharp/ef-core/implementation_plan","01.csharp/10.ef-core/implementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","/csharp/aspnet","01.csharp/11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","/csharp/aspnet/minimal-api","01.csharp/11.aspnet/01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","/csharp/aspnet/minimal-api/introduction","01.csharp/11.aspnet/01.minimal-api/01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","/csharp/aspnet/minimal-api/first-application","01.csharp/11.aspnet/01.minimal-api/02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","/csharp/aspnet/minimal-api/webapplication-builder","01.csharp/11.aspnet/01.minimal-api/03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","/csharp/aspnet/minimal-api/request-pipeline-middleware","01.csharp/11.aspnet/01.minimal-api/04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/routing-basics","01.csharp/11.aspnet/01.minimal-api/05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","/csharp/aspnet/minimal-api/routing-advanced","01.csharp/11.aspnet/01.minimal-api/06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","/csharp/aspnet/minimal-api/static-files","01.csharp/11.aspnet/01.minimal-api/07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","/csharp/aspnet/minimal-api/static-assets","01.csharp/11.aspnet/01.minimal-api/08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/configuration-fundamentals","01.csharp/11.aspnet/01.minimal-api/09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","/csharp/aspnet/minimal-api/configuration-options","01.csharp/11.aspnet/01.minimal-api/10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/logging-basics","01.csharp/11.aspnet/01.minimal-api/11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","/csharp/aspnet/minimal-api/logging-advanced","01.csharp/11.aspnet/01.minimal-api/12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","/csharp/aspnet/minimal-api/state-management","01.csharp/11.aspnet/01.minimal-api/13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","/csharp/aspnet/minimal-api/session-state","01.csharp/11.aspnet/01.minimal-api/14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","/csharp/aspnet/minimal-api/project-structure","01.csharp/11.aspnet/01.minimal-api/15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","/csharp/aspnet/minimal-api/scalar-openapi-fluent","01.csharp/11.aspnet/01.minimal-api/16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger / Swashbuckle у Minimal API: окремий класичний шлях","/csharp/aspnet/minimal-api/swagger-swashbuckle","01.csharp/11.aspnet/01.minimal-api/17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","/csharp/aspnet/api","01.csharp/11.aspnet/02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","/csharp/aspnet/api/what-is-api","01.csharp/11.aspnet/02.api/01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","/csharp/aspnet/api/data-formats","01.csharp/11.aspnet/02.api/02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","/csharp/aspnet/api/api-paradigms-rest","01.csharp/11.aspnet/02.api/03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","/csharp/aspnet/api/http-methods-status-codes","01.csharp/11.aspnet/02.api/04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","/csharp/aspnet/api/rest-organizing","01.csharp/11.aspnet/02.api/05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","/csharp/aspnet/api/url-nomenclature-crud","01.csharp/11.aspnet/02.api/06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","/csharp/aspnet/api/api-design-naming","01.csharp/11.aspnet/02.api/07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","/csharp/aspnet/api/api-design-validation","01.csharp/11.aspnet/02.api/08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","/csharp/aspnet/api/error-handling-http","01.csharp/11.aspnet/02.api/09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","/csharp/aspnet/api/idempotency-sync","01.csharp/11.aspnet/02.api/10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","/csharp/aspnet/api/pagination-lists","01.csharp/11.aspnet/02.api/11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","/csharp/aspnet/api/security-auth","01.csharp/11.aspnet/02.api/12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","/csharp/aspnet/api/api-design-process","01.csharp/11.aspnet/02.api/13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","/csharp/aspnet/api/openapi","01.csharp/11.aspnet/02.api/14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","/csharp/aspnet/auth","01.csharp/11.aspnet/03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","/csharp/aspnet/auth/auth-fundamentals","01.csharp/11.aspnet/03.auth/01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","/csharp/aspnet/auth/jwt-authentication","01.csharp/11.aspnet/03.auth/02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","/csharp/aspnet/auth/authorization-policies","01.csharp/11.aspnet/03.auth/03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","/csharp/aspnet/auth/cookie-auth-identity","01.csharp/11.aspnet/03.auth/04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","/csharp/aspnet/auth/04b.identity-auth-jwt","01.csharp/11.aspnet/03.auth/04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","/csharp/aspnet/auth/identity-email-confirmation","01.csharp/11.aspnet/03.auth/05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","/csharp/aspnet/auth/identity-two-factor","01.csharp/11.aspnet/03.auth/06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","/csharp/aspnet/auth/identity-internals","01.csharp/11.aspnet/03.auth/07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","/csharp/aspnet/auth/oauth-external-providers","01.csharp/11.aspnet/03.auth/08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","/csharp/aspnet/auth/security-hardening","01.csharp/11.aspnet/03.auth/09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","/csharp/aspnet/auth/oauth-theory","01.csharp/11.aspnet/03.auth/10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","/csharp/aspnet/auth/oidc-keycloak","01.csharp/11.aspnet/03.auth/10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","/csharp/aspnet/auth/api-keys","01.csharp/11.aspnet/03.auth/11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","/csharp/aspnet/auth/rate-limiting","01.csharp/11.aspnet/03.auth/12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","/csharp/aspnet/auth/refresh-token-rotation","01.csharp/11.aspnet/03.auth/13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","/csharp/aspnet/auth/certificate-auth","01.csharp/11.aspnet/03.auth/14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","/csharp/aspnet/auth/rbac-abac-rebac","01.csharp/11.aspnet/03.auth/15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","/csharp/aspnet/auth/multi-tenancy","01.csharp/11.aspnet/03.auth/16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","/csharp/aspnet/notifications","01.csharp/11.aspnet/04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","/csharp/aspnet/notifications/in-app-database-notifications","01.csharp/11.aspnet/04.notifications/01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","/csharp/aspnet/notifications/polling","01.csharp/11.aspnet/04.notifications/02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","/csharp/aspnet/notifications/server-sent-events","01.csharp/11.aspnet/04.notifications/03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","/csharp/aspnet/notifications/websockets","01.csharp/11.aspnet/04.notifications/04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","/csharp/aspnet/notifications/signalr","01.csharp/11.aspnet/04.notifications/05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","/csharp/aspnet/notifications/background-services","01.csharp/11.aspnet/04.notifications/06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","/csharp/aspnet/notifications/web-push","01.csharp/11.aspnet/04.notifications/07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","/csharp/aspnet/notifications/email-notifications","01.csharp/11.aspnet/04.notifications/08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","/csharp/aspnet/notifications/choosing-the-right-approach","01.csharp/11.aspnet/04.notifications/09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","/csharp/aspnet/notifications/hangfire","01.csharp/11.aspnet/04.notifications/10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","/csharp/aspnet/notifications/hangfire-image-webp","01.csharp/11.aspnet/04.notifications/11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","/csharp/aspnet/notifications/hangfire-video-hls","01.csharp/11.aspnet/04.notifications/12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","/csharp/aspnet/notifications/telegram-notifications","01.csharp/11.aspnet/04.notifications/13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","/csharp/aspnet/i18n","01.csharp/11.aspnet/05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","/csharp/aspnet/i18n/internationalization","01.csharp/11.aspnet/05.i18n/01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","/csharp/aspnet/i18n/humanizer","01.csharp/11.aspnet/05.i18n/02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","/csharp/aspnet/caching","01.csharp/11.aspnet/06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","/csharp/aspnet/caching/caching","01.csharp/11.aspnet/06.caching/01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","/csharp/aspnet/caching/memory-cache","01.csharp/11.aspnet/06.caching/02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","/csharp/aspnet/caching/distributed-cache","01.csharp/11.aspnet/06.caching/03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","/csharp/aspnet/caching/response-cache","01.csharp/11.aspnet/06.caching/04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","/csharp/aspnet/caching/output-cache","01.csharp/11.aspnet/06.caching/05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","/csharp/aspnet/testing","01.csharp/11.aspnet/07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","/csharp/aspnet/testing/what-is-testing","01.csharp/11.aspnet/07.testing/01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","/csharp/aspnet/testing/testing-pyramid","01.csharp/11.aspnet/07.testing/02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","/csharp/aspnet/testing/testing-schools","01.csharp/11.aspnet/07.testing/03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","/csharp/aspnet/testing/tdd-and-bdd","01.csharp/11.aspnet/07.testing/04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","/csharp/aspnet/testing/what-to-test","01.csharp/11.aspnet/07.testing/05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","/csharp/aspnet/testing/test-frameworks","01.csharp/11.aspnet/07.testing/06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","/csharp/aspnet/testing/xunit-basics","01.csharp/11.aspnet/07.testing/07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","/csharp/aspnet/testing/xunit-advanced","01.csharp/11.aspnet/07.testing/08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","/csharp/aspnet/testing/mocking-with-moq","01.csharp/11.aspnet/07.testing/09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","/csharp/aspnet/testing/database-testing","01.csharp/11.aspnet/07.testing/10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","/csharp/aspnet/testing/integration-testing","01.csharp/11.aspnet/07.testing/11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","/csharp/aspnet/testing/11a.integration-testing-practice","01.csharp/11.aspnet/07.testing/11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","/csharp/aspnet/testing/integration-testing-advanced","01.csharp/11.aspnet/07.testing/12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","/csharp/aspnet/testing/postman-professional","01.csharp/11.aspnet/07.testing/13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","/csharp/aspnet/testing/httpclient-testing","01.csharp/11.aspnet/07.testing/14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","/csharp/aspnet/testing/wiremock-net","01.csharp/11.aspnet/07.testing/15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","/csharp/aspnet/testing/testing-patterns","01.csharp/11.aspnet/07.testing/16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","/csharp/aspnet/testing/advanced-testing-tools","01.csharp/11.aspnet/07.testing/17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","/csharp/aspnet/testing/architecture-testing","01.csharp/11.aspnet/07.testing/18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","/csharp/aspnet/testing/performance-testing","01.csharp/11.aspnet/07.testing/19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","/csharp/aspnet/testing/remaining_plan","01.csharp/11.aspnet/07.testing/remaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","/csharp/aspnet/payments","01.csharp/11.aspnet/08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","/csharp/aspnet/payments/payment-fundamentals","01.csharp/11.aspnet/08.payments/01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","/csharp/aspnet/payments/payment-methods-ukraine","01.csharp/11.aspnet/08.payments/02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","/csharp/aspnet/payments/pci-dss-security","01.csharp/11.aspnet/08.payments/03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","/csharp/aspnet/payments/payment-architecture","01.csharp/11.aspnet/08.payments/04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","/csharp/aspnet/payments/liqpay-integration","01.csharp/11.aspnet/08.payments/05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","/csharp/aspnet/payments/monobank-acquiring","01.csharp/11.aspnet/08.payments/06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","/csharp/aspnet/payments/stripe-integration","01.csharp/11.aspnet/08.payments/07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","/csharp/aspnet/payments/webhooks-deep-dive","01.csharp/11.aspnet/08.payments/08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","/csharp/aspnet/payments/subscriptions-recurring","01.csharp/11.aspnet/08.payments/09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","/csharp/aspnet/payments/refunds-disputes","01.csharp/11.aspnet/08.payments/10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","/csharp/aspnet/payments/testing-payments","01.csharp/11.aspnet/08.payments/11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","/csharp/aspnet/payments/production-checklist","01.csharp/11.aspnet/08.payments/12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","/csharp/aspnet/libraries","01.csharp/11.aspnet/09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","/csharp/aspnet/libraries/fluent-validation","01.csharp/11.aspnet/09.libraries/01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","/csharp/aspnet/libraries/mapster","01.csharp/11.aspnet/09.libraries/02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","/csharp/aspnet/libraries/erroror-result-pattern","01.csharp/11.aspnet/09.libraries/03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","/csharp/aspnet/libraries/serilog","01.csharp/11.aspnet/09.libraries/04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","/csharp/aspnet/libraries/mediatr","01.csharp/11.aspnet/09.libraries/05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","/csharp/aspnet/libraries/polly","01.csharp/11.aspnet/09.libraries/06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","/csharp/aspnet/libraries/health-checks","01.csharp/11.aspnet/09.libraries/07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","/csharp/aspnet/libraries/feature-management","01.csharp/11.aspnet/09.libraries/08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","/csharp/aspnet/libraries/fluent-email","01.csharp/11.aspnet/09.libraries/09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","/csharp/aspnet/libraries/quest-pdf","01.csharp/11.aspnet/09.libraries/10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","/csharp/aspnet/libraries/bogus","01.csharp/11.aspnet/09.libraries/11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","/csharp/aspnet/libraries/humanizer-guard","01.csharp/11.aspnet/09.libraries/12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","/csharp/aspnet/libraries/plan","01.csharp/11.aspnet/09.libraries/plan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","/csharp/aspnet/razor-pages","01.csharp/11.aspnet/10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","/csharp/aspnet/razor-pages/from-minimal-api","01.csharp/11.aspnet/10.razor-pages/01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","/csharp/aspnet/razor-pages/page-model","01.csharp/11.aspnet/10.razor-pages/02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","/csharp/aspnet/razor-pages/razor-syntax","01.csharp/11.aspnet/10.razor-pages/03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","/csharp/aspnet/razor-pages/tag-helpers","01.csharp/11.aspnet/10.razor-pages/04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","/csharp/aspnet/razor-pages/forms-validation","01.csharp/11.aspnet/10.razor-pages/05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","/csharp/aspnet/razor-pages/project-task-manager","01.csharp/11.aspnet/10.razor-pages/06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","/csharp/aspnet/mvc","01.csharp/11.aspnet/11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","/csharp/aspnet/mvc/mvc-pattern","01.csharp/11.aspnet/11.mvc/01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","/csharp/aspnet/mvc/from-razor-pages","01.csharp/11.aspnet/11.mvc/02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","/csharp/aspnet/mvc/controllers-actions","01.csharp/11.aspnet/11.mvc/03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","/csharp/aspnet/mvc/routing-mvc","01.csharp/11.aspnet/11.mvc/04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","/csharp/aspnet/mvc/model-binding","01.csharp/11.aspnet/11.mvc/05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","/csharp/aspnet/mvc/views-viewdata-tempdata","01.csharp/11.aspnet/11.mvc/06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","/csharp/aspnet/mvc/filters","01.csharp/11.aspnet/11.mvc/07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","/csharp/aspnet/mvc/areas","01.csharp/11.aspnet/11.mvc/08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","/csharp/aspnet/mvc/view-components","01.csharp/11.aspnet/11.mvc/09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","/csharp/aspnet/mvc/display-editor-templates","01.csharp/11.aspnet/11.mvc/10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","/csharp/aspnet/mvc/validation-advanced","01.csharp/11.aspnet/11.mvc/11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","/csharp/aspnet/mvc/htmx","01.csharp/11.aspnet/11.mvc/12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","/csharp/aspnet/mvc/ajax-htmx-mvc","01.csharp/11.aspnet/11.mvc/13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","/csharp/aspnet/mvc/htmx-project","01.csharp/11.aspnet/11.mvc/14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","/csharp/aspnet/mvc/file-upload","01.csharp/11.aspnet/11.mvc/15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","/csharp/aspnet/mvc/globalization-localization","01.csharp/11.aspnet/11.mvc/16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","/csharp/aspnet/mvc/mvc-project","01.csharp/11.aspnet/11.mvc/17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","/csharp/aspnet/mvc/plan","01.csharp/11.aspnet/11.mvc/plan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","/csharp/aspnet/web-api","01.csharp/11.aspnet/12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","/csharp/aspnet/web-api/from-minimal-api-to-controllers","01.csharp/11.aspnet/12.web-api/01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","/csharp/aspnet/web-api/controller-base-actionresult","01.csharp/11.aspnet/12.web-api/02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","/csharp/aspnet/web-api/content-negotiation","01.csharp/11.aspnet/12.web-api/03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","/csharp/aspnet/web-api/api-versioning","01.csharp/11.aspnet/12.web-api/04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","/csharp/aspnet/web-api/problemdetails-error-handling","01.csharp/11.aspnet/12.web-api/05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","/csharp/aspnet/web-api/filters-for-api","01.csharp/11.aspnet/12.web-api/06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","/csharp/aspnet/web-api/pagination-filtering-sorting","01.csharp/11.aspnet/12.web-api/07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","/csharp/aspnet/web-api/hateoas-resource-expansion","01.csharp/11.aspnet/12.web-api/08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","/csharp/aspnet/web-api/minimal-api-vs-controllers-hybrid","01.csharp/11.aspnet/12.web-api/09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","/csharp/aspnet/web-api/api-documentation-generation","01.csharp/11.aspnet/12.web-api/10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","/csharp/aspnet/web-api/health-checks-monitoring","01.csharp/11.aspnet/12.web-api/11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","/csharp/aspnet/web-api/web-api-project","01.csharp/11.aspnet/12.web-api/12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","/csharp/aspnet/web-api/plan","01.csharp/11.aspnet/12.web-api/plan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Desktop UI","i-lucide-app-window","/csharp/desktop-ui","01.csharp/12.desktop-ui",[1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632],{"title":1353,"path":1354,"stem":1355},"Що таке десктопна розробка?","/csharp/desktop-ui/what-is-desktop-dev","01.csharp/12.desktop-ui/01.what-is-desktop-dev",{"title":1357,"path":1358,"stem":1359},"Архітектура WPF — як влаштований графічний інтерфейс","/csharp/desktop-ui/wpf-architecture","01.csharp/12.desktop-ui/02.wpf-architecture",{"title":1361,"path":1362,"stem":1363},"Перший WPF-проєкт — від нуля до вікна","/csharp/desktop-ui/first-wpf-app","01.csharp/12.desktop-ui/03.first-wpf-app",{"title":1365,"path":1366,"stem":1367},"Перший Avalonia-проєкт: WPF для всіх платформ","/csharp/desktop-ui/03a.first-avalonia-app","01.csharp/12.desktop-ui/03a.first-avalonia-app",{"title":1369,"path":1370,"stem":1371},"XAML: декларативний інтерфейс","/csharp/desktop-ui/xaml-basics","01.csharp/12.desktop-ui/04.xaml-basics",{"title":1373,"path":1374,"stem":1375},"Fluent UI у WPF — сучасний дизайн Windows 11","/csharp/desktop-ui/04a.wpf-fluent-ui","01.csharp/12.desktop-ui/04a.wpf-fluent-ui",{"title":1377,"path":1378,"stem":1379},"WPF UI — сучасна бібліотека Fluent контролів","/csharp/desktop-ui/04b.wpf-ui-library","01.csharp/12.desktop-ui/04b.wpf-ui-library",{"title":1381,"path":1382,"stem":1383},"HandyControl — велика бібліотека UI контролів для WPF","/csharp/desktop-ui/04c.handycontrol-library","01.csharp/12.desktop-ui/04c.handycontrol-library",{"title":1385,"path":1386,"stem":1387},"Простори імен та ресурси XAML","/csharp/desktop-ui/xaml-namespaces-resources","01.csharp/12.desktop-ui/05.xaml-namespaces-resources",{"title":1389,"path":1390,"stem":1391},"XAML в Avalonia: ключові відмінності від WPF","/csharp/desktop-ui/05a.avalonia-xaml-differences","01.csharp/12.desktop-ui/05a.avalonia-xaml-differences",{"title":1393,"path":1394,"stem":1395},"Розширення розмітки XAML (Markup Extensions)","/csharp/desktop-ui/xaml-markup-extensions","01.csharp/12.desktop-ui/06.xaml-markup-extensions",{"title":1397,"path":1398,"stem":1399},"Панелі Layout: StackPanel, WrapPanel, DockPanel","/csharp/desktop-ui/layout-panels-part1","01.csharp/12.desktop-ui/07.layout-panels-part1",{"title":1401,"path":1402,"stem":1403},"Grid, Canvas, UniformGrid","/csharp/desktop-ui/layout-panels-part2","01.csharp/12.desktop-ui/07.layout-panels-part2",{"title":1405,"path":1406,"stem":1407},"Просунуті техніки Layout","/csharp/desktop-ui/layout-advanced","01.csharp/12.desktop-ui/08.layout-advanced",{"title":1409,"path":1410,"stem":1411},"Адаптивний Layout та найкращі практики","/csharp/desktop-ui/layout-responsive","01.csharp/12.desktop-ui/09.layout-responsive",{"title":1413,"path":1414,"stem":1415},"Layout в Avalonia: відмінності та нові можливості","/csharp/desktop-ui/09a.layout-avalonia","01.csharp/12.desktop-ui/09a.layout-avalonia",{"title":1417,"path":1418,"stem":1419},"Button, Image, ProgressBar та інші базові контроли","/csharp/desktop-ui/basic-controls","01.csharp/12.desktop-ui/10.basic-controls",{"title":1421,"path":1422,"stem":1423},"Контроли в Avalonia: відмінності від WPF","/csharp/desktop-ui/10a.controls-avalonia","01.csharp/12.desktop-ui/10a.controls-avalonia",{"title":1425,"path":1426,"stem":1427},"Текстові контроли — TextBlock, TextBox, RichTextBox","/csharp/desktop-ui/text-controls","01.csharp/12.desktop-ui/11.text-controls",{"title":1429,"path":1430,"stem":1431},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","/csharp/desktop-ui/selection-controls","01.csharp/12.desktop-ui/12.selection-controls",{"title":1433,"path":1434,"stem":1435},"Content Model — GroupBox, Expander, TabControl, StatusBar","/csharp/desktop-ui/content-controls","01.csharp/12.desktop-ui/13.content-controls",{"title":1437,"path":1438,"stem":1439},"UI/UX принципи десктопних застосунків","/csharp/desktop-ui/13a.ui-ux-principles","01.csharp/12.desktop-ui/13a.ui-ux-principles",{"title":1441,"path":1442,"stem":1443},"Dependency Properties — Концепція та Value Resolution","/csharp/desktop-ui/dependency-properties-part1","01.csharp/12.desktop-ui/14.dependency-properties-part1",{"title":1445,"path":1446,"stem":1447},"Avalonia Property System — StyledProperty та DirectProperty","/csharp/desktop-ui/14a.avalonia-property-system","01.csharp/12.desktop-ui/14a.avalonia-property-system",{"title":1449,"path":1450,"stem":1451},"Attached Properties — Властивості без меж","/csharp/desktop-ui/attached-properties","01.csharp/12.desktop-ui/15.attached-properties",{"title":1453,"path":1454,"stem":1455},"Routed Events — Маршрутизація подій у WPF","/csharp/desktop-ui/routed-events","01.csharp/12.desktop-ui/16.routed-events",{"title":1457,"path":1458,"stem":1459},"Data Binding — Від Code-Behind до Декларативності","/csharp/desktop-ui/data-binding-basics-part1","01.csharp/12.desktop-ui/17.data-binding-basics-part1",{"title":1461,"path":1462,"stem":1463},"INotifyPropertyChanged — Живе оновлення UI","/csharp/desktop-ui/data-binding-basics-part2","01.csharp/12.desktop-ui/17.data-binding-basics-part2",{"title":1465,"path":1466,"stem":1467},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","/csharp/desktop-ui/17a.avalonia-compiled-bindings","01.csharp/12.desktop-ui/17a.avalonia-compiled-bindings",{"title":1469,"path":1470,"stem":1471},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","/csharp/desktop-ui/data-binding-advanced","01.csharp/12.desktop-ui/18.data-binding-advanced",{"title":1473,"path":1474,"stem":1475},"Value Converters — Перетворення типів даних у Data Binding","/csharp/desktop-ui/value-converters","01.csharp/12.desktop-ui/19.value-converters",{"title":1477,"path":1478,"stem":1479},"Data Templates — Візуалізація об'єктів у WPF","/csharp/desktop-ui/data-templates","01.csharp/12.desktop-ui/20.data-templates",{"title":1481,"path":1482,"stem":1483},"Collections Binding Part 1 — ObservableCollection та ItemsControl","/csharp/desktop-ui/collections-binding-part1","01.csharp/12.desktop-ui/21.collections-binding-part1",{"title":1485,"path":1486,"stem":1487},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","/csharp/desktop-ui/collections-binding-part2","01.csharp/12.desktop-ui/21.collections-binding-part2",{"title":1489,"path":1490,"stem":1491},"MVVM Pattern — Від Spaghetti Code до архітектури","/csharp/desktop-ui/mvvm-pattern","01.csharp/12.desktop-ui/22.mvvm-pattern",{"title":1493,"path":1494,"stem":1495},"ViewModel Implementation — Від BaseViewModel до валідації","/csharp/desktop-ui/viewmodel-implementation","01.csharp/12.desktop-ui/23.viewmodel-implementation",{"title":1497,"path":1498,"stem":1499},"Commands — Від event handlers до декларативних команд","/csharp/desktop-ui/commands","01.csharp/12.desktop-ui/24.commands",{"title":1501,"path":1502,"stem":1503},"MVVM Toolkit — MVVM без boilerplate через Source Generators","/csharp/desktop-ui/mvvm-toolkit","01.csharp/12.desktop-ui/25.mvvm-toolkit",{"title":1505,"path":1506,"stem":1507},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","/csharp/desktop-ui/messenger-pattern","01.csharp/12.desktop-ui/26.messenger-pattern",{"title":1509,"path":1510,"stem":1511},"Стилі WPF — CSS для десктопу","/csharp/desktop-ui/styles-basics","01.csharp/12.desktop-ui/27.styles-basics",{"title":1513,"path":1514,"stem":1515},"CSS-like стилі Avalonia","/csharp/desktop-ui/27a.avalonia-css-styling","01.csharp/12.desktop-ui/27a.avalonia-css-styling",{"title":1517,"path":1518,"stem":1519},"Control Templates — Частина 1. Концепція та TemplateBinding","/csharp/desktop-ui/control-templates-part1","01.csharp/12.desktop-ui/28.control-templates-part1",{"title":1521,"path":1522,"stem":1523},"Control Templates — Частина 2. Named Parts та ContentPresenter","/csharp/desktop-ui/control-templates-part2","01.csharp/12.desktop-ui/28.control-templates-part2",{"title":1525,"path":1526,"stem":1527},"Control Themes в Avalonia — нова ера стилізації","/csharp/desktop-ui/28a.avalonia-control-themes","01.csharp/12.desktop-ui/28a.avalonia-control-themes",{"title":1529,"path":1530,"stem":1531},"Triggers та Visual State Manager у WPF","/csharp/desktop-ui/triggers-visual-states","01.csharp/12.desktop-ui/29.triggers-visual-states",{"title":1533,"path":1534,"stem":1535},"Pseudo-classes в Avalonia — замість WPF Triggers","/csharp/desktop-ui/29a.avalonia-pseudo-classes","01.csharp/12.desktop-ui/29a.avalonia-pseudo-classes",{"title":1537,"path":1538,"stem":1539},"Теми та ресурсні словники у WPF","/csharp/desktop-ui/resources-themes","01.csharp/12.desktop-ui/30.resources-themes",{"title":1541,"path":1542,"stem":1543},"Avalonia Themes — Fluent Design та система тематизації","/csharp/desktop-ui/30a.avalonia-themes-fluent","01.csharp/12.desktop-ui/30a.avalonia-themes-fluent",{"title":1545,"path":1546,"stem":1547},"Контроли колекцій — глибоке занурення","/csharp/desktop-ui/collection-controls","01.csharp/12.desktop-ui/31.collection-controls",{"title":1549,"path":1550,"stem":1551},"DataGrid — колонки та базове відображення","/csharp/desktop-ui/datagrid-part1","01.csharp/12.desktop-ui/32.datagrid-part1",{"title":1553,"path":1554,"stem":1555},"DataGrid — сортування, фільтрація, редагування","/csharp/desktop-ui/datagrid-part2","01.csharp/12.desktop-ui/32.datagrid-part2",{"title":1557,"path":1558,"stem":1559},"TreeView та GridView","/csharp/desktop-ui/treeview-listview","01.csharp/12.desktop-ui/33.treeview-listview",{"title":1561,"path":1562,"stem":1563},"Меню, Toolbar, ContextMenu, StatusBar","/csharp/desktop-ui/menus-toolbars","01.csharp/12.desktop-ui/34.menus-toolbars",{"title":1565,"path":1566,"stem":1567},"Навігація та керування вікнами. Частина 1: вікна та сторінки","/csharp/desktop-ui/navigation-windows-part1","01.csharp/12.desktop-ui/35.navigation-windows-part1",{"title":1569,"path":1570,"stem":1571},"Навігація та керування вікнами. Частина 2: MVVM-навігація","/csharp/desktop-ui/navigation-windows-part2","01.csharp/12.desktop-ui/35.navigation-windows-part2",{"title":1573,"path":1574,"stem":1575},"Avalonia — Навігація та діалоги","/csharp/desktop-ui/35a.avalonia-navigation-dialogs","01.csharp/12.desktop-ui/35a.avalonia-navigation-dialogs",{"title":1577,"path":1578,"stem":1579},"Діалоги та File Pickers у WPF","/csharp/desktop-ui/dialogs-file-pickers","01.csharp/12.desktop-ui/36.dialogs-file-pickers",{"title":1581,"path":1582,"stem":1583},"UserControl: компонентний підхід у WPF","/csharp/desktop-ui/user-controls","01.csharp/12.desktop-ui/37.user-controls",{"title":1585,"path":1586,"stem":1587},"Custom Controls: Lookless Controls у WPF","/csharp/desktop-ui/custom-controls","01.csharp/12.desktop-ui/38.custom-controls",{"title":1589,"path":1590,"stem":1591},"Avalonia TemplatedControl — Lookless Controls","/csharp/desktop-ui/38a.avalonia-templated-controls","01.csharp/12.desktop-ui/38a.avalonia-templated-controls",{"title":1593,"path":1594,"stem":1595},"Анімації у WPF: Storyboard та Easing Functions","/csharp/desktop-ui/animations-transitions","01.csharp/12.desktop-ui/39.animations-transitions",{"title":1597,"path":1598,"stem":1599},"Анімації в Avalonia","/csharp/desktop-ui/39a.avalonia-animations","01.csharp/12.desktop-ui/39a.avalonia-animations",{"title":1601,"path":1602,"stem":1603},"2D Графіка та Мультимедіа у WPF","/csharp/desktop-ui/media-graphics","01.csharp/12.desktop-ui/40.media-graphics",{"title":1605,"path":1606,"stem":1607},"Dependency Injection у WPF та Avalonia","/csharp/desktop-ui/di-integration","01.csharp/12.desktop-ui/41.di-integration",{"title":1609,"path":1610,"stem":1611},"SQLite та EF Core у десктопних додатках","/csharp/desktop-ui/data-persistence-part1","01.csharp/12.desktop-ui/42.data-persistence-part1",{"title":1613,"path":1614,"stem":1615},"Repository Pattern та Unit of Work","/csharp/desktop-ui/data-persistence-part2","01.csharp/12.desktop-ui/43.data-persistence-part2",{"title":1617,"path":1618,"stem":1619},"Тестування ViewModels","/csharp/desktop-ui/viewmodel-testing","01.csharp/12.desktop-ui/44.viewmodel-testing",{"title":1621,"path":1622,"stem":1623},"Avalonia Headless Testing — тестування UI без вікон","/csharp/desktop-ui/44a.avalonia-headless-testing","01.csharp/12.desktop-ui/44a.avalonia-headless-testing",{"title":1625,"path":1626,"stem":1627},"Кросплатформна розробка з Avalonia","/csharp/desktop-ui/avalonia-cross-platform","01.csharp/12.desktop-ui/45.avalonia-cross-platform",{"title":1629,"path":1630,"stem":1631},"Пакування та розгортання Avalonia додатків","/csharp/desktop-ui/avalonia-packaging-deployment","01.csharp/12.desktop-ui/46.avalonia-packaging-deployment",{"title":1633,"path":1634,"stem":1635},"Розгортання WPF застосунків","/csharp/desktop-ui/wpf-packaging-deployment","01.csharp/12.desktop-ui/47.wpf-packaging-deployment",{"title":1637,"path":1638,"stem":1639},"C# & .NET: The Ultimate Roadmap","/csharp/roadmap","01.csharp/roadmap",{"title":1641,"icon":1642,"path":1643,"stem":1644,"children":1645,"page":59},"C++","i-devicon-cplusplus","/cpp","02.cpp",[1646,1650,1654,1658,1662,1666,1670,1674,1678,1681,1685,1689,1693,1697,1701,1705,1709,1713,1717,1721,1725,1729,1733,1737,1741,1745,1749,1753,1757,1761],{"title":1647,"path":1648,"stem":1649},"Вступ у програмування та алгоритми","/cpp/intro-algorithms","02.cpp/01.intro-algorithms",{"title":1651,"path":1652,"stem":1653},"Code Style: угоди про оформлення коду","/cpp/code-style","02.cpp/02.code-style",{"title":1655,"path":1656,"stem":1657},"Середовище розробки та перший проєкт","/cpp/ide-setup","02.cpp/03.ide-setup",{"title":1659,"path":1660,"stem":1661},"Вивід даних на екран","/cpp/data-output","02.cpp/04.data-output",{"title":1663,"path":1664,"stem":1665},"Типи даних, змінні та константи","/cpp/data-types-variables","02.cpp/05.data-types-variables",{"title":1667,"path":1668,"stem":1669},"Ввід даних з клавіатури","/cpp/data-input","02.cpp/06.data-input",{"title":1671,"path":1672,"stem":1673},"Оператори, перетворення типів та логічні операції","/cpp/operators-type-conversion","02.cpp/07.operators-type-conversion",{"title":1675,"path":1676,"stem":1677},"Цикли","/cpp/loops","02.cpp/08.loops",{"title":32,"path":1679,"stem":1680},"/cpp/arrays","02.cpp/09.arrays",{"title":1682,"path":1683,"stem":1684},"Алгоритми сортування та аналіз складності","/cpp/sorting","02.cpp/10.sorting",{"title":1686,"path":1687,"stem":1688},"Алгоритми пошуку","/cpp/searching","02.cpp/11.searching",{"title":1690,"path":1691,"stem":1692},"Функції: основи","/cpp/functions-basics","02.cpp/12.functions-basics",{"title":1694,"path":1695,"stem":1696},"Функції: прототипи, область видимості та додаткові можливості","/cpp/functions-scope","02.cpp/13.functions-scope",{"title":1698,"path":1699,"stem":1700},"Функції: перевантаження та шаблони","/cpp/functions-overloading-templates","02.cpp/14.functions-overloading-templates",{"title":1702,"path":1703,"stem":1704},"Вказівники: основи","/cpp/pointers-basics","02.cpp/15.pointers-basics",{"title":1706,"path":1707,"stem":1708},"Посилання (References)","/cpp/references","02.cpp/16.references",{"title":1710,"path":1711,"stem":1712},"Вказівники, const і масиви","/cpp/pointers-const-arrays","02.cpp/17.pointers-const-arrays",{"title":1714,"path":1715,"stem":1716},"Адресна арифметика","/cpp/pointer-arithmetic","02.cpp/18.pointer-arithmetic",{"title":1718,"path":1719,"stem":1720},"Динамічна пам'ять","/cpp/dynamic-memory","02.cpp/19.dynamic-memory",{"title":1722,"path":1723,"stem":1724},"Вказівники типу void","/cpp/void-pointers","02.cpp/20.void-pointers",{"title":1726,"path":1727,"stem":1728},"Вказівники на вказівники","/cpp/pointers-to-pointers","02.cpp/21.pointers-to-pointers",{"title":1730,"path":1731,"stem":1732},"Оператор доступу до членів через вказівник (->)","/cpp/member-access-operator","02.cpp/22.member-access-operator",{"title":1734,"path":1735,"stem":1736},"Цикл for-each (Range-based for)","/cpp/foreach-loop","02.cpp/23.foreach-loop",{"title":1738,"path":1739,"stem":1740},"Вказівники на функції","/cpp/function-pointers","02.cpp/24.function-pointers",{"title":1742,"path":1743,"stem":1744},"Лямбда-вирази","/cpp/lambdas","02.cpp/25.lambdas",{"title":1746,"path":1747,"stem":1748},"Лямбда-захоплення","/cpp/lambda-captures","02.cpp/26.lambda-captures",{"title":1750,"path":1751,"stem":1752},"Еліпсис","/cpp/ellipsis","02.cpp/27.ellipsis",{"title":1754,"path":1755,"stem":1756},"Аргументи командного рядка","/cpp/command-line-arguments","02.cpp/28.command-line-arguments",{"title":1758,"path":1759,"stem":1760},"Перерахування (enum)","/cpp/enum","02.cpp/29.enum",{"title":1762,"path":1763,"stem":1764},"План навчання: Курс C++ — Продовження (Статті 29–60+)","/cpp/curriculum-plan","02.cpp/curriculum-plan",{"title":1766,"icon":1767,"path":1768,"stem":1769,"children":1770,"page":59},"JavaScript","i-devicon-javascript","/javascript","03.javascript",[1771,1797,1851,1873,2177,2215],{"title":1772,"icon":1773,"path":1774,"stem":1775,"children":1776,"page":59},"Events","i-lucide-mouse-pointer-click","/javascript/events","03.javascript/01.events",[1777,1781,1785,1789,1793],{"title":1778,"path":1779,"stem":1780},"Вступ до подій браузера","/javascript/events/intro","03.javascript/01.events/01.intro",{"title":1782,"path":1783,"stem":1784},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","/javascript/events/bubbling-capturing","03.javascript/01.events/02.bubbling-capturing",{"title":1786,"path":1787,"stem":1788},"Делегування подій (Event Delegation)","/javascript/events/delegate-events","03.javascript/01.events/03.delegate-events",{"title":1790,"path":1791,"stem":1792},"Типові дії браузера та preventDefault()","/javascript/events/prevent-default","03.javascript/01.events/04.prevent-default",{"title":1794,"path":1795,"stem":1796},"Запуск користувацьких подій (Custom Events)","/javascript/events/custom-events","03.javascript/01.events/05.custom-events",{"title":1798,"icon":1799,"path":1800,"stem":1801,"children":1802,"page":59},"Network","i-lucide-globe","/javascript/network","03.javascript/02.network",[1803,1807,1811,1815,1819,1823,1827,1831,1835,1839,1843,1847],{"title":1804,"path":1805,"stem":1806},"Fetch API - Сучасний підхід до HTTP-запитів","/javascript/network/01-fetch-api","03.javascript/02.network/01-fetch-api",{"title":1808,"path":1809,"stem":1810},"FormData - Робота з формами та файлами","/javascript/network/02-formdata","03.javascript/02.network/02-formdata",{"title":1812,"path":1813,"stem":1814},"Відстеження прогресу завантаження","/javascript/network/03-download-progress","03.javascript/02.network/03-download-progress",{"title":1816,"path":1817,"stem":1818},"Переривання fetch-запитів","/javascript/network/04-abort-requests","03.javascript/02.network/04-abort-requests",{"title":1820,"path":1821,"stem":1822},"CORS - Запити між різними джерелами","/javascript/network/05-cors","03.javascript/02.network/05-cors",{"title":1824,"path":1825,"stem":1826},"Fetch API - Повний довідник опцій","/javascript/network/06-fetch-options","03.javascript/02.network/06-fetch-options",{"title":1828,"path":1829,"stem":1830},"URL Objects - Робота з посиланнями","/javascript/network/07-url-objects","03.javascript/02.network/07-url-objects",{"title":1832,"path":1833,"stem":1834},"XMLHttpRequest - AJAX та низькорівневі запити","/javascript/network/08-xmlhttprequest","03.javascript/02.network/08-xmlhttprequest",{"title":1836,"path":1837,"stem":1838},"Відновлюване завантаження файлів","/javascript/network/09-resumable-upload","03.javascript/02.network/09-resumable-upload",{"title":1840,"path":1841,"stem":1842},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","/javascript/network/10-cookies","03.javascript/02.network/10-cookies",{"title":1844,"path":1845,"stem":1846},"js-cookie: Керування Cookies без Болю","/javascript/network/11-js-cookie","03.javascript/02.network/11-js-cookie",{"title":1848,"path":1849,"stem":1850},"Axios: Потужний HTTP-клієнт для JavaScript","/javascript/network/12-axios","03.javascript/02.network/12-axios",{"title":1852,"icon":1853,"path":1854,"stem":1855,"children":1856,"page":59},"Bom","i-lucide-monitor","/javascript/bom","03.javascript/03.bom",[1857,1861,1865,1869],{"title":1858,"path":1859,"stem":1860},"LocalStorage, SessionStorage та patterns збереження даних","/javascript/bom/01-localstorage","03.javascript/03.bom/01-localstorage",{"title":1862,"path":1863,"stem":1864},"Location Object - Керування адресою сторінки","/javascript/bom/02-location-object","03.javascript/03.bom/02-location-object",{"title":1866,"path":1867,"stem":1868},"History API - Керування історією браузера","/javascript/bom/03-history-api","03.javascript/03.bom/03-history-api",{"title":1870,"path":1871,"stem":1872},"Navigator Object - Ідентифікація та Можливості Пристрою","/javascript/bom/04-navigator-object","03.javascript/03.bom/04-navigator-object",{"title":1874,"icon":1875,"path":1876,"stem":1877,"children":1878},"React","i-devicon-react","/javascript/react","03.javascript/04.react/index",[1879,1880,1884,1888,1892,1896,1959,1994,2146],{"title":1874,"path":1876,"stem":1877},{"title":1881,"path":1882,"stem":1883},"Робота з Формами в React","/javascript/react/react-forms","03.javascript/04.react/01.react-forms",{"title":1885,"path":1886,"stem":1887},"React Hook Form: Професійна Робота з Формами","/javascript/react/react-hook-form","03.javascript/04.react/02.react-hook-form",{"title":1889,"path":1890,"stem":1891},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","/javascript/react/react-hook-form-new","03.javascript/04.react/02.react-hook-form-new",{"title":1893,"path":1894,"stem":1895},"Axios та React: Професійна Архітектура Запитів","/javascript/react/data-fetching-axios","03.javascript/04.react/03.data-fetching-axios",{"title":1897,"icon":132,"path":1898,"stem":1899,"children":1900},"Tanstack Query","/javascript/react/tanstack-query","03.javascript/04.react/04.tanstack-query/index",[1901,1903,1907,1911,1915,1919,1923,1927,1931,1935,1939,1943,1947,1951,1955],{"title":1902,"path":1898,"stem":1899},"TanStack Query: Майстерність Керування Станом Сервера",{"title":1904,"path":1905,"stem":1906},"Парадигма Server State: Чому useEffect недостатньо","/javascript/react/tanstack-query/server-state-paradigm","03.javascript/04.react/04.tanstack-query/01.server-state-paradigm",{"title":1908,"path":1909,"stem":1910},"Встановлення та Налаштування: Фундамент","/javascript/react/tanstack-query/installation-and-devtools","03.javascript/04.react/04.tanstack-query/02.installation-and-devtools",{"title":1912,"path":1913,"stem":1914},"Основи Запитів та Магія Ключів","/javascript/react/tanstack-query/query-basics-and-keys","03.javascript/04.react/04.tanstack-query/03.query-basics-and-keys",{"title":1916,"path":1917,"stem":1918},"Синхронізація Даних: Життєвий Цикл Запиту","/javascript/react/tanstack-query/data-synchronization","03.javascript/04.react/04.tanstack-query/04.data-synchronization",{"title":1920,"path":1921,"stem":1922},"Мутації та Інвалідація: Зміна Даних","/javascript/react/tanstack-query/mutations-and-invalidation","03.javascript/04.react/04.tanstack-query/05.mutations-and-invalidation",{"title":1924,"path":1925,"stem":1926},"Оптимістичні Оновлення: Швидше за Світло","/javascript/react/tanstack-query/optimistic-updates","03.javascript/04.react/04.tanstack-query/06.optimistic-updates",{"title":1928,"path":1929,"stem":1930},"Пагінація та Infinite Scroll","/javascript/react/tanstack-query/pagination-and-load-more","03.javascript/04.react/04.tanstack-query/07.pagination-and-load-more",{"title":1932,"path":1933,"stem":1934},"Просунуті Патерни та Оптимізація","/javascript/react/tanstack-query/advanced-patterns","03.javascript/04.react/04.tanstack-query/08.advanced-patterns",{"title":1936,"path":1937,"stem":1938},"Архітектура та Best Practices","/javascript/react/tanstack-query/architecture-and-best-practices","03.javascript/04.react/04.tanstack-query/09.architecture-and-best-practices",{"title":1940,"path":1941,"stem":1942},"Server-Side Rendering (SSR) та Гідратація","/javascript/react/tanstack-query/server-side-rendering","03.javascript/04.react/04.tanstack-query/10.server-side-rendering",{"title":1944,"path":1945,"stem":1946},"Стратегії Тестування","/javascript/react/tanstack-query/testing-strategies","03.javascript/04.react/04.tanstack-query/11.testing-strategies",{"title":1948,"path":1949,"stem":1950},"Аутентифікація та Обробка Помилок","/javascript/react/tanstack-query/authentication-and-errors","03.javascript/04.react/04.tanstack-query/12.authentication-and-errors",{"title":1952,"path":1953,"stem":1954},"React Suspense та Майбутнє","/javascript/react/tanstack-query/react-suspense","03.javascript/04.react/04.tanstack-query/13.react-suspense",{"title":1956,"path":1957,"stem":1958},"Глибоке Занурення в Продуктивність","/javascript/react/tanstack-query/performance-deep-dive","03.javascript/04.react/04.tanstack-query/14.performance-deep-dive",{"title":1960,"icon":1875,"path":1961,"stem":1962,"children":1963},"React Router","/javascript/react/react-router","03.javascript/04.react/05.react-router/index",[1964,1966,1970,1974,1978,1982,1986,1990],{"title":1965,"path":1961,"stem":1962},"React Router: Навігаційна система сучасного вебу",{"title":1967,"path":1968,"stem":1969},"Налаштування та Базовий Роутинг","/javascript/react/react-router/setup-and-basic-routing","03.javascript/04.react/05.react-router/01.setup-and-basic-routing",{"title":1971,"path":1972,"stem":1973},"Динамічна Навігація","/javascript/react/react-router/navigation-and-links","03.javascript/04.react/05.react-router/02.navigation-and-links",{"title":1975,"path":1976,"stem":1977},"Вкладені Маршрути та Макети","/javascript/react/react-router/nested-routes-and-layouts","03.javascript/04.react/05.react-router/03.nested-routes-and-layouts",{"title":1979,"path":1980,"stem":1981},"Динамічні Маршрути та Параметри","/javascript/react/react-router/dynamic-routing","03.javascript/04.react/05.react-router/04.dynamic-routing",{"title":1983,"path":1984,"stem":1985},"Data APIs: Loaders та Actions","/javascript/react/react-router/data-loading","03.javascript/04.react/05.react-router/05.data-loading",{"title":1987,"path":1988,"stem":1989},"Просунуті Патерни","/javascript/react/react-router/advanced-patterns","03.javascript/04.react/05.react-router/06.advanced-patterns",{"title":1991,"path":1992,"stem":1993},"Legacy Routing: Компонентний підхід","/javascript/react/react-router/legacy-routing","03.javascript/04.react/05.react-router/07.legacy-routing",{"title":1995,"icon":132,"path":1996,"stem":1997,"children":1998},"Redux","/javascript/react/redux","03.javascript/04.react/06.redux/index",[1999,2001,2017,2046,2055,2076,2092,2121],{"title":2000,"path":1996,"stem":1997},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2002,"stem":2003,"children":2004,"page":59},"/javascript/react/redux/fundamentals","03.javascript/04.react/06.redux/01.fundamentals",[2005,2009,2013],{"title":2006,"path":2007,"stem":2008},"Вступ до State Management","/javascript/react/redux/fundamentals/intro-state-management","03.javascript/04.react/06.redux/01.fundamentals/01.intro-state-management",{"title":2010,"path":2011,"stem":2012},"Філософія Redux та Три Принципи","/javascript/react/redux/fundamentals/redux-philosophy","03.javascript/04.react/06.redux/01.fundamentals/02.redux-philosophy",{"title":2014,"path":2015,"stem":2016},"Чисті функції та Іммутабельність","/javascript/react/redux/fundamentals/pure-functions-immutability","03.javascript/04.react/06.redux/01.fundamentals/03.pure-functions-immutability",{"title":2018,"icon":132,"path":2019,"stem":2020,"children":2021,"page":59},"Classic Redux","/javascript/react/redux/classic-redux","03.javascript/04.react/06.redux/02.classic-redux",[2022,2026,2030,2034,2038,2042],{"title":2023,"path":2024,"stem":2025},"Створення Store (Classic Redux)","/javascript/react/redux/classic-redux/store-setup","03.javascript/04.react/06.redux/02.classic-redux/01.store-setup",{"title":2027,"path":2028,"stem":2029},"Actions, Constants та Action Creators","/javascript/react/redux/classic-redux/actions-constants","03.javascript/04.react/06.redux/02.classic-redux/02.actions-constants",{"title":2031,"path":2032,"stem":2033},"Логіка Reducers","/javascript/react/redux/classic-redux/reducers","03.javascript/04.react/06.redux/02.classic-redux/03.reducers",{"title":2035,"path":2036,"stem":2037},"Комбінування Reducers (Root Reducer)","/javascript/react/redux/classic-redux/data-flow","03.javascript/04.react/06.redux/02.classic-redux/04.data-flow",{"title":2039,"path":2040,"stem":2041},"Підключення до React (React-Redux)","/javascript/react/redux/classic-redux/react-redux-connection","03.javascript/04.react/06.redux/02.classic-redux/05.react-redux-connection",{"title":2043,"path":2044,"stem":2045},"Middleware та Асинхронність (Redux Thunk)","/javascript/react/redux/classic-redux/middleware-thunk","03.javascript/04.react/06.redux/02.classic-redux/06.middleware-thunk",{"title":2047,"icon":132,"path":2048,"stem":2049,"children":2050,"page":59},"Transition To Rtk","/javascript/react/redux/transition-to-rtk","03.javascript/04.react/06.redux/03.transition-to-rtk",[2051],{"title":2052,"path":2053,"stem":2054},"Проблеми класичного Redux","/javascript/react/redux/transition-to-rtk/problems-with-classic","03.javascript/04.react/06.redux/03.transition-to-rtk/01.problems-with-classic",{"title":2056,"icon":132,"path":2057,"stem":2058,"children":2059,"page":59},"Redux Toolkit","/javascript/react/redux/redux-toolkit","03.javascript/04.react/06.redux/04.redux-toolkit",[2060,2064,2068,2072],{"title":2061,"path":2062,"stem":2063},"Налаштування Store з configureStore","/javascript/react/redux/redux-toolkit/configure-store","03.javascript/04.react/06.redux/04.redux-toolkit/01.configure-store",{"title":2065,"path":2066,"stem":2067},"createSlice: Революція в Redux","/javascript/react/redux/redux-toolkit/create-slice","03.javascript/04.react/06.redux/04.redux-toolkit/02.create-slice",{"title":2069,"path":2070,"stem":2071},"Асинхронність з createAsyncThunk","/javascript/react/redux/redux-toolkit/async-thunks","03.javascript/04.react/06.redux/04.redux-toolkit/03.async-thunks",{"title":2073,"path":2074,"stem":2075},"04. Entity Adapter: Керування нормалізованим станом","/javascript/react/redux/redux-toolkit/entity-adapter","03.javascript/04.react/06.redux/04.redux-toolkit/04.entity-adapter",{"title":2077,"icon":92,"path":2078,"stem":2079,"children":2080,"page":59},"Advanced","/javascript/react/redux/advanced","03.javascript/04.react/06.redux/05.advanced",[2081,2085,2089],{"title":2082,"path":2083,"stem":2084},"Мемоізація та Селектори: Повний Гайд по Reselect","/javascript/react/redux/advanced/selectors-reselect","03.javascript/04.react/06.redux/05.advanced/01.selectors-reselect",{"title":2086,"path":2087,"stem":2088},"RTK Query: Архітектура Серверного Кешу","/javascript/react/redux/advanced/rtk-query-intro","03.javascript/04.react/06.redux/05.advanced/02.rtk-query-intro",{"title":1936,"path":2090,"stem":2091},"/javascript/react/redux/advanced/architecture-best-practices","03.javascript/04.react/06.redux/05.advanced/03.architecture-best-practices",{"title":2093,"icon":132,"path":2094,"stem":2095,"children":2096,"page":59},"Project Kanban","/javascript/react/redux/project-kanban","03.javascript/04.react/06.redux/06.project-kanban",[2097,2101,2105,2109,2113,2117],{"title":2098,"path":2099,"stem":2100},"Проєкт: Kanban Board (Trello Clone)","/javascript/react/redux/project-kanban/project-overview","03.javascript/04.react/06.redux/06.project-kanban/01.project-overview",{"title":2102,"path":2103,"stem":2104},"Налаштування та Типізація","/javascript/react/redux/project-kanban/setup-and-types","03.javascript/04.react/06.redux/06.project-kanban/02.setup-and-types",{"title":2106,"path":2107,"stem":2108},"Board Slice: Серце Дошки","/javascript/react/redux/project-kanban/board-slice","03.javascript/04.react/06.redux/06.project-kanban/03.board-slice",{"title":2110,"path":2111,"stem":2112},"Логіка Drag & Drop","/javascript/react/redux/project-kanban/drag-and-drop-logic","03.javascript/04.react/06.redux/06.project-kanban/04.drag-and-drop-logic",{"title":2114,"path":2115,"stem":2116},"Інтеграція з RTK Query","/javascript/react/redux/project-kanban/rtk-query-integration","03.javascript/04.react/06.redux/06.project-kanban/05.rtk-query-integration",{"title":2118,"path":2119,"stem":2120},"Optimistic Updates","/javascript/react/redux/project-kanban/optimistic-updates","03.javascript/04.react/06.redux/06.project-kanban/06.optimistic-updates",{"title":2122,"icon":132,"path":2123,"stem":2124,"children":2125,"page":59},"Testing","/javascript/react/redux/testing","03.javascript/04.react/06.redux/07.testing",[2126,2130,2134,2138,2142],{"title":2127,"path":2128,"stem":2129},"Тестування Redux","/javascript/react/redux/testing/intro-testing","03.javascript/04.react/06.redux/07.testing/01.intro-testing",{"title":2131,"path":2132,"stem":2133},"Тестування Reducers","/javascript/react/redux/testing/testing-reducers","03.javascript/04.react/06.redux/07.testing/02.testing-reducers",{"title":2135,"path":2136,"stem":2137},"Тестування Селекторів","/javascript/react/redux/testing/testing-selectors","03.javascript/04.react/06.redux/07.testing/03.testing-selectors",{"title":2139,"path":2140,"stem":2141},"Тестування Компонентів (Integration)","/javascript/react/redux/testing/testing-components","03.javascript/04.react/06.redux/07.testing/04.testing-components",{"title":2143,"path":2144,"stem":2145},"Тестування Async Thunks","/javascript/react/redux/testing/testing-thunks","03.javascript/04.react/06.redux/07.testing/05.testing-thunks",{"title":2147,"icon":132,"path":2148,"stem":2149,"children":2150},"Ui Libraries","/javascript/react/ui-libraries","03.javascript/04.react/07.ui-libraries/index",[2151,2153,2157,2161,2165,2169,2173],{"title":2152,"path":2148,"stem":2149},"UI Бібліотеки в React",{"title":2154,"path":2155,"stem":2156},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","/javascript/react/ui-libraries/introduction-to-ui-libraries","03.javascript/04.react/07.ui-libraries/01.introduction-to-ui-libraries",{"title":2158,"path":2159,"stem":2160},"Філософія shadcn/ui: \"Not a Component Library\"","/javascript/react/ui-libraries/shadcn-philosophy","03.javascript/04.react/07.ui-libraries/02.shadcn-philosophy",{"title":2162,"path":2163,"stem":2164},"Установка та Налаштування shadcn/ui","/javascript/react/ui-libraries/shadcn-installation","03.javascript/04.react/07.ui-libraries/03.shadcn-installation",{"title":2166,"path":2167,"stem":2168},"Базові Компоненти shadcn/ui: Фундамент Інтерфейсу","/javascript/react/ui-libraries/shadcn-components-basics","03.javascript/04.react/07.ui-libraries/04.shadcn-components-basics",{"title":2170,"path":2171,"stem":2172},"Компоненти Форм: Побудова Інтерактивних Form","/javascript/react/ui-libraries/shadcn-components-forms","03.javascript/04.react/07.ui-libraries/05.shadcn-components-forms",{"title":2174,"path":2175,"stem":2176},"Складні Компоненти: Dialog, Dropdown, Table та Command","/javascript/react/ui-libraries/shadcn-components-advanced","03.javascript/04.react/07.ui-libraries/06.shadcn-components-advanced",{"title":2178,"icon":2179,"path":2180,"stem":2181,"children":2182,"page":59},"TypeScript","i-devicon-typescript","/javascript/typescript","03.javascript/05.typescript",[2183,2187,2191,2195,2199,2203,2207,2211],{"title":2184,"path":2185,"stem":2186},"TypeScript: Броня для вашого коду","/javascript/typescript/intro-and-basic-types","03.javascript/05.typescript/01.intro-and-basic-types",{"title":2188,"path":2189,"stem":2190},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","/javascript/typescript/interfaces-and-advanced-types","03.javascript/05.typescript/02.interfaces-and-advanced-types",{"title":2192,"path":2193,"stem":2194},"Алхімія Типів: Generics та Utility Types","/javascript/typescript/generics-and-utilities","03.javascript/05.typescript/03.generics-and-utilities",{"title":2196,"path":2197,"stem":2198},"Архітектура та Шаблони: Класи в TypeScript","/javascript/typescript/classes-and-oop","03.javascript/05.typescript/04.classes-and-oop",{"title":2200,"path":2201,"stem":2202},"Продакшн та Екосистема: Advanced Config & Workflow","/javascript/typescript/advanced-patterns-and-config","03.javascript/05.typescript/05.advanced-patterns-and-config",{"title":2204,"path":2205,"stem":2206},"TypeScript у світі React","/javascript/typescript/react-basics","03.javascript/05.typescript/06.react-basics",{"title":2208,"path":2209,"stem":2210},"React + TypeScript: Продвинуті патерни","/javascript/typescript/react-advanced","03.javascript/05.typescript/07.react-advanced",{"title":2212,"path":2213,"stem":2214},"React + TypeScript: Екосистема та бібліотеки","/javascript/typescript/react-ecosystem","03.javascript/05.typescript/08.react-ecosystem",{"title":2216,"path":2217,"stem":2218},"Atomic Design","/javascript/atomic-design","03.javascript/2.atomic-design",{"title":2220,"icon":2221,"path":2222,"stem":2223,"children":2224,"page":59},"Java","i-devicon-java","/java","04.java",[2225,2228,2231,2235,2239,2243,2247],{"title":162,"path":2226,"stem":2227},"/java/data-mapper-part1","04.java/01.data-mapper-part1",{"title":166,"path":2229,"stem":2230},"/java/data-mapper-part2","04.java/02.data-mapper-part2",{"title":2232,"path":2233,"stem":2234},"Service Layer: Організація бізнес-логіки","/java/service-layer","04.java/03.service-layer",{"title":2236,"path":2237,"stem":2238},"Rich Domain Model та State Pattern","/java/rich-domain-model","04.java/04.rich-domain-model",{"title":2240,"path":2241,"stem":2242},"Патерни для складної бізнес-логіки","/java/business-logic-patterns","04.java/05.business-logic-patterns",{"title":2244,"path":2245,"stem":2246},"Обробка помилок та валідація","/java/error-handling-validation","04.java/06.error-handling-validation",{"title":2248,"path":2249,"stem":2250,"children":2251,"page":59},"Проектування баз даних","/java/pr2","04.java/pr2",[2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348],{"title":2253,"path":2254,"stem":2255},"Концептуальне моделювання: Мистецтво розуміння предметної області","/java/pr2/conceptual-modeling","04.java/pr2/01.conceptual-modeling",{"title":2257,"path":2258,"stem":2259},"Логічне моделювання: Від бізнес-ідей до структур даних","/java/pr2/logical-modeling","04.java/pr2/02.logical-modeling",{"title":2261,"path":2262,"stem":2263},"Нормалізація: Гігієна даних та боротьба з аномаліями","/java/pr2/normalization","04.java/pr2/03.normalization",{"title":2265,"path":2266,"stem":2267},"Фізична схема: Від абстракції до DDL","/java/pr2/physical-schema","04.java/pr2/04.physical-schema",{"title":2269,"path":2270,"stem":2271},"Архітектурна класифікація таблиць","/java/pr2/table-classification","04.java/pr2/05.table-classification",{"title":2273,"path":2274,"stem":2275},"Database Migrations: Версіонування схеми з Flyway","/java/pr2/database-migrations","04.java/pr2/06.database-migrations",{"title":2277,"path":2278,"stem":2279},"А що, якби це була не реляційна БД?","/java/pr2/beyond-relational","04.java/pr2/07.beyond-relational",{"title":2281,"path":2282,"stem":2283},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","/java/pr2/impedance-mismatch","04.java/pr2/09.impedance-mismatch",{"title":2285,"path":2286,"stem":2287},"JDBC: Перший контакт із базою даних","/java/pr2/jdbc-fundamentals","04.java/pr2/10.jdbc-fundamentals",{"title":2289,"path":2290,"stem":2291},"Якість коду: Spotless, SpotBugs та SonarQube","/java/pr2/10a.code-quality","04.java/pr2/10a.code-quality",{"title":2293,"path":2294,"stem":2295},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","/java/pr2/connection-pool","04.java/pr2/11.connection-pool",{"title":2297,"path":2298,"stem":2299},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","/java/pr2/row-data-gateway","04.java/pr2/12.row-data-gateway",{"title":2301,"path":2302,"stem":2303},"Table Data Gateway: Фасад таблиці як архітектурний відступ","/java/pr2/table-data-gateway","04.java/pr2/13.table-data-gateway",{"title":2305,"path":2306,"stem":2307},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","/java/pr2/repository-data-mapper","04.java/pr2/14.repository-data-mapper",{"title":2309,"path":2310,"stem":2311},"Identity Map: Кешування сутностей у рамках сесії","/java/pr2/identity-map","04.java/pr2/15.identity-map",{"title":2313,"path":2314,"stem":2315},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","/java/pr2/unit-of-work","04.java/pr2/16.unit-of-work",{"title":2317,"path":2318,"stem":2319},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","/java/pr2/strategy-sql","04.java/pr2/17.strategy-sql",{"title":2321,"path":2322,"stem":2323},"Proxy: Lazy Loading для One-To-Many колекцій","/java/pr2/proxy-lazy-loading","04.java/pr2/18.proxy-lazy-loading",{"title":2325,"path":2326,"stem":2327},"Generic Repository через Java Reflection: анотації та динамічний SQL","/java/pr2/generic-repository-reflection","04.java/pr2/19.generic-repository-reflection",{"title":2329,"path":2330,"stem":2331},"Specification Pattern: Композиція бізнес-правил для складних запитів","/java/pr2/specification-pattern","04.java/pr2/20.specification-pattern",{"title":2333,"path":2334,"stem":2335},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","/java/pr2/20a.advanced-specifications","04.java/pr2/20a.advanced-specifications",{"title":2337,"path":2338,"stem":2339},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","/java/pr2/asynchronous-jdbc","04.java/pr2/21.asynchronous-jdbc",{"title":2341,"path":2342,"stem":2343},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","/java/pr2/integration-testing-h2","04.java/pr2/22.integration-testing-h2",{"title":2345,"path":2346,"stem":2347},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","/java/pr2/integration-testing-testcontainers","04.java/pr2/23.integration-testing-testcontainers",{"title":2349,"path":2350,"stem":2351},"Модуль \"Проектування реляційних баз даних\" для 04.java/pr2","/java/pr2/implementation_plan","04.java/pr2/implementation_plan",{"title":2353,"icon":2354,"path":2355,"stem":2356,"children":2357,"page":59},"Бази даних","i-lucide-database","/databases","06.databases",[2358,2388,2411,2448,2477,2495,2529,2541,2550],{"title":2359,"icon":2360,"path":2361,"stem":2362,"children":2363,"page":59},"Intro","i-lucide-play","/databases/intro","06.databases/01.intro",[2364,2368,2372,2376,2380,2384],{"title":2365,"path":2366,"stem":2367},"Введення в теорію баз даних","/databases/intro/introduction-to-databases","06.databases/01.intro/01.introduction-to-databases",{"title":2369,"path":2370,"stem":2371},"Реляційна модель даних","/databases/intro/relational-model-theory","06.databases/01.intro/02.relational-model-theory",{"title":2373,"path":2374,"stem":2375},"ER-моделювання","/databases/intro/er-modeling","06.databases/01.intro/03.er-modeling",{"title":2377,"path":2378,"stem":2379},"Логічне проектування БД","/databases/intro/logical-schema","06.databases/01.intro/04.logical-schema",{"title":2381,"path":2382,"stem":2383},"Класифікація таблиць","/databases/intro/table-classification","06.databases/01.intro/05.table-classification",{"title":2385,"path":2386,"stem":2387},"PlantUML для баз даних","/databases/intro/plantuml-diagrams","06.databases/01.intro/06.plantuml-diagrams",{"title":2389,"icon":2354,"path":2390,"stem":2391,"children":2392,"page":59},"MS SQL Server Start","/databases/ms-sql-server-start","06.databases/02.ms-sql-server-start",[2393,2397,2403,2407],{"title":2394,"path":2395,"stem":2396},"Типи даних у MS SQL Server","/databases/ms-sql-server-start/data-types","06.databases/02.ms-sql-server-start/01.data-types",{"title":2398,"path":2399,"stem":2400,"children":2401},"Індекси у MS SQL Server","/databases/ms-sql-server-start/sql-indexes","06.databases/02.ms-sql-server-start/02.sql-indexes",[2402],{"title":2398,"path":2399,"stem":2400},{"title":2404,"path":2405,"stem":2406},"Системні бази даних MS SQL Server","/databases/ms-sql-server-start/system-databases","06.databases/02.ms-sql-server-start/03.system-databases",{"title":2408,"path":2409,"stem":2410},"Огляд мови SQL та запитів","/databases/ms-sql-server-start/sql-queries-overview","06.databases/02.ms-sql-server-start/04.sql-queries-overview",{"title":2412,"icon":2354,"path":2413,"stem":2414,"children":2415,"page":59},"SQL","/databases/sql","06.databases/03.sql",[2416,2420,2424,2428,2432,2436,2440,2444],{"title":2417,"path":2418,"stem":2419},"Налаштування демонстраційної бази даних","/databases/sql/sample-database-setup","06.databases/03.sql/00.sample-database-setup",{"title":2421,"path":2422,"stem":2423},"DDL - Створення таблиць (CREATE TABLE)","/databases/sql/ddl-create-table","06.databases/03.sql/01.ddl-create-table",{"title":2425,"path":2426,"stem":2427},"DDL - Зміна та видалення таблиць (ALTER, DROP)","/databases/sql/ddl-alter-drop-table","06.databases/03.sql/02.ddl-alter-drop-table",{"title":2429,"path":2430,"stem":2431},"SELECT запити - Основи","/databases/sql/select-queries-fundamentals","06.databases/03.sql/03.select-queries-fundamentals",{"title":2433,"path":2434,"stem":2435},"SELECT запити - Розширені можливості","/databases/sql/select-queries-advanced","06.databases/03.sql/04.select-queries-advanced",{"title":2437,"path":2438,"stem":2439},"INSERT запити - Додавання даних","/databases/sql/insert-queries","06.databases/03.sql/05.insert-queries",{"title":2441,"path":2442,"stem":2443},"UPDATE та DELETE запити","/databases/sql/update-delete-queries","06.databases/03.sql/06.update-delete-queries",{"title":2445,"path":2446,"stem":2447},"Транзакції в SQL","/databases/sql/transactions","06.databases/03.sql/07.transactions",{"title":2449,"icon":2354,"path":2450,"stem":2451,"children":2452,"page":59},"Multi Table Databases","/databases/multi-table-databases","06.databases/04.multi-table-databases",[2453,2457,2461,2465,2469,2473],{"title":2454,"path":2455,"stem":2456},"Зв'язки та нормалізація БД","/databases/multi-table-databases/relationships-and-normalization","06.databases/04.multi-table-databases/00.relationships-and-normalization",{"title":2458,"path":2459,"stem":2460},"INNER JOIN - З'єднання таблиць","/databases/multi-table-databases/inner-join","06.databases/04.multi-table-databases/01.inner-join",{"title":2462,"path":2463,"stem":2464},"OUTER JOINs - LEFT, RIGHT, FULL","/databases/multi-table-databases/outer-joins","06.databases/04.multi-table-databases/02.outer-joins",{"title":2466,"path":2467,"stem":2468},"CROSS та SELF JOINs","/databases/multi-table-databases/cross-self-joins","06.databases/04.multi-table-databases/03.cross-self-joins",{"title":2470,"path":2471,"stem":2472},"Підзапити (Subqueries)","/databases/multi-table-databases/subqueries","06.databases/04.multi-table-databases/04.subqueries",{"title":2474,"path":2475,"stem":2476},"Агрегації з JOIN","/databases/multi-table-databases/aggregations-with-joins","06.databases/04.multi-table-databases/05.aggregations-with-joins",{"title":2478,"icon":2479,"path":2480,"stem":2481,"children":2482,"page":59},"Aggregate Functions","i-lucide-calculator","/databases/aggregate-functions","06.databases/05.aggregate-functions",[2483,2487,2491],{"title":2484,"path":2485,"stem":2486},"Функції агрегування в MS SQL Server","/databases/aggregate-functions/introduction-aggregate-functions","06.databases/05.aggregate-functions/01.introduction-aggregate-functions",{"title":2488,"path":2489,"stem":2490},"Групування даних в MS SQL Server","/databases/aggregate-functions/grouping-data","06.databases/05.aggregate-functions/02.grouping-data",{"title":2492,"path":2493,"stem":2494},"Підзапити з агрегатними функціями","/databases/aggregate-functions/subqueries-aggregates","06.databases/05.aggregate-functions/03.subqueries-aggregates",{"title":2496,"icon":2497,"path":2498,"stem":2499,"children":2500,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","/databases/triggers-stored-procedures","06.databases/07.triggers-stored-procedures",[2501,2505,2509,2513,2517,2521,2525],{"title":2502,"path":2503,"stem":2504},"DML-тригери","/databases/triggers-stored-procedures/dml-triggers","06.databases/07.triggers-stored-procedures/01.dml-triggers",{"title":2506,"path":2507,"stem":2508},"DDL-тригери","/databases/triggers-stored-procedures/ddl-triggers","06.databases/07.triggers-stored-procedures/02.ddl-triggers",{"title":2510,"path":2511,"stem":2512},"Transact-SQL розширення","/databases/triggers-stored-procedures/transact-sql-extensions","06.databases/07.triggers-stored-procedures/03.transact-sql-extensions",{"title":2514,"path":2515,"stem":2516},"Транзакції","/databases/triggers-stored-procedures/transactions","06.databases/07.triggers-stored-procedures/04.transactions",{"title":2518,"path":2519,"stem":2520},"Зберігаємі процедури","/databases/triggers-stored-procedures/stored-procedures","06.databases/07.triggers-stored-procedures/05.stored-procedures",{"title":2522,"path":2523,"stem":2524},"Користувацькі функції","/databases/triggers-stored-procedures/user-defined-functions","06.databases/07.triggers-stored-procedures/06.user-defined-functions",{"title":2526,"path":2527,"stem":2528},"Безпека баз даних","/databases/triggers-stored-procedures/security","06.databases/07.triggers-stored-procedures/08.security",{"title":2526,"icon":793,"path":2530,"stem":2531,"children":2532,"page":59},"/databases/security","06.databases/08.security",[2533,2537],{"title":2534,"path":2535,"stem":2536},"Вступ до безпеки баз даних","/databases/security/introduction","06.databases/08.security/01.introduction",{"title":2538,"path":2539,"stem":2540},"Системні представлення та метадані","/databases/security/system-views","06.databases/08.security/02.system-views",{"title":2542,"icon":2543,"path":2544,"stem":2545,"children":2546,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","/databases/backup-recovery","06.databases/09.backup-recovery",[2547],{"title":2542,"path":2548,"stem":2549},"/databases/backup-recovery/backup-restore","06.databases/09.backup-recovery/01.backup-restore",{"title":2551,"icon":2552,"path":2553,"stem":2554,"children":2555,"page":59},"Повнотекстовий пошук","i-lucide-search","/databases/full-text-search","06.databases/10.full-text-search",[2556],{"title":2551,"path":2557,"stem":2558},"/databases/full-text-search/full-text-search","06.databases/10.full-text-search/01.full-text-search",{"title":2560,"icon":2561,"path":2562,"stem":2563,"children":2564,"page":59},"Tools","i-lucide-wrench","/tools","07.tools",[2565],{"title":2566,"icon":2567,"path":2568,"stem":2569,"children":2570},"Docker","i-simple-icons-docker","/tools/docker","07.tools/01.docker/index",[2571,2573,2577,2581,2585,2589,2593,2597,2601,2605,2609,2613,2617,2621,2625,2629,2633,2637],{"title":2572,"path":2568,"stem":2569},"Docker: від нуля до production",{"title":2574,"path":2575,"stem":2576},"Контейнеризація — від проблеми до рішення","/tools/docker/containerization-concept","07.tools/01.docker/01.containerization-concept",{"title":2578,"path":2579,"stem":2580},"Docker — що це і навіщо?","/tools/docker/docker-what-and-why","07.tools/01.docker/02.docker-what-and-why",{"title":2582,"path":2583,"stem":2584},"Архітектура Docker Engine","/tools/docker/docker-architecture","07.tools/01.docker/03.docker-architecture",{"title":2586,"path":2587,"stem":2588},"Встановлення Docker","/tools/docker/installation","07.tools/01.docker/04.installation",{"title":2590,"path":2591,"stem":2592},"Перший контейнер — docker run","/tools/docker/first-container","07.tools/01.docker/05.first-container",{"title":2594,"path":2595,"stem":2596},"Життєвий цикл контейнера","/tools/docker/container-lifecycle","07.tools/01.docker/06.container-lifecycle",{"title":2598,"path":2599,"stem":2600},"Docker Images — фундаментальні концепції","/tools/docker/docker-images-fundamentals","07.tools/01.docker/07.docker-images-fundamentals",{"title":2602,"path":2603,"stem":2604},"Dockerfile — основи","/tools/docker/dockerfile-basics","07.tools/01.docker/08.dockerfile-basics",{"title":2606,"path":2607,"stem":2608},"Dockerfile — просунуті техніки","/tools/docker/dockerfile-advanced","07.tools/01.docker/09.dockerfile-advanced",{"title":2610,"path":2611,"stem":2612},"Build Context та кешування шарів","/tools/docker/build-context-and-cache","07.tools/01.docker/10.build-context-and-cache",{"title":2614,"path":2615,"stem":2616},"Реєстри Docker-образів","/tools/docker/image-registries","07.tools/01.docker/11.image-registries",{"title":2618,"path":2619,"stem":2620},"Контейнеризація .NET додатків","/tools/docker/dotnet-containerization","07.tools/01.docker/12.dotnet-containerization",{"title":2622,"path":2623,"stem":2624},"Томи та збереження даних","/tools/docker/volumes-and-data","07.tools/01.docker/13.volumes-and-data",{"title":2626,"path":2627,"stem":2628},"Основи мережі в Docker","/tools/docker/networking-basics","07.tools/01.docker/14.networking-basics",{"title":2630,"path":2631,"stem":2632},"Змінні оточення та конфігурація","/tools/docker/environment-and-configuration","07.tools/01.docker/15.environment-and-configuration",{"title":2634,"path":2635,"stem":2636},"Docker Compose — оркестрація контейнерів","/tools/docker/docker-compose-basics","07.tools/01.docker/16.docker-compose-basics",{"title":2638,"path":2639,"stem":2640},"Docker Compose — Multi-Service застосунки","/tools/docker/compose-multi-service","07.tools/01.docker/17.compose-multi-service",{"title":2642,"icon":2643,"path":2644,"stem":2645,"children":2646,"page":59},"Software Engineering","i-lucide-code-2","/software-engineering","09.software-engineering",[2647,2651,2655,2659,2663,2667,2671,2675,2679,2683,2687],{"title":2648,"path":2649,"stem":2650},"1. Аналіз предметної області. Експертні знання та складність","/software-engineering/intro.subdomains","09.software-engineering/01.intro.subdomains",{"title":2652,"path":2653,"stem":2654},"2. Обмежені контексти. Інтеграція обмежених контекстів","/software-engineering/integrating-limited-contexts","09.software-engineering/02.integrating-limited-contexts",{"title":2656,"path":2657,"stem":2658},"3. Реалізація простої бізнес-логіки","/software-engineering/simple","09.software-engineering/03.simple",{"title":2660,"path":2661,"stem":2662},"4. Опрацювання складної бізнес-логіки","/software-engineering/complex-business-logic","09.software-engineering/04.complex-business-logic",{"title":2664,"path":2665,"stem":2666},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","/software-engineering/modelling-the-time-factor","09.software-engineering/05.modelling-the-time-factor",{"title":2668,"path":2669,"stem":2670},"6. Архітектурні патерни","/software-engineering/architectural-patterns","09.software-engineering/06.architectural-patterns",{"title":2672,"path":2673,"stem":2674},"Паттерни взаємодії","/software-engineering/patterns-of-interaction","09.software-engineering/07.patterns-of-interaction",{"title":2676,"path":2677,"stem":2678},"Евристика проєктування","/software-engineering/design-heuristics","09.software-engineering/08.design-heuristics",{"title":2680,"path":2681,"stem":2682},"Еволюція проєктних рішень","/software-engineering/evolution-of-design-solutions","09.software-engineering/09.evolution-of-design-solutions",{"title":2684,"path":2685,"stem":2686},"EventStorming","/software-engineering/eventstorming","09.software-engineering/10.eventstorming",{"title":2688,"path":2689,"stem":2690},"DDD на практиці","/software-engineering/ddd-in-practice","09.software-engineering/11.ddd-in-practice",{"title":2692,"icon":943,"path":2693,"stem":2694,"children":2695,"page":59},"DDD","/ddd","10.ddd",[2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744],{"title":2697,"path":2698,"stem":2699},"Аналіз предметної області","/ddd/domain-analysis","10.ddd/01.domain-analysis",{"title":2701,"path":2702,"stem":2703},"Експертні знання про предметну область","/ddd/domain-expert-knowledge","10.ddd/02.domain-expert-knowledge",{"title":2705,"path":2706,"stem":2707},"Як осмислити складність предметної області","/ddd/managing-domain-complexity","10.ddd/03.managing-domain-complexity",{"title":2709,"path":2710,"stem":2711},"Інтеграція обмежених контекстів","/ddd/bounded-context-integration","10.ddd/04.bounded-context-integration",{"title":2713,"path":2714,"stem":2715},"Реалізація простої бізнес-логіки","/ddd/simple-business-logic","10.ddd/05.simple-business-logic",{"title":2717,"path":2718,"stem":2719},"Обробка складної бізнес-логіки","/ddd/complex-business-logic","10.ddd/06.complex-business-logic",{"title":2721,"path":2722,"stem":2723},"Моделювання фактора часу","/ddd/time-modeling","10.ddd/07.time-modeling",{"title":2725,"path":2726,"stem":2727},"Глава 8. Архітектурні Патерни","/ddd/architectural-patterns","10.ddd/08.architectural-patterns",{"title":2729,"path":2730,"stem":2731},"Глава 9. Патерни Взаємодії","/ddd/interaction-patterns","10.ddd/09.interaction-patterns",{"title":2733,"path":2734,"stem":2735},"Глава 10. Проектні Евристики","/ddd/design-heuristics","10.ddd/10.design-heuristics",{"title":2737,"path":2738,"stem":2739},"Глава 11. Еволюція Проектних Рішень","/ddd/evolution-of-design-decisions","10.ddd/11.evolution-of-design-decisions",{"title":2741,"path":2742,"stem":2743},"Глава 12. EventStorming","/ddd/event-storming","10.ddd/12.event-storming",{"title":2745,"path":2746,"stem":2747},"Глава 13. DDD на Практиці","/ddd/ddd-in-practice","10.ddd/13.ddd-in-practice",{"title":2749,"icon":2750,"path":2751,"stem":2752,"children":2753,"page":59},"Media Streaming","i-lucide-video","/media-streaming","11.media-streaming",[2754,2758,2762,2766,2770,2774,2778],{"title":2755,"path":2756,"stem":2757},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","/media-streaming/introduction","11.media-streaming/01.introduction",{"title":2759,"path":2760,"stem":2761},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","/media-streaming/audio-video-anatomy","11.media-streaming/02.audio-video-anatomy",{"title":2763,"path":2764,"stem":2765},"03. The Gym: FFmpeg Deep Dive","/media-streaming/ffmpeg-gym","11.media-streaming/03.ffmpeg-gym",{"title":2767,"path":2768,"stem":2769},"04. HLS Protocol: HTTP Live Streaming у Деталях","/media-streaming/hls-protocol","11.media-streaming/04.hls-protocol",{"title":2771,"path":2772,"stem":2773},"05. DASH Protocol: Відкритий Стандарт","/media-streaming/dash-protocol","11.media-streaming/05.dash-protocol",{"title":2775,"path":2776,"stem":2777},"06. Масштабування: CDN та Adaptive Bitrate","/media-streaming/cdn-and-adaptive-bitrate","11.media-streaming/06.cdn-and-adaptive-bitrate",{"title":2779,"path":2780,"stem":2781},"07. Війна із Затримкою (Latency)","/media-streaming/realtime-latency","11.media-streaming/07.realtime-latency",{"title":2783,"icon":2784,"path":2785,"stem":2786,"children":2787,"page":59},"HTML & CSS","i-devicon-html5","/html-css","12.html-css",[2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904],{"title":2789,"path":2790,"stem":2791},"Вступ до HTML. Структура документа","/html-css/intro-html-structure","12.html-css/01.intro-html-structure",{"title":2793,"path":2794,"stem":2795},"Форматування тексту в HTML","/html-css/html-text-formatting","12.html-css/02.html-text-formatting",{"title":2797,"path":2798,"stem":2799},"Посилання та зображення в HTML","/html-css/html-links-images","12.html-css/03.html-links-images",{"title":2801,"path":2802,"stem":2803},"Списки та таблиці в HTML","/html-css/html-lists-tables","12.html-css/04.html-lists-tables",{"title":2805,"path":2806,"stem":2807},"Форми в HTML","/html-css/html-forms","12.html-css/05.html-forms",{"title":2809,"path":2810,"stem":2811},"Семантичні елементи HTML5","/html-css/html-semantic-elements","12.html-css/06.html-semantic-elements",{"title":2813,"path":2814,"stem":2815},"Мультимедіа та розширені елементи HTML","/html-css/html-multimedia-advanced","12.html-css/07.html-multimedia-advanced",{"title":2817,"path":2818,"stem":2819},"Мікророзмітка та SEO в HTML","/html-css/html-microdata-seo","12.html-css/08.html-microdata-seo",{"title":2821,"path":2822,"stem":2823},"Вступ до CSS. Селектори та специфічність","/html-css/css-intro-selectors","12.html-css/09.css-intro-selectors",{"title":2825,"path":2826,"stem":2827},"Блокова модель CSS. Відступи. Box Sizing","/html-css/css-box-model","12.html-css/10.css-box-model",{"title":2829,"path":2830,"stem":2831},"Розміри у CSS: повний довідник одиниць і ключових слів","/html-css/10a.css-sizing","12.html-css/10a.css-sizing",{"title":2833,"path":2834,"stem":2835},"Типографіка в CSS. Шрифти та текст","/html-css/css-typography","12.html-css/11.css-typography",{"title":2837,"path":2838,"stem":2839},"Кольори та фони в CSS","/html-css/css-colors-backgrounds","12.html-css/12.css-colors-backgrounds",{"title":2841,"path":2842,"stem":2843},"Тіні та фільтри в CSS","/html-css/12b.css-shadows-filters","12.html-css/12b.css-shadows-filters",{"title":2845,"path":2846,"stem":2847},"CSS Flexbox: Фундамент гнучких макетів","/html-css/css-flexbox-fundamentals","12.html-css/13.css-flexbox-fundamentals",{"title":2849,"path":2850,"stem":2851},"CSS Flexbox: Вирівнювання та Позиціонування","/html-css/css-flexbox-alignment-sizing-and-patterns","12.html-css/14.css-flexbox-alignment-sizing-and-patterns",{"title":2853,"path":2854,"stem":2855},"CSS Grid. Двовимірний макет. Частина 1","/html-css/css-layout-grid","12.html-css/15.css-layout-grid",{"title":2857,"path":2858,"stem":2859},"CSS Grid. Двовимірний макет. Частина 2","/html-css/css-layout-grid-advanced","12.html-css/16.css-layout-grid-advanced",{"title":2861,"path":2862,"stem":2863},"Позиціонування в CSS. Z-index. Stacking Context","/html-css/css-positioning","12.html-css/17.css-positioning",{"title":2865,"path":2866,"stem":2867},"CSS Анімації та Переходи","/html-css/css-animations-transitions","12.html-css/18.css-animations-transitions",{"title":2869,"path":2870,"stem":2871},"Адаптивний дизайн. Media Queries. Частина 1","/html-css/css-responsive-media-queries","12.html-css/19.css-responsive-media-queries",{"title":2873,"path":2874,"stem":2875},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","/html-css/css-responsive-advanced","12.html-css/20.css-responsive-advanced",{"title":2877,"path":2878,"stem":2879},"CSS Custom Properties. Методології. Сучасний CSS","/html-css/css-variables-methodologies","12.html-css/21.css-variables-methodologies",{"title":2881,"path":2882,"stem":2883},"Сучасний CSS 2023–2025: Нові можливості","/html-css/css-modern-features","12.html-css/22.css-modern-features",{"title":2885,"path":2886,"stem":2887},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","/html-css/22a.css-nesting-modern-syntax","12.html-css/22a.css-nesting-modern-syntax",{"title":2889,"path":2890,"stem":2891},"CSS для форм та інтерактивних станів","/html-css/css-forms-interactive-states","12.html-css/23.css-forms-interactive-states",{"title":2893,"path":2894,"stem":2895},"Доступність у CSS (CSS Accessibility)","/html-css/css-accessibility","12.html-css/24.css-accessibility",{"title":2897,"path":2898,"stem":2899},"CSS-функції та сучасні sizing primitives","/html-css/css-functions-sizing","12.html-css/25.css-functions-sizing",{"title":2901,"path":2902,"stem":2903},"Rendering Pipeline і CSS Performance","/html-css/css-rendering-performance","12.html-css/26.css-rendering-performance",{"title":2905,"path":2906,"stem":2907},"CSS Best Practices: типові ситуації та правильні рішення","/html-css/css-best-practices","12.html-css/27.css-best-practices",{"title":2909,"path":2910,"stem":2911,"children":2912,"page":59},"Tailwind","/tailwind","21.tailwind",[2913,2917,2921,2925,2929,2933,2937,2941],{"title":2914,"path":2915,"stem":2916},"Що таке Tailwind CSS і навіщо він потрібен","/tailwind/tailwind-intro-philosophy","21.tailwind/01.tailwind-intro-philosophy",{"title":2918,"path":2919,"stem":2920},"Встановлення та налаштування Tailwind CSS v4","/tailwind/tailwind-installation-setup","21.tailwind/02.tailwind-installation-setup",{"title":2922,"path":2923,"stem":2924},"Utility-класи: основи та система Tailwind","/tailwind/tailwind-utility-classes-core","21.tailwind/03.tailwind-utility-classes-core",{"title":2926,"path":2927,"stem":2928},"Layout: Flexbox та Grid через Tailwind","/tailwind/tailwind-flexbox-grid","21.tailwind/04.tailwind-flexbox-grid",{"title":2930,"path":2931,"stem":2932},"Кастомізація теми через @theme у Tailwind v4","/tailwind/tailwind-theme-customization","21.tailwind/05.tailwind-theme-customization",{"title":2934,"path":2935,"stem":2936},"Варіанти: hover, focus, responsive, dark mode та нові v4","/tailwind/tailwind-variants-states","21.tailwind/06.tailwind-variants-states",{"title":2938,"path":2939,"stem":2940},"Типографіка та система кольорів у Tailwind v4","/tailwind/tailwind-typography-colors","21.tailwind/07.tailwind-typography-colors",{"title":2942,"path":2943,"stem":2944},"Компоненти та повторюваність: @apply, @utility та патерни","/tailwind/tailwind-components-patterns","21.tailwind/08.tailwind-components-patterns",{"title":2946,"path":2947,"stem":2948},"Showcase Компонентів kostyl.dev","/test-new-components","98.test-new-components",{"id":2950,"title":1315,"body":2951,"description":19913,"extension":19914,"links":19915,"meta":19916,"navigation":4041,"path":1316,"seo":19917,"stem":1317,"__hash__":19918},"docs/01.csharp/11.aspnet/12.web-api/06.filters-for-api.md",{"type":2952,"value":2953,"toc":19867},"minimark",[2954,2958,2963,2967,3008,3011,3382,3387,3401,3415,3422,3427,3438,3444,3471,3477,3497,3503,3596,3602,3617,3623,3631,3634,3651,3654,3658,3662,3669,3813,3817,3933,3961,3965,3971,3976,4078,4083,4188,4205,4207,4211,4215,4734,4736,4740,4745,4750,5544,5549,5596,5600,5605,6456,6461,6498,6502,6507,7063,7068,7174,7178,7183,7502,7506,7511,8179,8184,8190,8192,8196,8203,8207,8430,8434,8511,8515,8586,8613,8617,9218,9222,9325,9327,9331,9336,10524,10528,10560,10562,10566,10813,10815,10819,10823,10826,11028,11033,11039,11043,11052,11386,11390,11393,11643,11647,11650,11820,11822,11826,11830,12003,12005,12009,12696,12699,12701,12705,12708,13447,13451,13458,13768,13770,13772,13776,19531,19533,19537,19547,19551,19556,19579,19584,19601,19606,19634,19639,19677,19681,19714,19718,19794,19800,19802,19806,19848,19850,19863],[2955,2956,1315],"h1",{"id":2957},"фільтри-у-web-api-контексті",[2959,2960,2962],"h2",{"id":2961},"вступ-cross-cutting-concerns","Вступ: Cross-Cutting Concerns",[2964,2965,2966],"p",{},"Уявіть, що ви будуєте API з 50 endpoints. Кожен endpoint потребує:",[2968,2969,2970,2978,2984,2990,2996,3002],"ul",{},[2971,2972,2973,2977],"li",{},[2974,2975,2976],"strong",{},"Логування"," запитів та відповідей",[2971,2979,2980,2983],{},[2974,2981,2982],{},"Валідації"," вхідних даних",[2971,2985,2986,2989],{},[2974,2987,2988],{},"Авторизації"," через API-ключ",[2971,2991,2992,2995],{},[2974,2993,2994],{},"Вимірювання"," часу виконання",[2971,2997,2998,3001],{},[2974,2999,3000],{},"Додавання"," correlation ID до кожної відповіді",[2971,3003,3004,3007],{},[2974,3005,3006],{},"Обгортання"," відповідей у стандартний envelope",[2964,3009,3010],{},"Якщо писати цю логіку в кожному контролері, ви отримаєте:",[3012,3013,3018],"pre",{"className":3014,"code":3015,"language":3016,"meta":3017,"style":3017},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","[HttpGet(\"{id}\")]\npublic async Task\u003CIActionResult> GetProduct(int id)\n{\n    // 1. Логування\n    _logger.LogInformation(\"Getting product {Id}\", id);\n    \n    // 2. Валідація\n    if (id \u003C= 0) return BadRequest(\"Invalid ID\");\n    \n    // 3. Перевірка API-ключа\n    if (!ValidateApiKey()) return Unauthorized();\n    \n    // 4. Вимірювання часу\n    var stopwatch = Stopwatch.StartNew();\n    \n    // 5. Бізнес-логіка (нарешті!)\n    var product = await _db.Products.FindAsync(id);\n    \n    // 6. Логування результату\n    stopwatch.Stop();\n    _logger.LogInformation(\"Request completed in {Ms}ms\", stopwatch.ElapsedMilliseconds);\n    \n    // 7. Обгортання відповіді\n    return Ok(new { success = true, data = product });\n}\n","csharp","",[3019,3020,3021,3044,3082,3088,3095,3121,3127,3133,3168,3173,3179,3201,3206,3212,3234,3239,3245,3277,3282,3288,3301,3327,3332,3338,3376],"code",{"__ignoreMap":3017},[3022,3023,3026,3030,3034,3037,3041],"span",{"class":3024,"line":3025},"line",1,[3022,3027,3029],{"class":3028},"sHH4Y","[",[3022,3031,3033],{"class":3032},"sN1BT","HttpGet",[3022,3035,3036],{"class":3028},"(",[3022,3038,3040],{"class":3039},"sbdoH","\"{id}\"",[3022,3042,3043],{"class":3028},")]\n",[3022,3045,3047,3051,3054,3057,3060,3063,3066,3070,3072,3075,3079],{"class":3024,"line":3046},2,[3022,3048,3050],{"class":3049},"su1O8","public",[3022,3052,3053],{"class":3049}," async",[3022,3055,3056],{"class":3032}," Task",[3022,3058,3059],{"class":3028},"\u003C",[3022,3061,3062],{"class":3032},"IActionResult",[3022,3064,3065],{"class":3028},"> ",[3022,3067,3069],{"class":3068},"s8Opu","GetProduct",[3022,3071,3036],{"class":3028},[3022,3073,3074],{"class":3049},"int",[3022,3076,3078],{"class":3077},"siwwj"," id",[3022,3080,3081],{"class":3028},")\n",[3022,3083,3085],{"class":3024,"line":3084},3,[3022,3086,3087],{"class":3028},"{\n",[3022,3089,3091],{"class":3024,"line":3090},4,[3022,3092,3094],{"class":3093},"spJ8K","    // 1. Логування\n",[3022,3096,3098,3101,3104,3107,3109,3112,3115,3118],{"class":3024,"line":3097},5,[3022,3099,3100],{"class":3077},"    _logger",[3022,3102,3103],{"class":3028},".",[3022,3105,3106],{"class":3068},"LogInformation",[3022,3108,3036],{"class":3028},[3022,3110,3111],{"class":3039},"\"Getting product {Id}\"",[3022,3113,3114],{"class":3028},", ",[3022,3116,3117],{"class":3077},"id",[3022,3119,3120],{"class":3028},");\n",[3022,3122,3124],{"class":3024,"line":3123},6,[3022,3125,3126],{"class":3028},"    \n",[3022,3128,3130],{"class":3024,"line":3129},7,[3022,3131,3132],{"class":3093},"    // 2. Валідація\n",[3022,3134,3136,3140,3143,3145,3148,3152,3155,3158,3161,3163,3166],{"class":3024,"line":3135},8,[3022,3137,3139],{"class":3138},"sCDza","    if",[3022,3141,3142],{"class":3028}," (",[3022,3144,3117],{"class":3077},[3022,3146,3147],{"class":3028}," \u003C= ",[3022,3149,3151],{"class":3150},"sJj4R","0",[3022,3153,3154],{"class":3028},") ",[3022,3156,3157],{"class":3138},"return",[3022,3159,3160],{"class":3068}," BadRequest",[3022,3162,3036],{"class":3028},[3022,3164,3165],{"class":3039},"\"Invalid ID\"",[3022,3167,3120],{"class":3028},[3022,3169,3171],{"class":3024,"line":3170},9,[3022,3172,3126],{"class":3028},[3022,3174,3176],{"class":3024,"line":3175},10,[3022,3177,3178],{"class":3093},"    // 3. Перевірка API-ключа\n",[3022,3180,3182,3184,3187,3190,3193,3195,3198],{"class":3024,"line":3181},11,[3022,3183,3139],{"class":3138},[3022,3185,3186],{"class":3028}," (!",[3022,3188,3189],{"class":3068},"ValidateApiKey",[3022,3191,3192],{"class":3028},"()) ",[3022,3194,3157],{"class":3138},[3022,3196,3197],{"class":3068}," Unauthorized",[3022,3199,3200],{"class":3028},"();\n",[3022,3202,3204],{"class":3024,"line":3203},12,[3022,3205,3126],{"class":3028},[3022,3207,3209],{"class":3024,"line":3208},13,[3022,3210,3211],{"class":3093},"    // 4. Вимірювання часу\n",[3022,3213,3215,3218,3221,3224,3227,3229,3232],{"class":3024,"line":3214},14,[3022,3216,3217],{"class":3049},"    var",[3022,3219,3220],{"class":3077}," stopwatch",[3022,3222,3223],{"class":3028}," = ",[3022,3225,3226],{"class":3077},"Stopwatch",[3022,3228,3103],{"class":3028},[3022,3230,3231],{"class":3068},"StartNew",[3022,3233,3200],{"class":3028},[3022,3235,3237],{"class":3024,"line":3236},15,[3022,3238,3126],{"class":3028},[3022,3240,3242],{"class":3024,"line":3241},16,[3022,3243,3244],{"class":3093},"    // 5. Бізнес-логіка (нарешті!)\n",[3022,3246,3248,3250,3253,3255,3258,3261,3263,3266,3268,3271,3273,3275],{"class":3024,"line":3247},17,[3022,3249,3217],{"class":3049},[3022,3251,3252],{"class":3077}," product",[3022,3254,3223],{"class":3028},[3022,3256,3257],{"class":3049},"await",[3022,3259,3260],{"class":3077}," _db",[3022,3262,3103],{"class":3028},[3022,3264,3265],{"class":3077},"Products",[3022,3267,3103],{"class":3028},[3022,3269,3270],{"class":3068},"FindAsync",[3022,3272,3036],{"class":3028},[3022,3274,3117],{"class":3077},[3022,3276,3120],{"class":3028},[3022,3278,3280],{"class":3024,"line":3279},18,[3022,3281,3126],{"class":3028},[3022,3283,3285],{"class":3024,"line":3284},19,[3022,3286,3287],{"class":3093},"    // 6. Логування результату\n",[3022,3289,3291,3294,3296,3299],{"class":3024,"line":3290},20,[3022,3292,3293],{"class":3077},"    stopwatch",[3022,3295,3103],{"class":3028},[3022,3297,3298],{"class":3068},"Stop",[3022,3300,3200],{"class":3028},[3022,3302,3304,3306,3308,3310,3312,3315,3317,3320,3322,3325],{"class":3024,"line":3303},21,[3022,3305,3100],{"class":3077},[3022,3307,3103],{"class":3028},[3022,3309,3106],{"class":3068},[3022,3311,3036],{"class":3028},[3022,3313,3314],{"class":3039},"\"Request completed in {Ms}ms\"",[3022,3316,3114],{"class":3028},[3022,3318,3319],{"class":3077},"stopwatch",[3022,3321,3103],{"class":3028},[3022,3323,3324],{"class":3077},"ElapsedMilliseconds",[3022,3326,3120],{"class":3028},[3022,3328,3330],{"class":3024,"line":3329},22,[3022,3331,3126],{"class":3028},[3022,3333,3335],{"class":3024,"line":3334},23,[3022,3336,3337],{"class":3093},"    // 7. Обгортання відповіді\n",[3022,3339,3341,3344,3347,3349,3352,3355,3358,3360,3363,3365,3368,3370,3373],{"class":3024,"line":3340},24,[3022,3342,3343],{"class":3138},"    return",[3022,3345,3346],{"class":3068}," Ok",[3022,3348,3036],{"class":3028},[3022,3350,3351],{"class":3049},"new",[3022,3353,3354],{"class":3028}," { ",[3022,3356,3357],{"class":3077},"success",[3022,3359,3223],{"class":3028},[3022,3361,3362],{"class":3049},"true",[3022,3364,3114],{"class":3028},[3022,3366,3367],{"class":3077},"data",[3022,3369,3223],{"class":3028},[3022,3371,3372],{"class":3077},"product",[3022,3374,3375],{"class":3028}," });\n",[3022,3377,3379],{"class":3024,"line":3378},25,[3022,3380,3381],{"class":3028},"}\n",[2964,3383,3384],{},[2974,3385,3386],{},"Проблеми цього підходу:",[2968,3388,3389,3392,3395,3398],{},[2971,3390,3391],{},"❌ Дублювання коду у кожному методі",[2971,3393,3394],{},"❌ Бізнес-логіка захаращена технічними деталями",[2971,3396,3397],{},"❌ Складно підтримувати консистентність",[2971,3399,3400],{},"❌ Важко тестувати",[2964,3402,3403,3406,3407,3410,3411,3414],{},[2974,3404,3405],{},"Рішення"," — ",[2974,3408,3409],{},"Filters (фільтри)"," — механізм ASP.NET Core для реалізації ",[2974,3412,3413],{},"cross-cutting concerns"," (наскрізних аспектів) без дублювання коду. Фільтри виконуються автоматично до або після action методів, дозволяючи централізувати логіку.",[3416,3417,3418,3421],"note",{},[2974,3419,3420],{},"Передумови:"," Ця стаття базується на знаннях з попередніх статей (01-05 Web API Controllers), а також на розумінні Filter Pipeline з курсу MVC (стаття 07).",[3423,3424,3426],"h3",{"id":3425},"що-ви-створите-в-цій-статті","Що ви створите в цій статті",[2964,3428,3429,3430,3433,3434,3437],{},"Ми побудуємо ",[2974,3431,3432],{},"E-commerce API"," з ",[2974,3435,3436],{},"професійною системою фільтрів",":",[2964,3439,3440,3443],{},[2974,3441,3442],{},"1. ApiKeyAuthFilter"," — авторизація через API-ключ:",[3012,3445,3449],{"className":3446,"code":3447,"language":3448,"meta":3017,"style":3017},"language-http shiki shiki-themes light-plus dark-plus dark-plus","GET /api/products\nX-Api-Key: sk_live_abc123xyz\n→ 200 OK (якщо ключ валідний)\n→ 401 Unauthorized (якщо ключ невалідний)\n","http",[3019,3450,3451,3456,3461,3466],{"__ignoreMap":3017},[3022,3452,3453],{"class":3024,"line":3025},[3022,3454,3455],{},"GET /api/products\n",[3022,3457,3458],{"class":3024,"line":3046},[3022,3459,3460],{},"X-Api-Key: sk_live_abc123xyz\n",[3022,3462,3463],{"class":3024,"line":3084},[3022,3464,3465],{},"→ 200 OK (якщо ключ валідний)\n",[3022,3467,3468],{"class":3024,"line":3090},[3022,3469,3470],{},"→ 401 Unauthorized (якщо ключ невалідний)\n",[2964,3472,3473,3476],{},[2974,3474,3475],{},"2. RequestValidationFilter"," — централізована валідація:",[3012,3478,3480],{"className":3446,"code":3479,"language":3448,"meta":3017,"style":3017},"POST /api/products\n{ \"name\": \"\", \"price\": -10 }\n→ 400 Bad Request з деталями помилок\n",[3019,3481,3482,3487,3492],{"__ignoreMap":3017},[3022,3483,3484],{"class":3024,"line":3025},[3022,3485,3486],{},"POST /api/products\n",[3022,3488,3489],{"class":3024,"line":3046},[3022,3490,3491],{},"{ \"name\": \"\", \"price\": -10 }\n",[3022,3493,3494],{"class":3024,"line":3084},[3022,3495,3496],{},"→ 400 Bad Request з деталями помилок\n",[2964,3498,3499,3502],{},[2974,3500,3501],{},"3. ResponseWrapperFilter"," — envelope pattern:",[3012,3504,3508],{"className":3505,"code":3506,"language":3507,"meta":3017,"style":3017},"language-json shiki shiki-themes light-plus dark-plus dark-plus","{\n  \"success\": true,\n  \"data\": { \"id\": 1, \"name\": \"Laptop\" },\n  \"meta\": {\n    \"timestamp\": \"2024-01-15T10:30:00Z\",\n    \"version\": \"1.0\"\n  }\n}\n","json",[3019,3509,3510,3514,3528,3557,3565,3577,3587,3592],{"__ignoreMap":3017},[3022,3511,3512],{"class":3024,"line":3025},[3022,3513,3087],{"class":3028},[3022,3515,3516,3520,3523,3525],{"class":3024,"line":3046},[3022,3517,3519],{"class":3518},"sLwNe","  \"success\"",[3022,3521,3522],{"class":3028},": ",[3022,3524,3362],{"class":3049},[3022,3526,3527],{"class":3028},",\n",[3022,3529,3530,3533,3536,3539,3541,3544,3546,3549,3551,3554],{"class":3024,"line":3084},[3022,3531,3532],{"class":3518},"  \"data\"",[3022,3534,3535],{"class":3028},": { ",[3022,3537,3538],{"class":3518},"\"id\"",[3022,3540,3522],{"class":3028},[3022,3542,3543],{"class":3150},"1",[3022,3545,3114],{"class":3028},[3022,3547,3548],{"class":3518},"\"name\"",[3022,3550,3522],{"class":3028},[3022,3552,3553],{"class":3039},"\"Laptop\"",[3022,3555,3556],{"class":3028}," },\n",[3022,3558,3559,3562],{"class":3024,"line":3090},[3022,3560,3561],{"class":3518},"  \"meta\"",[3022,3563,3564],{"class":3028},": {\n",[3022,3566,3567,3570,3572,3575],{"class":3024,"line":3097},[3022,3568,3569],{"class":3518},"    \"timestamp\"",[3022,3571,3522],{"class":3028},[3022,3573,3574],{"class":3039},"\"2024-01-15T10:30:00Z\"",[3022,3576,3527],{"class":3028},[3022,3578,3579,3582,3584],{"class":3024,"line":3123},[3022,3580,3581],{"class":3518},"    \"version\"",[3022,3583,3522],{"class":3028},[3022,3585,3586],{"class":3039},"\"1.0\"\n",[3022,3588,3589],{"class":3024,"line":3129},[3022,3590,3591],{"class":3028},"  }\n",[3022,3593,3594],{"class":3024,"line":3135},[3022,3595,3381],{"class":3028},[2964,3597,3598,3601],{},[2974,3599,3600],{},"4. CorrelationIdFilter"," — traceability:",[3012,3603,3605],{"className":3446,"code":3604,"language":3448,"meta":3017,"style":3017},"Response Headers:\nX-Correlation-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n",[3019,3606,3607,3612],{"__ignoreMap":3017},[3022,3608,3609],{"class":3024,"line":3025},[3022,3610,3611],{},"Response Headers:\n",[3022,3613,3614],{"class":3024,"line":3046},[3022,3615,3616],{},"X-Correlation-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n",[2964,3618,3619,3622],{},[2974,3620,3621],{},"5. PerformanceMonitoringFilter"," — метрики:",[3012,3624,3629],{"className":3625,"code":3627,"language":3628},[3626],"language-text","[INFO] GET /api/products/1 completed in 45ms\n","text",[3019,3630,3627],{"__ignoreMap":3017},[2964,3632,3633],{},"До кінця статті ви зможете:",[2968,3635,3636,3639,3642,3645,3648],{},[2971,3637,3638],{},"Створювати кастомні фільтри для API",[2971,3640,3641],{},"Використовувати різні типи фільтрів (Action, Exception, Result)",[2971,3643,3644],{},"Реалізовувати envelope pattern",[2971,3646,3647],{},"Додавати correlation IDs",[2971,3649,3650],{},"Вимірювати продуктивність endpoints",[3652,3653],"hr",{},[2959,3655,3657],{"id":3656},"фундаментальні-концепції-типи-фільтрів-та-pipeline","Фундаментальні концепції: Типи фільтрів та Pipeline",[3423,3659,3661],{"id":3660},"filter-pipeline-порядок-виконання","Filter Pipeline: Порядок виконання",[2964,3663,3664,3665,3668],{},"Фільтри виконуються у ",[2974,3666,3667],{},"чіткому порядку"," навколо action методу:",[3670,3671,3672],"mermaid",{},[3012,3673,3676],{"className":3674,"code":3675,"language":3670,"meta":3017,"style":3017},"language-mermaid shiki shiki-themes light-plus dark-plus dark-plus","sequenceDiagram\n    participant Request\n    participant Authorization\n    participant Resource\n    participant Action\n    participant Exception\n    participant Result\n    participant Response\n    \n    Request->>Authorization: 1. Authorization Filter\n    Authorization->>Resource: 2. Resource Filter (Before)\n    Resource->>Action: 3. Action Filter (Before)\n    Action->>Action: 4. Action Method Execution\n    Action->>Exception: 5. Exception Filter (if error)\n    Action->>Action: 6. Action Filter (After)\n    Action->>Result: 7. Result Filter (Before)\n    Result->>Result: 8. Result Execution\n    Result->>Result: 9. Result Filter (After)\n    Result->>Resource: 10. Resource Filter (After)\n    Resource->>Response: Response\n    \n    style Request fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n    style Authorization fill:#f59e0b,stroke:#b45309,color:#ffffff\n    style Action fill:#10b981,stroke:#059669,color:#ffffff\n    style Exception fill:#ef4444,stroke:#b91c1c,color:#ffffff\n    style Result fill:#8b5cf6,stroke:#6d28d9,color:#ffffff\n    style Response fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[3019,3677,3678,3683,3688,3693,3698,3703,3708,3713,3718,3722,3727,3732,3737,3742,3747,3752,3757,3762,3767,3772,3777,3781,3786,3791,3796,3801,3807],{"__ignoreMap":3017},[3022,3679,3680],{"class":3024,"line":3025},[3022,3681,3682],{},"sequenceDiagram\n",[3022,3684,3685],{"class":3024,"line":3046},[3022,3686,3687],{},"    participant Request\n",[3022,3689,3690],{"class":3024,"line":3084},[3022,3691,3692],{},"    participant Authorization\n",[3022,3694,3695],{"class":3024,"line":3090},[3022,3696,3697],{},"    participant Resource\n",[3022,3699,3700],{"class":3024,"line":3097},[3022,3701,3702],{},"    participant Action\n",[3022,3704,3705],{"class":3024,"line":3123},[3022,3706,3707],{},"    participant Exception\n",[3022,3709,3710],{"class":3024,"line":3129},[3022,3711,3712],{},"    participant Result\n",[3022,3714,3715],{"class":3024,"line":3135},[3022,3716,3717],{},"    participant Response\n",[3022,3719,3720],{"class":3024,"line":3170},[3022,3721,3126],{},[3022,3723,3724],{"class":3024,"line":3175},[3022,3725,3726],{},"    Request->>Authorization: 1. Authorization Filter\n",[3022,3728,3729],{"class":3024,"line":3181},[3022,3730,3731],{},"    Authorization->>Resource: 2. Resource Filter (Before)\n",[3022,3733,3734],{"class":3024,"line":3203},[3022,3735,3736],{},"    Resource->>Action: 3. Action Filter (Before)\n",[3022,3738,3739],{"class":3024,"line":3208},[3022,3740,3741],{},"    Action->>Action: 4. Action Method Execution\n",[3022,3743,3744],{"class":3024,"line":3214},[3022,3745,3746],{},"    Action->>Exception: 5. Exception Filter (if error)\n",[3022,3748,3749],{"class":3024,"line":3236},[3022,3750,3751],{},"    Action->>Action: 6. Action Filter (After)\n",[3022,3753,3754],{"class":3024,"line":3241},[3022,3755,3756],{},"    Action->>Result: 7. Result Filter (Before)\n",[3022,3758,3759],{"class":3024,"line":3247},[3022,3760,3761],{},"    Result->>Result: 8. Result Execution\n",[3022,3763,3764],{"class":3024,"line":3279},[3022,3765,3766],{},"    Result->>Result: 9. Result Filter (After)\n",[3022,3768,3769],{"class":3024,"line":3284},[3022,3770,3771],{},"    Result->>Resource: 10. Resource Filter (After)\n",[3022,3773,3774],{"class":3024,"line":3290},[3022,3775,3776],{},"    Resource->>Response: Response\n",[3022,3778,3779],{"class":3024,"line":3303},[3022,3780,3126],{},[3022,3782,3783],{"class":3024,"line":3329},[3022,3784,3785],{},"    style Request fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[3022,3787,3788],{"class":3024,"line":3334},[3022,3789,3790],{},"    style Authorization fill:#f59e0b,stroke:#b45309,color:#ffffff\n",[3022,3792,3793],{"class":3024,"line":3340},[3022,3794,3795],{},"    style Action fill:#10b981,stroke:#059669,color:#ffffff\n",[3022,3797,3798],{"class":3024,"line":3378},[3022,3799,3800],{},"    style Exception fill:#ef4444,stroke:#b91c1c,color:#ffffff\n",[3022,3802,3804],{"class":3024,"line":3803},26,[3022,3805,3806],{},"    style Result fill:#8b5cf6,stroke:#6d28d9,color:#ffffff\n",[3022,3808,3810],{"class":3024,"line":3809},27,[3022,3811,3812],{},"    style Response fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[3423,3814,3816],{"id":3815},"типи-фільтрів-для-api","Типи фільтрів для API",[3818,3819,3820,3839],"table",{},[3821,3822,3823],"thead",{},[3824,3825,3826,3830,3833,3836],"tr",{},[3827,3828,3829],"th",{},"Тип фільтру",[3827,3831,3832],{},"Інтерфейс",[3827,3834,3835],{},"Коли виконується",[3827,3837,3838],{},"Використання для API",[3840,3841,3842,3861,3879,3897,3915],"tbody",{},[3824,3843,3844,3850,3855,3858],{},[3845,3846,3847],"td",{},[2974,3848,3849],{},"Authorization",[3845,3851,3852],{},[3019,3853,3854],{},"IAuthorizationFilter",[3845,3856,3857],{},"Перший у pipeline",[3845,3859,3860],{},"API key validation, JWT verification",[3824,3862,3863,3868,3873,3876],{},[3845,3864,3865],{},[2974,3866,3867],{},"Resource",[3845,3869,3870],{},[3019,3871,3872],{},"IResourceFilter",[3845,3874,3875],{},"До model binding",[3845,3877,3878],{},"Caching, rate limiting",[3824,3880,3881,3886,3891,3894],{},[3845,3882,3883],{},[2974,3884,3885],{},"Action",[3845,3887,3888],{},[3019,3889,3890],{},"IActionFilter",[3845,3892,3893],{},"До/після action",[3845,3895,3896],{},"Валідація DTO, логування",[3824,3898,3899,3904,3909,3912],{},[3845,3900,3901],{},[2974,3902,3903],{},"Exception",[3845,3905,3906],{},[3019,3907,3908],{},"IExceptionFilter",[3845,3910,3911],{},"При винятку",[3845,3913,3914],{},"Перетворення у ProblemDetails",[3824,3916,3917,3922,3927,3930],{},[3845,3918,3919],{},[2974,3920,3921],{},"Result",[3845,3923,3924],{},[3019,3925,3926],{},"IResultFilter",[3845,3928,3929],{},"До/після result",[3845,3931,3932],{},"Response wrapping, headers",[3934,3935,3936,3941],"tip",{},[2964,3937,3938],{},[2974,3939,3940],{},"Для API найчастіше використовуються:",[2968,3942,3943,3949,3955],{},[2971,3944,3945,3948],{},[2974,3946,3947],{},"Action Filters"," — валідація, логування",[2971,3950,3951,3954],{},[2974,3952,3953],{},"Result Filters"," — обгортання відповідей, додавання headers",[2971,3956,3957,3960],{},[2974,3958,3959],{},"Authorization Filters"," — API key, custom auth",[3423,3962,3964],{"id":3963},"синхронні-vs-асинхронні-фільтри","Синхронні vs Асинхронні фільтри",[2964,3966,3967,3968,3437],{},"Кожен тип фільтру має ",[2974,3969,3970],{},"дві версії",[2964,3972,3973],{},[2974,3974,3975],{},"Синхронний:",[3012,3977,3979],{"className":3014,"code":3978,"language":3016,"meta":3017,"style":3017},"public class MyActionFilter : IActionFilter\n{\n    public void OnActionExecuting(ActionExecutingContext context)\n    {\n        // До виконання action\n    }\n\n    public void OnActionExecuted(ActionExecutedContext context)\n    {\n        // Після виконання action\n    }\n}\n",[3019,3980,3981,3997,4001,4022,4027,4032,4037,4043,4061,4065,4070,4074],{"__ignoreMap":3017},[3022,3982,3983,3985,3988,3991,3994],{"class":3024,"line":3025},[3022,3984,3050],{"class":3049},[3022,3986,3987],{"class":3049}," class",[3022,3989,3990],{"class":3032}," MyActionFilter",[3022,3992,3993],{"class":3028}," : ",[3022,3995,3996],{"class":3032},"IActionFilter\n",[3022,3998,3999],{"class":3024,"line":3046},[3022,4000,3087],{"class":3028},[3022,4002,4003,4006,4009,4012,4014,4017,4020],{"class":3024,"line":3084},[3022,4004,4005],{"class":3049},"    public",[3022,4007,4008],{"class":3049}," void",[3022,4010,4011],{"class":3068}," OnActionExecuting",[3022,4013,3036],{"class":3028},[3022,4015,4016],{"class":3032},"ActionExecutingContext",[3022,4018,4019],{"class":3077}," context",[3022,4021,3081],{"class":3028},[3022,4023,4024],{"class":3024,"line":3090},[3022,4025,4026],{"class":3028},"    {\n",[3022,4028,4029],{"class":3024,"line":3097},[3022,4030,4031],{"class":3093},"        // До виконання action\n",[3022,4033,4034],{"class":3024,"line":3123},[3022,4035,4036],{"class":3028},"    }\n",[3022,4038,4039],{"class":3024,"line":3129},[3022,4040,4042],{"emptyLinePlaceholder":4041},true,"\n",[3022,4044,4045,4047,4049,4052,4054,4057,4059],{"class":3024,"line":3135},[3022,4046,4005],{"class":3049},[3022,4048,4008],{"class":3049},[3022,4050,4051],{"class":3068}," OnActionExecuted",[3022,4053,3036],{"class":3028},[3022,4055,4056],{"class":3032},"ActionExecutedContext",[3022,4058,4019],{"class":3077},[3022,4060,3081],{"class":3028},[3022,4062,4063],{"class":3024,"line":3170},[3022,4064,4026],{"class":3028},[3022,4066,4067],{"class":3024,"line":3175},[3022,4068,4069],{"class":3093},"        // Після виконання action\n",[3022,4071,4072],{"class":3024,"line":3181},[3022,4073,4036],{"class":3028},[3022,4075,4076],{"class":3024,"line":3203},[3022,4077,3381],{"class":3028},[2964,4079,4080],{},[2974,4081,4082],{},"Асинхронний (рекомендовано для API):",[3012,4084,4086],{"className":3014,"code":4085,"language":3016,"meta":3017,"style":3017},"public class MyAsyncActionFilter : IAsyncActionFilter\n{\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // До виконання action\n        \n        var resultContext = await next(); // Виконуємо action\n        \n        // Після виконання action\n    }\n}\n",[3019,4087,4088,4102,4106,4120,4129,4139,4143,4147,4152,4172,4176,4180,4184],{"__ignoreMap":3017},[3022,4089,4090,4092,4094,4097,4099],{"class":3024,"line":3025},[3022,4091,3050],{"class":3049},[3022,4093,3987],{"class":3049},[3022,4095,4096],{"class":3032}," MyAsyncActionFilter",[3022,4098,3993],{"class":3028},[3022,4100,4101],{"class":3032},"IAsyncActionFilter\n",[3022,4103,4104],{"class":3024,"line":3046},[3022,4105,3087],{"class":3028},[3022,4107,4108,4110,4112,4114,4117],{"class":3024,"line":3084},[3022,4109,4005],{"class":3049},[3022,4111,3053],{"class":3049},[3022,4113,3056],{"class":3032},[3022,4115,4116],{"class":3068}," OnActionExecutionAsync",[3022,4118,4119],{"class":3028},"(\n",[3022,4121,4122,4125,4127],{"class":3024,"line":3090},[3022,4123,4124],{"class":3032},"        ActionExecutingContext",[3022,4126,4019],{"class":3077},[3022,4128,3527],{"class":3028},[3022,4130,4131,4134,4137],{"class":3024,"line":3097},[3022,4132,4133],{"class":3032},"        ActionExecutionDelegate",[3022,4135,4136],{"class":3077}," next",[3022,4138,3081],{"class":3028},[3022,4140,4141],{"class":3024,"line":3123},[3022,4142,4026],{"class":3028},[3022,4144,4145],{"class":3024,"line":3129},[3022,4146,4031],{"class":3093},[3022,4148,4149],{"class":3024,"line":3135},[3022,4150,4151],{"class":3028},"        \n",[3022,4153,4154,4157,4160,4162,4164,4166,4169],{"class":3024,"line":3170},[3022,4155,4156],{"class":3049},"        var",[3022,4158,4159],{"class":3077}," resultContext",[3022,4161,3223],{"class":3028},[3022,4163,3257],{"class":3049},[3022,4165,4136],{"class":3068},[3022,4167,4168],{"class":3028},"(); ",[3022,4170,4171],{"class":3093},"// Виконуємо action\n",[3022,4173,4174],{"class":3024,"line":3175},[3022,4175,4151],{"class":3028},[3022,4177,4178],{"class":3024,"line":3181},[3022,4179,4069],{"class":3093},[3022,4181,4182],{"class":3024,"line":3203},[3022,4183,4036],{"class":3028},[3022,4185,4186],{"class":3024,"line":3208},[3022,4187,3381],{"class":3028},[4189,4190,4191,4194,4195,4198,4199,4201,4202,3103],"warning",{},[2974,4192,4193],{},"Важливо:"," Не можна реалізовувати обидва інтерфейси одночасно. Якщо реалізовано ",[3019,4196,4197],{},"IAsyncActionFilter",", синхронні методи ",[3019,4200,3890],{}," ",[2974,4203,4204],{},"ігноруються",[3652,4206],{},[2959,4208,4210],{"id":4209},"практична-реалізація-e-commerce-api-з-фільтрами","Практична реалізація: E-commerce API з фільтрами",[3423,4212,4214],{"id":4213},"крок-1-налаштування-проєкту","Крок 1: Налаштування проєкту",[4216,4217,4218,4222,4280,4284,4290],"steps",{},[3423,4219,4221],{"id":4220},"створення-проєкту","Створення проєкту",[4223,4224,4226,4240,4248,4251,4261,4271],"terminal-preview",{"title":4225},"bash",[4227,4228,4230,4201,4235],"div",{"className":4229},[3024],[3022,4231,4234],{"className":4232},[4233],"opacity-40","$",[2974,4236,4239],{"className":4237},[4238],"font-bold","dotnet new webapi -n EcommerceFiltersApi",[4227,4241,4243],{"className":4242},[3024],[3022,4244,4247],{"className":4245},[4246,4238],"text-green-400","The template \"ASP.NET Core Web API\" was created successfully.",[4227,4249],{"className":4250},[3024],[4227,4252,4254,4201,4257],{"className":4253},[3024],[3022,4255,4234],{"className":4256},[4233],[2974,4258,4260],{"className":4259},[4238],"cd EcommerceFiltersApi",[4227,4262,4264,4201,4267],{"className":4263},[3024],[3022,4265,4234],{"className":4266},[4233],[2974,4268,4270],{"className":4269},[4238],"dotnet add package Microsoft.EntityFrameworkCore.InMemory",[4227,4272,4274,4279],{"className":4273},[3024],[3022,4275,4278],{"className":4276},[4277],"text-blue-400","info"," : PackageReference added successfully",[3423,4281,4283],{"id":4282},"створення-базових-моделей","Створення базових моделей",[2964,4285,4286,4287,3437],{},"Створіть файл ",[3019,4288,4289],{},"Models/Product.cs",[3012,4291,4293],{"className":3014,"code":4292,"language":3016,"meta":3017,"style":3017},"using System.ComponentModel.DataAnnotations;\n\nnamespace EcommerceFiltersApi.Models;\n\npublic class Product\n{\n    public int Id { get; set; }\n    \n    [Required]\n    [MaxLength(200)]\n    public required string Name { get; set; }\n    \n    [Range(0.01, 1_000_000)]\n    public decimal Price { get; set; }\n    \n    [Range(0, int.MaxValue)]\n    public int Stock { get; set; }\n    \n    public bool IsActive { get; set; } = true;\n}\n\npublic record CreateProductDto\n{\n    [Required(ErrorMessage = \"Product name is required\")]\n    [MaxLength(200, ErrorMessage = \"Name cannot exceed 200 characters\")]\n    public required string Name { get; init; }\n\n    [Range(0.01, 1_000_000, ErrorMessage = \"Price must be between 0.01 and 1,000,000\")]\n    public decimal Price { get; init; }\n\n    [Range(0, int.MaxValue, ErrorMessage = \"Stock cannot be negative\")]\n    public int Stock { get; init; }\n}\n",[3019,4294,4295,4316,4320,4335,4339,4348,4352,4376,4380,4391,4405,4428,4432,4451,4471,4475,4496,4515,4519,4544,4548,4552,4562,4566,4584,4605,4626,4630,4656,4675,4680,4710,4729],{"__ignoreMap":3017},[3022,4296,4297,4300,4303,4305,4308,4310,4313],{"class":3024,"line":3025},[3022,4298,4299],{"class":3138},"using",[3022,4301,4302],{"class":3032}," System",[3022,4304,3103],{"class":3028},[3022,4306,4307],{"class":3032},"ComponentModel",[3022,4309,3103],{"class":3028},[3022,4311,4312],{"class":3032},"DataAnnotations",[3022,4314,4315],{"class":3028},";\n",[3022,4317,4318],{"class":3024,"line":3046},[3022,4319,4042],{"emptyLinePlaceholder":4041},[3022,4321,4322,4325,4328,4330,4333],{"class":3024,"line":3084},[3022,4323,4324],{"class":3049},"namespace",[3022,4326,4327],{"class":3032}," EcommerceFiltersApi",[3022,4329,3103],{"class":3028},[3022,4331,4332],{"class":3032},"Models",[3022,4334,4315],{"class":3028},[3022,4336,4337],{"class":3024,"line":3090},[3022,4338,4042],{"emptyLinePlaceholder":4041},[3022,4340,4341,4343,4345],{"class":3024,"line":3097},[3022,4342,3050],{"class":3049},[3022,4344,3987],{"class":3049},[3022,4346,4347],{"class":3032}," Product\n",[3022,4349,4350],{"class":3024,"line":3123},[3022,4351,3087],{"class":3028},[3022,4353,4354,4356,4359,4362,4364,4367,4370,4373],{"class":3024,"line":3129},[3022,4355,4005],{"class":3049},[3022,4357,4358],{"class":3049}," int",[3022,4360,4361],{"class":3077}," Id",[3022,4363,3354],{"class":3028},[3022,4365,4366],{"class":3049},"get",[3022,4368,4369],{"class":3028},"; ",[3022,4371,4372],{"class":3049},"set",[3022,4374,4375],{"class":3028},"; }\n",[3022,4377,4378],{"class":3024,"line":3135},[3022,4379,3126],{"class":3028},[3022,4381,4382,4385,4388],{"class":3024,"line":3170},[3022,4383,4384],{"class":3028},"    [",[3022,4386,4387],{"class":3032},"Required",[3022,4389,4390],{"class":3028},"]\n",[3022,4392,4393,4395,4398,4400,4403],{"class":3024,"line":3175},[3022,4394,4384],{"class":3028},[3022,4396,4397],{"class":3032},"MaxLength",[3022,4399,3036],{"class":3028},[3022,4401,4402],{"class":3150},"200",[3022,4404,3043],{"class":3028},[3022,4406,4407,4409,4412,4415,4418,4420,4422,4424,4426],{"class":3024,"line":3181},[3022,4408,4005],{"class":3049},[3022,4410,4411],{"class":3049}," required",[3022,4413,4414],{"class":3049}," string",[3022,4416,4417],{"class":3077}," Name",[3022,4419,3354],{"class":3028},[3022,4421,4366],{"class":3049},[3022,4423,4369],{"class":3028},[3022,4425,4372],{"class":3049},[3022,4427,4375],{"class":3028},[3022,4429,4430],{"class":3024,"line":3203},[3022,4431,3126],{"class":3028},[3022,4433,4434,4436,4439,4441,4444,4446,4449],{"class":3024,"line":3208},[3022,4435,4384],{"class":3028},[3022,4437,4438],{"class":3032},"Range",[3022,4440,3036],{"class":3028},[3022,4442,4443],{"class":3150},"0.01",[3022,4445,3114],{"class":3028},[3022,4447,4448],{"class":3150},"1_000_000",[3022,4450,3043],{"class":3028},[3022,4452,4453,4455,4458,4461,4463,4465,4467,4469],{"class":3024,"line":3214},[3022,4454,4005],{"class":3049},[3022,4456,4457],{"class":3049}," decimal",[3022,4459,4460],{"class":3077}," Price",[3022,4462,3354],{"class":3028},[3022,4464,4366],{"class":3049},[3022,4466,4369],{"class":3028},[3022,4468,4372],{"class":3049},[3022,4470,4375],{"class":3028},[3022,4472,4473],{"class":3024,"line":3236},[3022,4474,3126],{"class":3028},[3022,4476,4477,4479,4481,4483,4485,4487,4489,4491,4494],{"class":3024,"line":3241},[3022,4478,4384],{"class":3028},[3022,4480,4438],{"class":3032},[3022,4482,3036],{"class":3028},[3022,4484,3151],{"class":3150},[3022,4486,3114],{"class":3028},[3022,4488,3074],{"class":3049},[3022,4490,3103],{"class":3028},[3022,4492,4493],{"class":3077},"MaxValue",[3022,4495,3043],{"class":3028},[3022,4497,4498,4500,4502,4505,4507,4509,4511,4513],{"class":3024,"line":3247},[3022,4499,4005],{"class":3049},[3022,4501,4358],{"class":3049},[3022,4503,4504],{"class":3077}," Stock",[3022,4506,3354],{"class":3028},[3022,4508,4366],{"class":3049},[3022,4510,4369],{"class":3028},[3022,4512,4372],{"class":3049},[3022,4514,4375],{"class":3028},[3022,4516,4517],{"class":3024,"line":3279},[3022,4518,3126],{"class":3028},[3022,4520,4521,4523,4526,4529,4531,4533,4535,4537,4540,4542],{"class":3024,"line":3284},[3022,4522,4005],{"class":3049},[3022,4524,4525],{"class":3049}," bool",[3022,4527,4528],{"class":3077}," IsActive",[3022,4530,3354],{"class":3028},[3022,4532,4366],{"class":3049},[3022,4534,4369],{"class":3028},[3022,4536,4372],{"class":3049},[3022,4538,4539],{"class":3028},"; } = ",[3022,4541,3362],{"class":3049},[3022,4543,4315],{"class":3028},[3022,4545,4546],{"class":3024,"line":3290},[3022,4547,3381],{"class":3028},[3022,4549,4550],{"class":3024,"line":3303},[3022,4551,4042],{"emptyLinePlaceholder":4041},[3022,4553,4554,4556,4559],{"class":3024,"line":3329},[3022,4555,3050],{"class":3049},[3022,4557,4558],{"class":3049}," record",[3022,4560,4561],{"class":3032}," CreateProductDto\n",[3022,4563,4564],{"class":3024,"line":3334},[3022,4565,3087],{"class":3028},[3022,4567,4568,4570,4572,4574,4577,4579,4582],{"class":3024,"line":3340},[3022,4569,4384],{"class":3028},[3022,4571,4387],{"class":3032},[3022,4573,3036],{"class":3028},[3022,4575,4576],{"class":3077},"ErrorMessage",[3022,4578,3223],{"class":3028},[3022,4580,4581],{"class":3039},"\"Product name is required\"",[3022,4583,3043],{"class":3028},[3022,4585,4586,4588,4590,4592,4594,4596,4598,4600,4603],{"class":3024,"line":3378},[3022,4587,4384],{"class":3028},[3022,4589,4397],{"class":3032},[3022,4591,3036],{"class":3028},[3022,4593,4402],{"class":3150},[3022,4595,3114],{"class":3028},[3022,4597,4576],{"class":3077},[3022,4599,3223],{"class":3028},[3022,4601,4602],{"class":3039},"\"Name cannot exceed 200 characters\"",[3022,4604,3043],{"class":3028},[3022,4606,4607,4609,4611,4613,4615,4617,4619,4621,4624],{"class":3024,"line":3803},[3022,4608,4005],{"class":3049},[3022,4610,4411],{"class":3049},[3022,4612,4414],{"class":3049},[3022,4614,4417],{"class":3077},[3022,4616,3354],{"class":3028},[3022,4618,4366],{"class":3049},[3022,4620,4369],{"class":3028},[3022,4622,4623],{"class":3049},"init",[3022,4625,4375],{"class":3028},[3022,4627,4628],{"class":3024,"line":3809},[3022,4629,4042],{"emptyLinePlaceholder":4041},[3022,4631,4633,4635,4637,4639,4641,4643,4645,4647,4649,4651,4654],{"class":3024,"line":4632},28,[3022,4634,4384],{"class":3028},[3022,4636,4438],{"class":3032},[3022,4638,3036],{"class":3028},[3022,4640,4443],{"class":3150},[3022,4642,3114],{"class":3028},[3022,4644,4448],{"class":3150},[3022,4646,3114],{"class":3028},[3022,4648,4576],{"class":3077},[3022,4650,3223],{"class":3028},[3022,4652,4653],{"class":3039},"\"Price must be between 0.01 and 1,000,000\"",[3022,4655,3043],{"class":3028},[3022,4657,4659,4661,4663,4665,4667,4669,4671,4673],{"class":3024,"line":4658},29,[3022,4660,4005],{"class":3049},[3022,4662,4457],{"class":3049},[3022,4664,4460],{"class":3077},[3022,4666,3354],{"class":3028},[3022,4668,4366],{"class":3049},[3022,4670,4369],{"class":3028},[3022,4672,4623],{"class":3049},[3022,4674,4375],{"class":3028},[3022,4676,4678],{"class":3024,"line":4677},30,[3022,4679,4042],{"emptyLinePlaceholder":4041},[3022,4681,4683,4685,4687,4689,4691,4693,4695,4697,4699,4701,4703,4705,4708],{"class":3024,"line":4682},31,[3022,4684,4384],{"class":3028},[3022,4686,4438],{"class":3032},[3022,4688,3036],{"class":3028},[3022,4690,3151],{"class":3150},[3022,4692,3114],{"class":3028},[3022,4694,3074],{"class":3049},[3022,4696,3103],{"class":3028},[3022,4698,4493],{"class":3077},[3022,4700,3114],{"class":3028},[3022,4702,4576],{"class":3077},[3022,4704,3223],{"class":3028},[3022,4706,4707],{"class":3039},"\"Stock cannot be negative\"",[3022,4709,3043],{"class":3028},[3022,4711,4713,4715,4717,4719,4721,4723,4725,4727],{"class":3024,"line":4712},32,[3022,4714,4005],{"class":3049},[3022,4716,4358],{"class":3049},[3022,4718,4504],{"class":3077},[3022,4720,3354],{"class":3028},[3022,4722,4366],{"class":3049},[3022,4724,4369],{"class":3028},[3022,4726,4623],{"class":3049},[3022,4728,4375],{"class":3028},[3022,4730,4732],{"class":3024,"line":4731},33,[3022,4733,3381],{"class":3028},[3652,4735],{},[3423,4737,4739],{"id":4738},"крок-2-створення-фільтрів","Крок 2: Створення фільтрів",[4741,4742,4744],"h4",{"id":4743},"_1-apikeyauthfilter-авторизація-через-api-ключ","1. ApiKeyAuthFilter — Авторизація через API-ключ",[2964,4746,4286,4747,3437],{},[3019,4748,4749],{},"Filters/ApiKeyAuthFilter.cs",[3012,4751,4753],{"className":3014,"code":4752,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.Filters;\n\nnamespace EcommerceFiltersApi.Filters;\n\npublic class ApiKeyAuthFilter : IAsyncAuthorizationFilter\n{\n    private const string ApiKeyHeaderName = \"X-Api-Key\";\n    private readonly IConfiguration _configuration;\n    private readonly ILogger\u003CApiKeyAuthFilter> _logger;\n\n    public ApiKeyAuthFilter(IConfiguration configuration, ILogger\u003CApiKeyAuthFilter> logger)\n    {\n        _configuration = configuration;\n        _logger = logger;\n    }\n\n    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)\n    {\n        // Перевіряємо наявність API-ключа у заголовку\n        if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))\n        {\n            _logger.LogWarning(\"API key is missing in request to {Path}\", context.HttpContext.Request.Path);\n            \n            context.Result = new UnauthorizedObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status401Unauthorized,\n                Title = \"API Key Required\",\n                Detail = $\"API key must be provided in {ApiKeyHeaderName} header\",\n                Instance = context.HttpContext.Request.Path\n            });\n            return;\n        }\n\n        // Отримуємо валідні ключі з конфігурації\n        var validApiKeys = _configuration.GetSection(\"ApiKeys\").Get\u003Cstring[]>() ?? Array.Empty\u003Cstring>();\n\n        // Перевіряємо валідність ключа\n        if (!validApiKeys.Contains(extractedApiKey.ToString()))\n        {\n            _logger.LogWarning(\"Invalid API key attempted: {ApiKey}\", extractedApiKey);\n            \n            context.Result = new UnauthorizedObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status401Unauthorized,\n                Title = \"Invalid API Key\",\n                Detail = \"The provided API key is not valid\",\n                Instance = context.HttpContext.Request.Path\n            });\n            return;\n        }\n\n        _logger.LogInformation(\"API key validated successfully for {Path}\", context.HttpContext.Request.Path);\n        \n        // Додаємо інформацію про API key до HttpContext для використання в контролерах\n        context.HttpContext.Items[\"ApiKey\"] = extractedApiKey.ToString();\n        \n        await Task.CompletedTask;\n    }\n}\n",[3019,4754,4755,4774,4795,4799,4811,4815,4829,4833,4853,4868,4889,4893,4923,4927,4939,4950,4954,4958,4978,4982,4987,5036,5041,5075,5080,5103,5108,5125,5137,5161,5183,5188,5195,5200,5205,5211,5263,5268,5274,5302,5307,5327,5332,5353,5358,5373,5385,5397,5418,5423,5430,5435,5440,5472,5477,5483,5514,5519,5534,5539],{"__ignoreMap":3017},[3022,4756,4757,4759,4762,4764,4767,4769,4772],{"class":3024,"line":3025},[3022,4758,4299],{"class":3138},[3022,4760,4761],{"class":3032}," Microsoft",[3022,4763,3103],{"class":3028},[3022,4765,4766],{"class":3032},"AspNetCore",[3022,4768,3103],{"class":3028},[3022,4770,4771],{"class":3032},"Mvc",[3022,4773,4315],{"class":3028},[3022,4775,4776,4778,4780,4782,4784,4786,4788,4790,4793],{"class":3024,"line":3046},[3022,4777,4299],{"class":3138},[3022,4779,4761],{"class":3032},[3022,4781,3103],{"class":3028},[3022,4783,4766],{"class":3032},[3022,4785,3103],{"class":3028},[3022,4787,4771],{"class":3032},[3022,4789,3103],{"class":3028},[3022,4791,4792],{"class":3032},"Filters",[3022,4794,4315],{"class":3028},[3022,4796,4797],{"class":3024,"line":3084},[3022,4798,4042],{"emptyLinePlaceholder":4041},[3022,4800,4801,4803,4805,4807,4809],{"class":3024,"line":3090},[3022,4802,4324],{"class":3049},[3022,4804,4327],{"class":3032},[3022,4806,3103],{"class":3028},[3022,4808,4792],{"class":3032},[3022,4810,4315],{"class":3028},[3022,4812,4813],{"class":3024,"line":3097},[3022,4814,4042],{"emptyLinePlaceholder":4041},[3022,4816,4817,4819,4821,4824,4826],{"class":3024,"line":3123},[3022,4818,3050],{"class":3049},[3022,4820,3987],{"class":3049},[3022,4822,4823],{"class":3032}," ApiKeyAuthFilter",[3022,4825,3993],{"class":3028},[3022,4827,4828],{"class":3032},"IAsyncAuthorizationFilter\n",[3022,4830,4831],{"class":3024,"line":3129},[3022,4832,3087],{"class":3028},[3022,4834,4835,4838,4841,4843,4846,4848,4851],{"class":3024,"line":3135},[3022,4836,4837],{"class":3049},"    private",[3022,4839,4840],{"class":3049}," const",[3022,4842,4414],{"class":3049},[3022,4844,4845],{"class":3077}," ApiKeyHeaderName",[3022,4847,3223],{"class":3028},[3022,4849,4850],{"class":3039},"\"X-Api-Key\"",[3022,4852,4315],{"class":3028},[3022,4854,4855,4857,4860,4863,4866],{"class":3024,"line":3170},[3022,4856,4837],{"class":3049},[3022,4858,4859],{"class":3049}," readonly",[3022,4861,4862],{"class":3032}," IConfiguration",[3022,4864,4865],{"class":3077}," _configuration",[3022,4867,4315],{"class":3028},[3022,4869,4870,4872,4874,4877,4879,4882,4884,4887],{"class":3024,"line":3175},[3022,4871,4837],{"class":3049},[3022,4873,4859],{"class":3049},[3022,4875,4876],{"class":3032}," ILogger",[3022,4878,3059],{"class":3028},[3022,4880,4881],{"class":3032},"ApiKeyAuthFilter",[3022,4883,3065],{"class":3028},[3022,4885,4886],{"class":3077},"_logger",[3022,4888,4315],{"class":3028},[3022,4890,4891],{"class":3024,"line":3181},[3022,4892,4042],{"emptyLinePlaceholder":4041},[3022,4894,4895,4897,4899,4901,4904,4907,4909,4912,4914,4916,4918,4921],{"class":3024,"line":3203},[3022,4896,4005],{"class":3049},[3022,4898,4823],{"class":3068},[3022,4900,3036],{"class":3028},[3022,4902,4903],{"class":3032},"IConfiguration",[3022,4905,4906],{"class":3077}," configuration",[3022,4908,3114],{"class":3028},[3022,4910,4911],{"class":3032},"ILogger",[3022,4913,3059],{"class":3028},[3022,4915,4881],{"class":3032},[3022,4917,3065],{"class":3028},[3022,4919,4920],{"class":3077},"logger",[3022,4922,3081],{"class":3028},[3022,4924,4925],{"class":3024,"line":3208},[3022,4926,4026],{"class":3028},[3022,4928,4929,4932,4934,4937],{"class":3024,"line":3214},[3022,4930,4931],{"class":3077},"        _configuration",[3022,4933,3223],{"class":3028},[3022,4935,4936],{"class":3077},"configuration",[3022,4938,4315],{"class":3028},[3022,4940,4941,4944,4946,4948],{"class":3024,"line":3236},[3022,4942,4943],{"class":3077},"        _logger",[3022,4945,3223],{"class":3028},[3022,4947,4920],{"class":3077},[3022,4949,4315],{"class":3028},[3022,4951,4952],{"class":3024,"line":3241},[3022,4953,4036],{"class":3028},[3022,4955,4956],{"class":3024,"line":3247},[3022,4957,4042],{"emptyLinePlaceholder":4041},[3022,4959,4960,4962,4964,4966,4969,4971,4974,4976],{"class":3024,"line":3279},[3022,4961,4005],{"class":3049},[3022,4963,3053],{"class":3049},[3022,4965,3056],{"class":3032},[3022,4967,4968],{"class":3068}," OnAuthorizationAsync",[3022,4970,3036],{"class":3028},[3022,4972,4973],{"class":3032},"AuthorizationFilterContext",[3022,4975,4019],{"class":3077},[3022,4977,3081],{"class":3028},[3022,4979,4980],{"class":3024,"line":3284},[3022,4981,4026],{"class":3028},[3022,4983,4984],{"class":3024,"line":3290},[3022,4985,4986],{"class":3093},"        // Перевіряємо наявність API-ключа у заголовку\n",[3022,4988,4989,4992,4994,4997,4999,5002,5004,5007,5009,5012,5014,5017,5019,5022,5024,5027,5030,5033],{"class":3024,"line":3303},[3022,4990,4991],{"class":3138},"        if",[3022,4993,3186],{"class":3028},[3022,4995,4996],{"class":3077},"context",[3022,4998,3103],{"class":3028},[3022,5000,5001],{"class":3077},"HttpContext",[3022,5003,3103],{"class":3028},[3022,5005,5006],{"class":3077},"Request",[3022,5008,3103],{"class":3028},[3022,5010,5011],{"class":3077},"Headers",[3022,5013,3103],{"class":3028},[3022,5015,5016],{"class":3068},"TryGetValue",[3022,5018,3036],{"class":3028},[3022,5020,5021],{"class":3077},"ApiKeyHeaderName",[3022,5023,3114],{"class":3028},[3022,5025,5026],{"class":3049},"out",[3022,5028,5029],{"class":3049}," var",[3022,5031,5032],{"class":3077}," extractedApiKey",[3022,5034,5035],{"class":3028},"))\n",[3022,5037,5038],{"class":3024,"line":3329},[3022,5039,5040],{"class":3028},"        {\n",[3022,5042,5043,5046,5048,5051,5053,5056,5058,5060,5062,5064,5066,5068,5070,5073],{"class":3024,"line":3334},[3022,5044,5045],{"class":3077},"            _logger",[3022,5047,3103],{"class":3028},[3022,5049,5050],{"class":3068},"LogWarning",[3022,5052,3036],{"class":3028},[3022,5054,5055],{"class":3039},"\"API key is missing in request to {Path}\"",[3022,5057,3114],{"class":3028},[3022,5059,4996],{"class":3077},[3022,5061,3103],{"class":3028},[3022,5063,5001],{"class":3077},[3022,5065,3103],{"class":3028},[3022,5067,5006],{"class":3077},[3022,5069,3103],{"class":3028},[3022,5071,5072],{"class":3077},"Path",[3022,5074,3120],{"class":3028},[3022,5076,5077],{"class":3024,"line":3340},[3022,5078,5079],{"class":3028},"            \n",[3022,5081,5082,5085,5087,5089,5091,5093,5096,5098,5100],{"class":3024,"line":3378},[3022,5083,5084],{"class":3077},"            context",[3022,5086,3103],{"class":3028},[3022,5088,3921],{"class":3077},[3022,5090,3223],{"class":3028},[3022,5092,3351],{"class":3049},[3022,5094,5095],{"class":3032}," UnauthorizedObjectResult",[3022,5097,3036],{"class":3028},[3022,5099,3351],{"class":3049},[3022,5101,5102],{"class":3032}," ProblemDetails\n",[3022,5104,5105],{"class":3024,"line":3803},[3022,5106,5107],{"class":3028},"            {\n",[3022,5109,5110,5113,5115,5118,5120,5123],{"class":3024,"line":3809},[3022,5111,5112],{"class":3077},"                Status",[3022,5114,3223],{"class":3028},[3022,5116,5117],{"class":3077},"StatusCodes",[3022,5119,3103],{"class":3028},[3022,5121,5122],{"class":3077},"Status401Unauthorized",[3022,5124,3527],{"class":3028},[3022,5126,5127,5130,5132,5135],{"class":3024,"line":4632},[3022,5128,5129],{"class":3077},"                Title",[3022,5131,3223],{"class":3028},[3022,5133,5134],{"class":3039},"\"API Key Required\"",[3022,5136,3527],{"class":3028},[3022,5138,5139,5142,5144,5147,5151,5153,5156,5159],{"class":3024,"line":4658},[3022,5140,5141],{"class":3077},"                Detail",[3022,5143,3223],{"class":3028},[3022,5145,5146],{"class":3039},"$\"API key must be provided in ",[3022,5148,5150],{"class":5149},"sD7JJ","{",[3022,5152,5021],{"class":3077},[3022,5154,5155],{"class":5149},"}",[3022,5157,5158],{"class":3039}," header\"",[3022,5160,3527],{"class":3028},[3022,5162,5163,5166,5168,5170,5172,5174,5176,5178,5180],{"class":3024,"line":4677},[3022,5164,5165],{"class":3077},"                Instance",[3022,5167,3223],{"class":3028},[3022,5169,4996],{"class":3077},[3022,5171,3103],{"class":3028},[3022,5173,5001],{"class":3077},[3022,5175,3103],{"class":3028},[3022,5177,5006],{"class":3077},[3022,5179,3103],{"class":3028},[3022,5181,5182],{"class":3077},"Path\n",[3022,5184,5185],{"class":3024,"line":4682},[3022,5186,5187],{"class":3028},"            });\n",[3022,5189,5190,5193],{"class":3024,"line":4712},[3022,5191,5192],{"class":3138},"            return",[3022,5194,4315],{"class":3028},[3022,5196,5197],{"class":3024,"line":4731},[3022,5198,5199],{"class":3028},"        }\n",[3022,5201,5203],{"class":3024,"line":5202},34,[3022,5204,4042],{"emptyLinePlaceholder":4041},[3022,5206,5208],{"class":3024,"line":5207},35,[3022,5209,5210],{"class":3093},"        // Отримуємо валідні ключі з конфігурації\n",[3022,5212,5214,5216,5219,5221,5224,5226,5229,5231,5234,5237,5240,5242,5245,5248,5251,5253,5256,5258,5260],{"class":3024,"line":5213},36,[3022,5215,4156],{"class":3049},[3022,5217,5218],{"class":3077}," validApiKeys",[3022,5220,3223],{"class":3028},[3022,5222,5223],{"class":3077},"_configuration",[3022,5225,3103],{"class":3028},[3022,5227,5228],{"class":3068},"GetSection",[3022,5230,3036],{"class":3028},[3022,5232,5233],{"class":3039},"\"ApiKeys\"",[3022,5235,5236],{"class":3028},").",[3022,5238,5239],{"class":3068},"Get",[3022,5241,3059],{"class":3028},[3022,5243,5244],{"class":3049},"string",[3022,5246,5247],{"class":3028},"[]>() ?? ",[3022,5249,5250],{"class":3077},"Array",[3022,5252,3103],{"class":3028},[3022,5254,5255],{"class":3068},"Empty",[3022,5257,3059],{"class":3028},[3022,5259,5244],{"class":3049},[3022,5261,5262],{"class":3028},">();\n",[3022,5264,5266],{"class":3024,"line":5265},37,[3022,5267,4042],{"emptyLinePlaceholder":4041},[3022,5269,5271],{"class":3024,"line":5270},38,[3022,5272,5273],{"class":3093},"        // Перевіряємо валідність ключа\n",[3022,5275,5277,5279,5281,5284,5286,5289,5291,5294,5296,5299],{"class":3024,"line":5276},39,[3022,5278,4991],{"class":3138},[3022,5280,3186],{"class":3028},[3022,5282,5283],{"class":3077},"validApiKeys",[3022,5285,3103],{"class":3028},[3022,5287,5288],{"class":3068},"Contains",[3022,5290,3036],{"class":3028},[3022,5292,5293],{"class":3077},"extractedApiKey",[3022,5295,3103],{"class":3028},[3022,5297,5298],{"class":3068},"ToString",[3022,5300,5301],{"class":3028},"()))\n",[3022,5303,5305],{"class":3024,"line":5304},40,[3022,5306,5040],{"class":3028},[3022,5308,5310,5312,5314,5316,5318,5321,5323,5325],{"class":3024,"line":5309},41,[3022,5311,5045],{"class":3077},[3022,5313,3103],{"class":3028},[3022,5315,5050],{"class":3068},[3022,5317,3036],{"class":3028},[3022,5319,5320],{"class":3039},"\"Invalid API key attempted: {ApiKey}\"",[3022,5322,3114],{"class":3028},[3022,5324,5293],{"class":3077},[3022,5326,3120],{"class":3028},[3022,5328,5330],{"class":3024,"line":5329},42,[3022,5331,5079],{"class":3028},[3022,5333,5335,5337,5339,5341,5343,5345,5347,5349,5351],{"class":3024,"line":5334},43,[3022,5336,5084],{"class":3077},[3022,5338,3103],{"class":3028},[3022,5340,3921],{"class":3077},[3022,5342,3223],{"class":3028},[3022,5344,3351],{"class":3049},[3022,5346,5095],{"class":3032},[3022,5348,3036],{"class":3028},[3022,5350,3351],{"class":3049},[3022,5352,5102],{"class":3032},[3022,5354,5356],{"class":3024,"line":5355},44,[3022,5357,5107],{"class":3028},[3022,5359,5361,5363,5365,5367,5369,5371],{"class":3024,"line":5360},45,[3022,5362,5112],{"class":3077},[3022,5364,3223],{"class":3028},[3022,5366,5117],{"class":3077},[3022,5368,3103],{"class":3028},[3022,5370,5122],{"class":3077},[3022,5372,3527],{"class":3028},[3022,5374,5376,5378,5380,5383],{"class":3024,"line":5375},46,[3022,5377,5129],{"class":3077},[3022,5379,3223],{"class":3028},[3022,5381,5382],{"class":3039},"\"Invalid API Key\"",[3022,5384,3527],{"class":3028},[3022,5386,5388,5390,5392,5395],{"class":3024,"line":5387},47,[3022,5389,5141],{"class":3077},[3022,5391,3223],{"class":3028},[3022,5393,5394],{"class":3039},"\"The provided API key is not valid\"",[3022,5396,3527],{"class":3028},[3022,5398,5400,5402,5404,5406,5408,5410,5412,5414,5416],{"class":3024,"line":5399},48,[3022,5401,5165],{"class":3077},[3022,5403,3223],{"class":3028},[3022,5405,4996],{"class":3077},[3022,5407,3103],{"class":3028},[3022,5409,5001],{"class":3077},[3022,5411,3103],{"class":3028},[3022,5413,5006],{"class":3077},[3022,5415,3103],{"class":3028},[3022,5417,5182],{"class":3077},[3022,5419,5421],{"class":3024,"line":5420},49,[3022,5422,5187],{"class":3028},[3022,5424,5426,5428],{"class":3024,"line":5425},50,[3022,5427,5192],{"class":3138},[3022,5429,4315],{"class":3028},[3022,5431,5433],{"class":3024,"line":5432},51,[3022,5434,5199],{"class":3028},[3022,5436,5438],{"class":3024,"line":5437},52,[3022,5439,4042],{"emptyLinePlaceholder":4041},[3022,5441,5443,5445,5447,5449,5451,5454,5456,5458,5460,5462,5464,5466,5468,5470],{"class":3024,"line":5442},53,[3022,5444,4943],{"class":3077},[3022,5446,3103],{"class":3028},[3022,5448,3106],{"class":3068},[3022,5450,3036],{"class":3028},[3022,5452,5453],{"class":3039},"\"API key validated successfully for {Path}\"",[3022,5455,3114],{"class":3028},[3022,5457,4996],{"class":3077},[3022,5459,3103],{"class":3028},[3022,5461,5001],{"class":3077},[3022,5463,3103],{"class":3028},[3022,5465,5006],{"class":3077},[3022,5467,3103],{"class":3028},[3022,5469,5072],{"class":3077},[3022,5471,3120],{"class":3028},[3022,5473,5475],{"class":3024,"line":5474},54,[3022,5476,4151],{"class":3028},[3022,5478,5480],{"class":3024,"line":5479},55,[3022,5481,5482],{"class":3093},"        // Додаємо інформацію про API key до HttpContext для використання в контролерах\n",[3022,5484,5486,5489,5491,5493,5495,5498,5500,5503,5506,5508,5510,5512],{"class":3024,"line":5485},56,[3022,5487,5488],{"class":3077},"        context",[3022,5490,3103],{"class":3028},[3022,5492,5001],{"class":3077},[3022,5494,3103],{"class":3028},[3022,5496,5497],{"class":3077},"Items",[3022,5499,3029],{"class":3028},[3022,5501,5502],{"class":3039},"\"ApiKey\"",[3022,5504,5505],{"class":3028},"] = ",[3022,5507,5293],{"class":3077},[3022,5509,3103],{"class":3028},[3022,5511,5298],{"class":3068},[3022,5513,3200],{"class":3028},[3022,5515,5517],{"class":3024,"line":5516},57,[3022,5518,4151],{"class":3028},[3022,5520,5522,5525,5527,5529,5532],{"class":3024,"line":5521},58,[3022,5523,5524],{"class":3049},"        await",[3022,5526,3056],{"class":3077},[3022,5528,3103],{"class":3028},[3022,5530,5531],{"class":3077},"CompletedTask",[3022,5533,4315],{"class":3028},[3022,5535,5537],{"class":3024,"line":5536},59,[3022,5538,4036],{"class":3028},[3022,5540,5542],{"class":3024,"line":5541},60,[3022,5543,3381],{"class":3028},[2964,5545,5546],{},[2974,5547,5548],{},"Декомпозиція:",[5550,5551,5552,5560,5569,5575,5583,5588],"ol",{},[2971,5553,5554,5559],{},[2974,5555,5556],{},[3019,5557,5558],{},"IAsyncAuthorizationFilter"," — виконується першим у pipeline",[2971,5561,5562,5565,5566],{},[2974,5563,5564],{},"Перевірка наявності"," — чи є заголовок ",[3019,5567,5568],{},"X-Api-Key",[2971,5570,5571,5574],{},[2974,5572,5573],{},"Валідація ключа"," — порівняння з конфігурацією",[2971,5576,5577,5582],{},[2974,5578,5579],{},[3019,5580,5581],{},"context.Result"," — якщо встановлено, action не виконується",[2971,5584,5585,5587],{},[2974,5586,2976],{}," — всі спроби авторизації логуються",[2971,5589,5590,5595],{},[2974,5591,5592],{},[3019,5593,5594],{},"HttpContext.Items"," — зберігаємо ключ для використання в контролерах",[4741,5597,5599],{"id":5598},"_2-requestvalidationfilter-централізована-валідація","2. RequestValidationFilter — Централізована валідація",[2964,5601,4286,5602,3437],{},[3019,5603,5604],{},"Filters/RequestValidationFilter.cs",[3012,5606,5608],{"className":3014,"code":5607,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.Filters;\n\nnamespace EcommerceFiltersApi.Filters;\n\npublic class RequestValidationFilter : IAsyncActionFilter\n{\n    private readonly ILogger\u003CRequestValidationFilter> _logger;\n\n    public RequestValidationFilter(ILogger\u003CRequestValidationFilter> logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // Перевіряємо ModelState (DataAnnotations валідація)\n        if (!context.ModelState.IsValid)\n        {\n            _logger.LogWarning(\n                \"Validation failed for {Action}. Errors: {Errors}\",\n                context.ActionDescriptor.DisplayName,\n                string.Join(\", \", context.ModelState.Values\n                    .SelectMany(v => v.Errors)\n                    .Select(e => e.ErrorMessage)));\n\n            var errors = context.ModelState\n                .Where(x => x.Value?.Errors.Count > 0)\n                .ToDictionary(\n                    kvp => kvp.Key,\n                    kvp => kvp.Value!.Errors.Select(e => e.ErrorMessage).ToArray()\n                );\n\n            var problemDetails = new ValidationProblemDetails(errors)\n            {\n                Status = StatusCodes.Status400BadRequest,\n                Title = \"One or more validation errors occurred\",\n                Instance = context.HttpContext.Request.Path\n            };\n\n            context.Result = new BadRequestObjectResult(problemDetails);\n            return; // Не викликаємо next() - action не виконується\n        }\n\n        // Кастомна валідація: перевірка бізнес-правил\n        foreach (var argument in context.ActionArguments.Values)\n        {\n            if (argument is CreateProductDto dto)\n            {\n                // Приклад бізнес-правила: ціна має бути кратною 0.01\n                if (dto.Price % 0.01m != 0)\n                {\n                    context.Result = new BadRequestObjectResult(new ProblemDetails\n                    {\n                        Status = StatusCodes.Status400BadRequest,\n                        Title = \"Invalid Price Format\",\n                        Detail = \"Price must have at most 2 decimal places\",\n                        Instance = context.HttpContext.Request.Path\n                    });\n                    return;\n                }\n            }\n        }\n\n        _logger.LogInformation(\"Validation passed for {Action}\", context.ActionDescriptor.DisplayName);\n\n        // Валідація пройшла - виконуємо action\n        await next();\n    }\n}\n",[3019,5609,5610,5626,5646,5650,5662,5666,5679,5683,5702,5706,5726,5730,5740,5744,5748,5760,5768,5776,5780,5785,5805,5809,5819,5826,5843,5871,5896,5919,5923,5940,5979,5988,6005,6046,6051,6055,6076,6080,6095,6106,6126,6131,6135,6157,6166,6170,6174,6179,6209,6213,6234,6238,6243,6271,6276,6297,6302,6317,6329,6341,6362,6368,6376,6382,6388,6393,6398,6426,6431,6437,6446,6451],{"__ignoreMap":3017},[3022,5611,5612,5614,5616,5618,5620,5622,5624],{"class":3024,"line":3025},[3022,5613,4299],{"class":3138},[3022,5615,4761],{"class":3032},[3022,5617,3103],{"class":3028},[3022,5619,4766],{"class":3032},[3022,5621,3103],{"class":3028},[3022,5623,4771],{"class":3032},[3022,5625,4315],{"class":3028},[3022,5627,5628,5630,5632,5634,5636,5638,5640,5642,5644],{"class":3024,"line":3046},[3022,5629,4299],{"class":3138},[3022,5631,4761],{"class":3032},[3022,5633,3103],{"class":3028},[3022,5635,4766],{"class":3032},[3022,5637,3103],{"class":3028},[3022,5639,4771],{"class":3032},[3022,5641,3103],{"class":3028},[3022,5643,4792],{"class":3032},[3022,5645,4315],{"class":3028},[3022,5647,5648],{"class":3024,"line":3084},[3022,5649,4042],{"emptyLinePlaceholder":4041},[3022,5651,5652,5654,5656,5658,5660],{"class":3024,"line":3090},[3022,5653,4324],{"class":3049},[3022,5655,4327],{"class":3032},[3022,5657,3103],{"class":3028},[3022,5659,4792],{"class":3032},[3022,5661,4315],{"class":3028},[3022,5663,5664],{"class":3024,"line":3097},[3022,5665,4042],{"emptyLinePlaceholder":4041},[3022,5667,5668,5670,5672,5675,5677],{"class":3024,"line":3123},[3022,5669,3050],{"class":3049},[3022,5671,3987],{"class":3049},[3022,5673,5674],{"class":3032}," RequestValidationFilter",[3022,5676,3993],{"class":3028},[3022,5678,4101],{"class":3032},[3022,5680,5681],{"class":3024,"line":3129},[3022,5682,3087],{"class":3028},[3022,5684,5685,5687,5689,5691,5693,5696,5698,5700],{"class":3024,"line":3135},[3022,5686,4837],{"class":3049},[3022,5688,4859],{"class":3049},[3022,5690,4876],{"class":3032},[3022,5692,3059],{"class":3028},[3022,5694,5695],{"class":3032},"RequestValidationFilter",[3022,5697,3065],{"class":3028},[3022,5699,4886],{"class":3077},[3022,5701,4315],{"class":3028},[3022,5703,5704],{"class":3024,"line":3170},[3022,5705,4042],{"emptyLinePlaceholder":4041},[3022,5707,5708,5710,5712,5714,5716,5718,5720,5722,5724],{"class":3024,"line":3175},[3022,5709,4005],{"class":3049},[3022,5711,5674],{"class":3068},[3022,5713,3036],{"class":3028},[3022,5715,4911],{"class":3032},[3022,5717,3059],{"class":3028},[3022,5719,5695],{"class":3032},[3022,5721,3065],{"class":3028},[3022,5723,4920],{"class":3077},[3022,5725,3081],{"class":3028},[3022,5727,5728],{"class":3024,"line":3181},[3022,5729,4026],{"class":3028},[3022,5731,5732,5734,5736,5738],{"class":3024,"line":3203},[3022,5733,4943],{"class":3077},[3022,5735,3223],{"class":3028},[3022,5737,4920],{"class":3077},[3022,5739,4315],{"class":3028},[3022,5741,5742],{"class":3024,"line":3208},[3022,5743,4036],{"class":3028},[3022,5745,5746],{"class":3024,"line":3214},[3022,5747,4042],{"emptyLinePlaceholder":4041},[3022,5749,5750,5752,5754,5756,5758],{"class":3024,"line":3236},[3022,5751,4005],{"class":3049},[3022,5753,3053],{"class":3049},[3022,5755,3056],{"class":3032},[3022,5757,4116],{"class":3068},[3022,5759,4119],{"class":3028},[3022,5761,5762,5764,5766],{"class":3024,"line":3241},[3022,5763,4124],{"class":3032},[3022,5765,4019],{"class":3077},[3022,5767,3527],{"class":3028},[3022,5769,5770,5772,5774],{"class":3024,"line":3247},[3022,5771,4133],{"class":3032},[3022,5773,4136],{"class":3077},[3022,5775,3081],{"class":3028},[3022,5777,5778],{"class":3024,"line":3279},[3022,5779,4026],{"class":3028},[3022,5781,5782],{"class":3024,"line":3284},[3022,5783,5784],{"class":3093},"        // Перевіряємо ModelState (DataAnnotations валідація)\n",[3022,5786,5787,5789,5791,5793,5795,5798,5800,5803],{"class":3024,"line":3290},[3022,5788,4991],{"class":3138},[3022,5790,3186],{"class":3028},[3022,5792,4996],{"class":3077},[3022,5794,3103],{"class":3028},[3022,5796,5797],{"class":3077},"ModelState",[3022,5799,3103],{"class":3028},[3022,5801,5802],{"class":3077},"IsValid",[3022,5804,3081],{"class":3028},[3022,5806,5807],{"class":3024,"line":3303},[3022,5808,5040],{"class":3028},[3022,5810,5811,5813,5815,5817],{"class":3024,"line":3329},[3022,5812,5045],{"class":3077},[3022,5814,3103],{"class":3028},[3022,5816,5050],{"class":3068},[3022,5818,4119],{"class":3028},[3022,5820,5821,5824],{"class":3024,"line":3334},[3022,5822,5823],{"class":3039},"                \"Validation failed for {Action}. Errors: {Errors}\"",[3022,5825,3527],{"class":3028},[3022,5827,5828,5831,5833,5836,5838,5841],{"class":3024,"line":3340},[3022,5829,5830],{"class":3077},"                context",[3022,5832,3103],{"class":3028},[3022,5834,5835],{"class":3077},"ActionDescriptor",[3022,5837,3103],{"class":3028},[3022,5839,5840],{"class":3077},"DisplayName",[3022,5842,3527],{"class":3028},[3022,5844,5845,5848,5850,5853,5855,5858,5860,5862,5864,5866,5868],{"class":3024,"line":3378},[3022,5846,5847],{"class":3049},"                string",[3022,5849,3103],{"class":3028},[3022,5851,5852],{"class":3068},"Join",[3022,5854,3036],{"class":3028},[3022,5856,5857],{"class":3039},"\", \"",[3022,5859,3114],{"class":3028},[3022,5861,4996],{"class":3077},[3022,5863,3103],{"class":3028},[3022,5865,5797],{"class":3077},[3022,5867,3103],{"class":3028},[3022,5869,5870],{"class":3077},"Values\n",[3022,5872,5873,5876,5879,5881,5884,5887,5889,5891,5894],{"class":3024,"line":3803},[3022,5874,5875],{"class":3028},"                    .",[3022,5877,5878],{"class":3068},"SelectMany",[3022,5880,3036],{"class":3028},[3022,5882,5883],{"class":3077},"v",[3022,5885,5886],{"class":3028}," => ",[3022,5888,5883],{"class":3077},[3022,5890,3103],{"class":3028},[3022,5892,5893],{"class":3077},"Errors",[3022,5895,3081],{"class":3028},[3022,5897,5898,5900,5903,5905,5908,5910,5912,5914,5916],{"class":3024,"line":3809},[3022,5899,5875],{"class":3028},[3022,5901,5902],{"class":3068},"Select",[3022,5904,3036],{"class":3028},[3022,5906,5907],{"class":3077},"e",[3022,5909,5886],{"class":3028},[3022,5911,5907],{"class":3077},[3022,5913,3103],{"class":3028},[3022,5915,4576],{"class":3077},[3022,5917,5918],{"class":3028},")));\n",[3022,5920,5921],{"class":3024,"line":4632},[3022,5922,4042],{"emptyLinePlaceholder":4041},[3022,5924,5925,5928,5931,5933,5935,5937],{"class":3024,"line":4658},[3022,5926,5927],{"class":3049},"            var",[3022,5929,5930],{"class":3077}," errors",[3022,5932,3223],{"class":3028},[3022,5934,4996],{"class":3077},[3022,5936,3103],{"class":3028},[3022,5938,5939],{"class":3077},"ModelState\n",[3022,5941,5942,5945,5948,5950,5953,5955,5957,5959,5962,5965,5967,5969,5972,5975,5977],{"class":3024,"line":4677},[3022,5943,5944],{"class":3028},"                .",[3022,5946,5947],{"class":3068},"Where",[3022,5949,3036],{"class":3028},[3022,5951,5952],{"class":3077},"x",[3022,5954,5886],{"class":3028},[3022,5956,5952],{"class":3077},[3022,5958,3103],{"class":3028},[3022,5960,5961],{"class":3077},"Value",[3022,5963,5964],{"class":3028},"?.",[3022,5966,5893],{"class":3077},[3022,5968,3103],{"class":3028},[3022,5970,5971],{"class":3077},"Count",[3022,5973,5974],{"class":3028}," > ",[3022,5976,3151],{"class":3150},[3022,5978,3081],{"class":3028},[3022,5980,5981,5983,5986],{"class":3024,"line":4682},[3022,5982,5944],{"class":3028},[3022,5984,5985],{"class":3068},"ToDictionary",[3022,5987,4119],{"class":3028},[3022,5989,5990,5993,5995,5998,6000,6003],{"class":3024,"line":4712},[3022,5991,5992],{"class":3077},"                    kvp",[3022,5994,5886],{"class":3028},[3022,5996,5997],{"class":3077},"kvp",[3022,5999,3103],{"class":3028},[3022,6001,6002],{"class":3077},"Key",[3022,6004,3527],{"class":3028},[3022,6006,6007,6009,6011,6013,6015,6017,6020,6022,6024,6026,6028,6030,6032,6034,6036,6038,6040,6043],{"class":3024,"line":4731},[3022,6008,5992],{"class":3077},[3022,6010,5886],{"class":3028},[3022,6012,5997],{"class":3077},[3022,6014,3103],{"class":3028},[3022,6016,5961],{"class":3077},[3022,6018,6019],{"class":3028},"!.",[3022,6021,5893],{"class":3077},[3022,6023,3103],{"class":3028},[3022,6025,5902],{"class":3068},[3022,6027,3036],{"class":3028},[3022,6029,5907],{"class":3077},[3022,6031,5886],{"class":3028},[3022,6033,5907],{"class":3077},[3022,6035,3103],{"class":3028},[3022,6037,4576],{"class":3077},[3022,6039,5236],{"class":3028},[3022,6041,6042],{"class":3068},"ToArray",[3022,6044,6045],{"class":3028},"()\n",[3022,6047,6048],{"class":3024,"line":5202},[3022,6049,6050],{"class":3028},"                );\n",[3022,6052,6053],{"class":3024,"line":5207},[3022,6054,4042],{"emptyLinePlaceholder":4041},[3022,6056,6057,6059,6062,6064,6066,6069,6071,6074],{"class":3024,"line":5213},[3022,6058,5927],{"class":3049},[3022,6060,6061],{"class":3077}," problemDetails",[3022,6063,3223],{"class":3028},[3022,6065,3351],{"class":3049},[3022,6067,6068],{"class":3032}," ValidationProblemDetails",[3022,6070,3036],{"class":3028},[3022,6072,6073],{"class":3077},"errors",[3022,6075,3081],{"class":3028},[3022,6077,6078],{"class":3024,"line":5265},[3022,6079,5107],{"class":3028},[3022,6081,6082,6084,6086,6088,6090,6093],{"class":3024,"line":5270},[3022,6083,5112],{"class":3077},[3022,6085,3223],{"class":3028},[3022,6087,5117],{"class":3077},[3022,6089,3103],{"class":3028},[3022,6091,6092],{"class":3077},"Status400BadRequest",[3022,6094,3527],{"class":3028},[3022,6096,6097,6099,6101,6104],{"class":3024,"line":5276},[3022,6098,5129],{"class":3077},[3022,6100,3223],{"class":3028},[3022,6102,6103],{"class":3039},"\"One or more validation errors occurred\"",[3022,6105,3527],{"class":3028},[3022,6107,6108,6110,6112,6114,6116,6118,6120,6122,6124],{"class":3024,"line":5304},[3022,6109,5165],{"class":3077},[3022,6111,3223],{"class":3028},[3022,6113,4996],{"class":3077},[3022,6115,3103],{"class":3028},[3022,6117,5001],{"class":3077},[3022,6119,3103],{"class":3028},[3022,6121,5006],{"class":3077},[3022,6123,3103],{"class":3028},[3022,6125,5182],{"class":3077},[3022,6127,6128],{"class":3024,"line":5309},[3022,6129,6130],{"class":3028},"            };\n",[3022,6132,6133],{"class":3024,"line":5329},[3022,6134,4042],{"emptyLinePlaceholder":4041},[3022,6136,6137,6139,6141,6143,6145,6147,6150,6152,6155],{"class":3024,"line":5334},[3022,6138,5084],{"class":3077},[3022,6140,3103],{"class":3028},[3022,6142,3921],{"class":3077},[3022,6144,3223],{"class":3028},[3022,6146,3351],{"class":3049},[3022,6148,6149],{"class":3032}," BadRequestObjectResult",[3022,6151,3036],{"class":3028},[3022,6153,6154],{"class":3077},"problemDetails",[3022,6156,3120],{"class":3028},[3022,6158,6159,6161,6163],{"class":3024,"line":5355},[3022,6160,5192],{"class":3138},[3022,6162,4369],{"class":3028},[3022,6164,6165],{"class":3093},"// Не викликаємо next() - action не виконується\n",[3022,6167,6168],{"class":3024,"line":5360},[3022,6169,5199],{"class":3028},[3022,6171,6172],{"class":3024,"line":5375},[3022,6173,4042],{"emptyLinePlaceholder":4041},[3022,6175,6176],{"class":3024,"line":5387},[3022,6177,6178],{"class":3093},"        // Кастомна валідація: перевірка бізнес-правил\n",[3022,6180,6181,6184,6186,6189,6192,6195,6197,6199,6202,6204,6207],{"class":3024,"line":5399},[3022,6182,6183],{"class":3138},"        foreach",[3022,6185,3142],{"class":3028},[3022,6187,6188],{"class":3049},"var",[3022,6190,6191],{"class":3077}," argument",[3022,6193,6194],{"class":3138}," in",[3022,6196,4019],{"class":3077},[3022,6198,3103],{"class":3028},[3022,6200,6201],{"class":3077},"ActionArguments",[3022,6203,3103],{"class":3028},[3022,6205,6206],{"class":3077},"Values",[3022,6208,3081],{"class":3028},[3022,6210,6211],{"class":3024,"line":5420},[3022,6212,5040],{"class":3028},[3022,6214,6215,6218,6220,6223,6226,6229,6232],{"class":3024,"line":5425},[3022,6216,6217],{"class":3138},"            if",[3022,6219,3142],{"class":3028},[3022,6221,6222],{"class":3077},"argument",[3022,6224,6225],{"class":3049}," is",[3022,6227,6228],{"class":3032}," CreateProductDto",[3022,6230,6231],{"class":3077}," dto",[3022,6233,3081],{"class":3028},[3022,6235,6236],{"class":3024,"line":5432},[3022,6237,5107],{"class":3028},[3022,6239,6240],{"class":3024,"line":5437},[3022,6241,6242],{"class":3093},"                // Приклад бізнес-правила: ціна має бути кратною 0.01\n",[3022,6244,6245,6248,6250,6253,6255,6258,6261,6264,6267,6269],{"class":3024,"line":5442},[3022,6246,6247],{"class":3138},"                if",[3022,6249,3142],{"class":3028},[3022,6251,6252],{"class":3077},"dto",[3022,6254,3103],{"class":3028},[3022,6256,6257],{"class":3077},"Price",[3022,6259,6260],{"class":3028}," % ",[3022,6262,6263],{"class":3150},"0.01m",[3022,6265,6266],{"class":3028}," != ",[3022,6268,3151],{"class":3150},[3022,6270,3081],{"class":3028},[3022,6272,6273],{"class":3024,"line":5474},[3022,6274,6275],{"class":3028},"                {\n",[3022,6277,6278,6281,6283,6285,6287,6289,6291,6293,6295],{"class":3024,"line":5479},[3022,6279,6280],{"class":3077},"                    context",[3022,6282,3103],{"class":3028},[3022,6284,3921],{"class":3077},[3022,6286,3223],{"class":3028},[3022,6288,3351],{"class":3049},[3022,6290,6149],{"class":3032},[3022,6292,3036],{"class":3028},[3022,6294,3351],{"class":3049},[3022,6296,5102],{"class":3032},[3022,6298,6299],{"class":3024,"line":5485},[3022,6300,6301],{"class":3028},"                    {\n",[3022,6303,6304,6307,6309,6311,6313,6315],{"class":3024,"line":5516},[3022,6305,6306],{"class":3077},"                        Status",[3022,6308,3223],{"class":3028},[3022,6310,5117],{"class":3077},[3022,6312,3103],{"class":3028},[3022,6314,6092],{"class":3077},[3022,6316,3527],{"class":3028},[3022,6318,6319,6322,6324,6327],{"class":3024,"line":5521},[3022,6320,6321],{"class":3077},"                        Title",[3022,6323,3223],{"class":3028},[3022,6325,6326],{"class":3039},"\"Invalid Price Format\"",[3022,6328,3527],{"class":3028},[3022,6330,6331,6334,6336,6339],{"class":3024,"line":5536},[3022,6332,6333],{"class":3077},"                        Detail",[3022,6335,3223],{"class":3028},[3022,6337,6338],{"class":3039},"\"Price must have at most 2 decimal places\"",[3022,6340,3527],{"class":3028},[3022,6342,6343,6346,6348,6350,6352,6354,6356,6358,6360],{"class":3024,"line":5541},[3022,6344,6345],{"class":3077},"                        Instance",[3022,6347,3223],{"class":3028},[3022,6349,4996],{"class":3077},[3022,6351,3103],{"class":3028},[3022,6353,5001],{"class":3077},[3022,6355,3103],{"class":3028},[3022,6357,5006],{"class":3077},[3022,6359,3103],{"class":3028},[3022,6361,5182],{"class":3077},[3022,6363,6365],{"class":3024,"line":6364},61,[3022,6366,6367],{"class":3028},"                    });\n",[3022,6369,6371,6374],{"class":3024,"line":6370},62,[3022,6372,6373],{"class":3138},"                    return",[3022,6375,4315],{"class":3028},[3022,6377,6379],{"class":3024,"line":6378},63,[3022,6380,6381],{"class":3028},"                }\n",[3022,6383,6385],{"class":3024,"line":6384},64,[3022,6386,6387],{"class":3028},"            }\n",[3022,6389,6391],{"class":3024,"line":6390},65,[3022,6392,5199],{"class":3028},[3022,6394,6396],{"class":3024,"line":6395},66,[3022,6397,4042],{"emptyLinePlaceholder":4041},[3022,6399,6401,6403,6405,6407,6409,6412,6414,6416,6418,6420,6422,6424],{"class":3024,"line":6400},67,[3022,6402,4943],{"class":3077},[3022,6404,3103],{"class":3028},[3022,6406,3106],{"class":3068},[3022,6408,3036],{"class":3028},[3022,6410,6411],{"class":3039},"\"Validation passed for {Action}\"",[3022,6413,3114],{"class":3028},[3022,6415,4996],{"class":3077},[3022,6417,3103],{"class":3028},[3022,6419,5835],{"class":3077},[3022,6421,3103],{"class":3028},[3022,6423,5840],{"class":3077},[3022,6425,3120],{"class":3028},[3022,6427,6429],{"class":3024,"line":6428},68,[3022,6430,4042],{"emptyLinePlaceholder":4041},[3022,6432,6434],{"class":3024,"line":6433},69,[3022,6435,6436],{"class":3093},"        // Валідація пройшла - виконуємо action\n",[3022,6438,6440,6442,6444],{"class":3024,"line":6439},70,[3022,6441,5524],{"class":3049},[3022,6443,4136],{"class":3068},[3022,6445,3200],{"class":3028},[3022,6447,6449],{"class":3024,"line":6448},71,[3022,6450,4036],{"class":3028},[3022,6452,6454],{"class":3024,"line":6453},72,[3022,6455,3381],{"class":3028},[2964,6457,6458],{},[2974,6459,6460],{},"Ключові моменти:",[5550,6462,6463,6470,6478,6484,6490],{},[2971,6464,6465,6469],{},[2974,6466,6467],{},[3019,6468,4197],{}," — виконується безпосередньо до/після action",[2971,6471,6472,6477],{},[2974,6473,6474],{},[3019,6475,6476],{},"ModelState.IsValid"," — перевірка DataAnnotations",[2971,6479,6480,6483],{},[2974,6481,6482],{},"Кастомна валідація"," — бізнес-правила, що не можна виразити через атрибути",[2971,6485,6486,5582],{},[2974,6487,6488],{},[3019,6489,5581],{},[2971,6491,6492,6497],{},[2974,6493,6494],{},[3019,6495,6496],{},"await next()"," — виконує action та наступні фільтри",[4741,6499,6501],{"id":6500},"_3-responsewrapperfilter-envelope-pattern","3. ResponseWrapperFilter — Envelope Pattern",[2964,6503,4286,6504,3437],{},[3019,6505,6506],{},"Filters/ResponseWrapperFilter.cs",[3012,6508,6510],{"className":3014,"code":6509,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.Filters;\n\nnamespace EcommerceFiltersApi.Filters;\n\npublic class ResponseWrapperFilter : IAsyncResultFilter\n{\n    public async Task OnResultExecutionAsync(\n        ResultExecutingContext context,\n        ResultExecutionDelegate next)\n    {\n        // Обгортаємо тільки успішні відповіді (200-299)\n        if (context.Result is ObjectResult objectResult && \n            objectResult.StatusCode >= 200 && \n            objectResult.StatusCode \u003C 300)\n        {\n            var wrappedResponse = new ApiResponse\u003Cobject>\n            {\n                Success = true,\n                Data = objectResult.Value,\n                Meta = new ResponseMeta\n                {\n                    Timestamp = DateTime.UtcNow,\n                    Version = \"1.0\",\n                    Path = context.HttpContext.Request.Path\n                }\n            };\n\n            context.Result = new ObjectResult(wrappedResponse)\n            {\n                StatusCode = objectResult.StatusCode\n            };\n        }\n\n        await next();\n    }\n}\n\n// DTO для обгортки\npublic record ApiResponse\u003CT>\n{\n    public bool Success { get; init; }\n    public T? Data { get; init; }\n    public ResponseMeta? Meta { get; init; }\n}\n\npublic record ResponseMeta\n{\n    public DateTime Timestamp { get; init; }\n    public string Version { get; init; } = \"1.0\";\n    public string? Path { get; init; }\n}\n",[3019,6511,6512,6528,6548,6552,6564,6568,6582,6586,6599,6608,6617,6621,6626,6649,6666,6682,6686,6708,6712,6723,6739,6751,6755,6772,6784,6805,6809,6813,6817,6838,6842,6856,6860,6864,6868,6876,6880,6884,6888,6893,6908,6912,6931,6954,6976,6980,6984,6992,6996,7016,7039,7059],{"__ignoreMap":3017},[3022,6513,6514,6516,6518,6520,6522,6524,6526],{"class":3024,"line":3025},[3022,6515,4299],{"class":3138},[3022,6517,4761],{"class":3032},[3022,6519,3103],{"class":3028},[3022,6521,4766],{"class":3032},[3022,6523,3103],{"class":3028},[3022,6525,4771],{"class":3032},[3022,6527,4315],{"class":3028},[3022,6529,6530,6532,6534,6536,6538,6540,6542,6544,6546],{"class":3024,"line":3046},[3022,6531,4299],{"class":3138},[3022,6533,4761],{"class":3032},[3022,6535,3103],{"class":3028},[3022,6537,4766],{"class":3032},[3022,6539,3103],{"class":3028},[3022,6541,4771],{"class":3032},[3022,6543,3103],{"class":3028},[3022,6545,4792],{"class":3032},[3022,6547,4315],{"class":3028},[3022,6549,6550],{"class":3024,"line":3084},[3022,6551,4042],{"emptyLinePlaceholder":4041},[3022,6553,6554,6556,6558,6560,6562],{"class":3024,"line":3090},[3022,6555,4324],{"class":3049},[3022,6557,4327],{"class":3032},[3022,6559,3103],{"class":3028},[3022,6561,4792],{"class":3032},[3022,6563,4315],{"class":3028},[3022,6565,6566],{"class":3024,"line":3097},[3022,6567,4042],{"emptyLinePlaceholder":4041},[3022,6569,6570,6572,6574,6577,6579],{"class":3024,"line":3123},[3022,6571,3050],{"class":3049},[3022,6573,3987],{"class":3049},[3022,6575,6576],{"class":3032}," ResponseWrapperFilter",[3022,6578,3993],{"class":3028},[3022,6580,6581],{"class":3032},"IAsyncResultFilter\n",[3022,6583,6584],{"class":3024,"line":3129},[3022,6585,3087],{"class":3028},[3022,6587,6588,6590,6592,6594,6597],{"class":3024,"line":3135},[3022,6589,4005],{"class":3049},[3022,6591,3053],{"class":3049},[3022,6593,3056],{"class":3032},[3022,6595,6596],{"class":3068}," OnResultExecutionAsync",[3022,6598,4119],{"class":3028},[3022,6600,6601,6604,6606],{"class":3024,"line":3170},[3022,6602,6603],{"class":3032},"        ResultExecutingContext",[3022,6605,4019],{"class":3077},[3022,6607,3527],{"class":3028},[3022,6609,6610,6613,6615],{"class":3024,"line":3175},[3022,6611,6612],{"class":3032},"        ResultExecutionDelegate",[3022,6614,4136],{"class":3077},[3022,6616,3081],{"class":3028},[3022,6618,6619],{"class":3024,"line":3181},[3022,6620,4026],{"class":3028},[3022,6622,6623],{"class":3024,"line":3203},[3022,6624,6625],{"class":3093},"        // Обгортаємо тільки успішні відповіді (200-299)\n",[3022,6627,6628,6630,6632,6634,6636,6638,6640,6643,6646],{"class":3024,"line":3208},[3022,6629,4991],{"class":3138},[3022,6631,3142],{"class":3028},[3022,6633,4996],{"class":3077},[3022,6635,3103],{"class":3028},[3022,6637,3921],{"class":3077},[3022,6639,6225],{"class":3049},[3022,6641,6642],{"class":3032}," ObjectResult",[3022,6644,6645],{"class":3077}," objectResult",[3022,6647,6648],{"class":3028}," && \n",[3022,6650,6651,6654,6656,6659,6662,6664],{"class":3024,"line":3214},[3022,6652,6653],{"class":3077},"            objectResult",[3022,6655,3103],{"class":3028},[3022,6657,6658],{"class":3077},"StatusCode",[3022,6660,6661],{"class":3028}," >= ",[3022,6663,4402],{"class":3150},[3022,6665,6648],{"class":3028},[3022,6667,6668,6670,6672,6674,6677,6680],{"class":3024,"line":3236},[3022,6669,6653],{"class":3077},[3022,6671,3103],{"class":3028},[3022,6673,6658],{"class":3077},[3022,6675,6676],{"class":3028}," \u003C ",[3022,6678,6679],{"class":3150},"300",[3022,6681,3081],{"class":3028},[3022,6683,6684],{"class":3024,"line":3241},[3022,6685,5040],{"class":3028},[3022,6687,6688,6690,6693,6695,6697,6700,6702,6705],{"class":3024,"line":3247},[3022,6689,5927],{"class":3049},[3022,6691,6692],{"class":3077}," wrappedResponse",[3022,6694,3223],{"class":3028},[3022,6696,3351],{"class":3049},[3022,6698,6699],{"class":3032}," ApiResponse",[3022,6701,3059],{"class":3028},[3022,6703,6704],{"class":3049},"object",[3022,6706,6707],{"class":3028},">\n",[3022,6709,6710],{"class":3024,"line":3279},[3022,6711,5107],{"class":3028},[3022,6713,6714,6717,6719,6721],{"class":3024,"line":3284},[3022,6715,6716],{"class":3077},"                Success",[3022,6718,3223],{"class":3028},[3022,6720,3362],{"class":3049},[3022,6722,3527],{"class":3028},[3022,6724,6725,6728,6730,6733,6735,6737],{"class":3024,"line":3290},[3022,6726,6727],{"class":3077},"                Data",[3022,6729,3223],{"class":3028},[3022,6731,6732],{"class":3077},"objectResult",[3022,6734,3103],{"class":3028},[3022,6736,5961],{"class":3077},[3022,6738,3527],{"class":3028},[3022,6740,6741,6744,6746,6748],{"class":3024,"line":3303},[3022,6742,6743],{"class":3077},"                Meta",[3022,6745,3223],{"class":3028},[3022,6747,3351],{"class":3049},[3022,6749,6750],{"class":3032}," ResponseMeta\n",[3022,6752,6753],{"class":3024,"line":3329},[3022,6754,6275],{"class":3028},[3022,6756,6757,6760,6762,6765,6767,6770],{"class":3024,"line":3334},[3022,6758,6759],{"class":3077},"                    Timestamp",[3022,6761,3223],{"class":3028},[3022,6763,6764],{"class":3077},"DateTime",[3022,6766,3103],{"class":3028},[3022,6768,6769],{"class":3077},"UtcNow",[3022,6771,3527],{"class":3028},[3022,6773,6774,6777,6779,6782],{"class":3024,"line":3340},[3022,6775,6776],{"class":3077},"                    Version",[3022,6778,3223],{"class":3028},[3022,6780,6781],{"class":3039},"\"1.0\"",[3022,6783,3527],{"class":3028},[3022,6785,6786,6789,6791,6793,6795,6797,6799,6801,6803],{"class":3024,"line":3378},[3022,6787,6788],{"class":3077},"                    Path",[3022,6790,3223],{"class":3028},[3022,6792,4996],{"class":3077},[3022,6794,3103],{"class":3028},[3022,6796,5001],{"class":3077},[3022,6798,3103],{"class":3028},[3022,6800,5006],{"class":3077},[3022,6802,3103],{"class":3028},[3022,6804,5182],{"class":3077},[3022,6806,6807],{"class":3024,"line":3803},[3022,6808,6381],{"class":3028},[3022,6810,6811],{"class":3024,"line":3809},[3022,6812,6130],{"class":3028},[3022,6814,6815],{"class":3024,"line":4632},[3022,6816,4042],{"emptyLinePlaceholder":4041},[3022,6818,6819,6821,6823,6825,6827,6829,6831,6833,6836],{"class":3024,"line":4658},[3022,6820,5084],{"class":3077},[3022,6822,3103],{"class":3028},[3022,6824,3921],{"class":3077},[3022,6826,3223],{"class":3028},[3022,6828,3351],{"class":3049},[3022,6830,6642],{"class":3032},[3022,6832,3036],{"class":3028},[3022,6834,6835],{"class":3077},"wrappedResponse",[3022,6837,3081],{"class":3028},[3022,6839,6840],{"class":3024,"line":4677},[3022,6841,5107],{"class":3028},[3022,6843,6844,6847,6849,6851,6853],{"class":3024,"line":4682},[3022,6845,6846],{"class":3077},"                StatusCode",[3022,6848,3223],{"class":3028},[3022,6850,6732],{"class":3077},[3022,6852,3103],{"class":3028},[3022,6854,6855],{"class":3077},"StatusCode\n",[3022,6857,6858],{"class":3024,"line":4712},[3022,6859,6130],{"class":3028},[3022,6861,6862],{"class":3024,"line":4731},[3022,6863,5199],{"class":3028},[3022,6865,6866],{"class":3024,"line":5202},[3022,6867,4042],{"emptyLinePlaceholder":4041},[3022,6869,6870,6872,6874],{"class":3024,"line":5207},[3022,6871,5524],{"class":3049},[3022,6873,4136],{"class":3068},[3022,6875,3200],{"class":3028},[3022,6877,6878],{"class":3024,"line":5213},[3022,6879,4036],{"class":3028},[3022,6881,6882],{"class":3024,"line":5265},[3022,6883,3381],{"class":3028},[3022,6885,6886],{"class":3024,"line":5270},[3022,6887,4042],{"emptyLinePlaceholder":4041},[3022,6889,6890],{"class":3024,"line":5276},[3022,6891,6892],{"class":3093},"// DTO для обгортки\n",[3022,6894,6895,6897,6899,6901,6903,6906],{"class":3024,"line":5304},[3022,6896,3050],{"class":3049},[3022,6898,4558],{"class":3049},[3022,6900,6699],{"class":3032},[3022,6902,3059],{"class":3028},[3022,6904,6905],{"class":3032},"T",[3022,6907,6707],{"class":3028},[3022,6909,6910],{"class":3024,"line":5309},[3022,6911,3087],{"class":3028},[3022,6913,6914,6916,6918,6921,6923,6925,6927,6929],{"class":3024,"line":5329},[3022,6915,4005],{"class":3049},[3022,6917,4525],{"class":3049},[3022,6919,6920],{"class":3077}," Success",[3022,6922,3354],{"class":3028},[3022,6924,4366],{"class":3049},[3022,6926,4369],{"class":3028},[3022,6928,4623],{"class":3049},[3022,6930,4375],{"class":3028},[3022,6932,6933,6935,6938,6941,6944,6946,6948,6950,6952],{"class":3024,"line":5334},[3022,6934,4005],{"class":3049},[3022,6936,6937],{"class":3032}," T",[3022,6939,6940],{"class":3028},"? ",[3022,6942,6943],{"class":3077},"Data",[3022,6945,3354],{"class":3028},[3022,6947,4366],{"class":3049},[3022,6949,4369],{"class":3028},[3022,6951,4623],{"class":3049},[3022,6953,4375],{"class":3028},[3022,6955,6956,6958,6961,6963,6966,6968,6970,6972,6974],{"class":3024,"line":5355},[3022,6957,4005],{"class":3049},[3022,6959,6960],{"class":3032}," ResponseMeta",[3022,6962,6940],{"class":3028},[3022,6964,6965],{"class":3077},"Meta",[3022,6967,3354],{"class":3028},[3022,6969,4366],{"class":3049},[3022,6971,4369],{"class":3028},[3022,6973,4623],{"class":3049},[3022,6975,4375],{"class":3028},[3022,6977,6978],{"class":3024,"line":5360},[3022,6979,3381],{"class":3028},[3022,6981,6982],{"class":3024,"line":5375},[3022,6983,4042],{"emptyLinePlaceholder":4041},[3022,6985,6986,6988,6990],{"class":3024,"line":5387},[3022,6987,3050],{"class":3049},[3022,6989,4558],{"class":3049},[3022,6991,6750],{"class":3032},[3022,6993,6994],{"class":3024,"line":5399},[3022,6995,3087],{"class":3028},[3022,6997,6998,7000,7003,7006,7008,7010,7012,7014],{"class":3024,"line":5420},[3022,6999,4005],{"class":3049},[3022,7001,7002],{"class":3032}," DateTime",[3022,7004,7005],{"class":3077}," Timestamp",[3022,7007,3354],{"class":3028},[3022,7009,4366],{"class":3049},[3022,7011,4369],{"class":3028},[3022,7013,4623],{"class":3049},[3022,7015,4375],{"class":3028},[3022,7017,7018,7020,7022,7025,7027,7029,7031,7033,7035,7037],{"class":3024,"line":5425},[3022,7019,4005],{"class":3049},[3022,7021,4414],{"class":3049},[3022,7023,7024],{"class":3077}," Version",[3022,7026,3354],{"class":3028},[3022,7028,4366],{"class":3049},[3022,7030,4369],{"class":3028},[3022,7032,4623],{"class":3049},[3022,7034,4539],{"class":3028},[3022,7036,6781],{"class":3039},[3022,7038,4315],{"class":3028},[3022,7040,7041,7043,7045,7047,7049,7051,7053,7055,7057],{"class":3024,"line":5432},[3022,7042,4005],{"class":3049},[3022,7044,4414],{"class":3049},[3022,7046,6940],{"class":3028},[3022,7048,5072],{"class":3077},[3022,7050,3354],{"class":3028},[3022,7052,4366],{"class":3049},[3022,7054,4369],{"class":3028},[3022,7056,4623],{"class":3049},[3022,7058,4375],{"class":3028},[3022,7060,7061],{"class":3024,"line":5437},[3022,7062,3381],{"class":3028},[2964,7064,7065],{},[2974,7066,7067],{},"Результат:",[3012,7069,7071],{"className":3505,"code":7070,"language":3507,"meta":3017,"style":3017},"{\n  \"success\": true,\n  \"data\": {\n    \"id\": 1,\n    \"name\": \"Laptop\",\n    \"price\": 1499.99\n  },\n  \"meta\": {\n    \"timestamp\": \"2024-01-15T10:30:00Z\",\n    \"version\": \"1.0\",\n    \"path\": \"/api/products/1\"\n  }\n}\n",[3019,7072,7073,7077,7087,7093,7104,7115,7125,7130,7136,7146,7156,7166,7170],{"__ignoreMap":3017},[3022,7074,7075],{"class":3024,"line":3025},[3022,7076,3087],{"class":3028},[3022,7078,7079,7081,7083,7085],{"class":3024,"line":3046},[3022,7080,3519],{"class":3518},[3022,7082,3522],{"class":3028},[3022,7084,3362],{"class":3049},[3022,7086,3527],{"class":3028},[3022,7088,7089,7091],{"class":3024,"line":3084},[3022,7090,3532],{"class":3518},[3022,7092,3564],{"class":3028},[3022,7094,7095,7098,7100,7102],{"class":3024,"line":3090},[3022,7096,7097],{"class":3518},"    \"id\"",[3022,7099,3522],{"class":3028},[3022,7101,3543],{"class":3150},[3022,7103,3527],{"class":3028},[3022,7105,7106,7109,7111,7113],{"class":3024,"line":3097},[3022,7107,7108],{"class":3518},"    \"name\"",[3022,7110,3522],{"class":3028},[3022,7112,3553],{"class":3039},[3022,7114,3527],{"class":3028},[3022,7116,7117,7120,7122],{"class":3024,"line":3123},[3022,7118,7119],{"class":3518},"    \"price\"",[3022,7121,3522],{"class":3028},[3022,7123,7124],{"class":3150},"1499.99\n",[3022,7126,7127],{"class":3024,"line":3129},[3022,7128,7129],{"class":3028},"  },\n",[3022,7131,7132,7134],{"class":3024,"line":3135},[3022,7133,3561],{"class":3518},[3022,7135,3564],{"class":3028},[3022,7137,7138,7140,7142,7144],{"class":3024,"line":3170},[3022,7139,3569],{"class":3518},[3022,7141,3522],{"class":3028},[3022,7143,3574],{"class":3039},[3022,7145,3527],{"class":3028},[3022,7147,7148,7150,7152,7154],{"class":3024,"line":3175},[3022,7149,3581],{"class":3518},[3022,7151,3522],{"class":3028},[3022,7153,6781],{"class":3039},[3022,7155,3527],{"class":3028},[3022,7157,7158,7161,7163],{"class":3024,"line":3181},[3022,7159,7160],{"class":3518},"    \"path\"",[3022,7162,3522],{"class":3028},[3022,7164,7165],{"class":3039},"\"/api/products/1\"\n",[3022,7167,7168],{"class":3024,"line":3203},[3022,7169,3591],{"class":3028},[3022,7171,7172],{"class":3024,"line":3208},[3022,7173,3381],{"class":3028},[4741,7175,7177],{"id":7176},"_4-correlationidfilter-traceability","4. CorrelationIdFilter — Traceability",[2964,7179,4286,7180,3437],{},[3019,7181,7182],{},"Filters/CorrelationIdFilter.cs",[3012,7184,7186],{"className":3014,"code":7185,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc.Filters;\nusing System.Diagnostics;\n\nnamespace EcommerceFiltersApi.Filters;\n\npublic class CorrelationIdFilter : IAsyncActionFilter\n{\n    private const string CorrelationIdHeader = \"X-Correlation-ID\";\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // Отримуємо або генеруємо Correlation ID\n        var correlationId = context.HttpContext.Request.Headers[CorrelationIdHeader]\n            .FirstOrDefault() ?? Guid.NewGuid().ToString();\n\n        // Додаємо до response headers\n        context.HttpContext.Response.Headers.Append(CorrelationIdHeader, correlationId);\n\n        // Додаємо до Activity для distributed tracing\n        Activity.Current?.SetTag(\"correlation.id\", correlationId);\n\n        // Додаємо до HttpContext для доступу в контролерах\n        context.HttpContext.Items[\"CorrelationId\"] = correlationId;\n\n        await next();\n    }\n}\n",[3019,7187,7188,7208,7221,7225,7237,7241,7254,7258,7276,7280,7292,7300,7308,7312,7317,7347,7373,7377,7382,7415,7419,7424,7450,7454,7459,7482,7486,7494,7498],{"__ignoreMap":3017},[3022,7189,7190,7192,7194,7196,7198,7200,7202,7204,7206],{"class":3024,"line":3025},[3022,7191,4299],{"class":3138},[3022,7193,4761],{"class":3032},[3022,7195,3103],{"class":3028},[3022,7197,4766],{"class":3032},[3022,7199,3103],{"class":3028},[3022,7201,4771],{"class":3032},[3022,7203,3103],{"class":3028},[3022,7205,4792],{"class":3032},[3022,7207,4315],{"class":3028},[3022,7209,7210,7212,7214,7216,7219],{"class":3024,"line":3046},[3022,7211,4299],{"class":3138},[3022,7213,4302],{"class":3032},[3022,7215,3103],{"class":3028},[3022,7217,7218],{"class":3032},"Diagnostics",[3022,7220,4315],{"class":3028},[3022,7222,7223],{"class":3024,"line":3084},[3022,7224,4042],{"emptyLinePlaceholder":4041},[3022,7226,7227,7229,7231,7233,7235],{"class":3024,"line":3090},[3022,7228,4324],{"class":3049},[3022,7230,4327],{"class":3032},[3022,7232,3103],{"class":3028},[3022,7234,4792],{"class":3032},[3022,7236,4315],{"class":3028},[3022,7238,7239],{"class":3024,"line":3097},[3022,7240,4042],{"emptyLinePlaceholder":4041},[3022,7242,7243,7245,7247,7250,7252],{"class":3024,"line":3123},[3022,7244,3050],{"class":3049},[3022,7246,3987],{"class":3049},[3022,7248,7249],{"class":3032}," CorrelationIdFilter",[3022,7251,3993],{"class":3028},[3022,7253,4101],{"class":3032},[3022,7255,7256],{"class":3024,"line":3129},[3022,7257,3087],{"class":3028},[3022,7259,7260,7262,7264,7266,7269,7271,7274],{"class":3024,"line":3135},[3022,7261,4837],{"class":3049},[3022,7263,4840],{"class":3049},[3022,7265,4414],{"class":3049},[3022,7267,7268],{"class":3077}," CorrelationIdHeader",[3022,7270,3223],{"class":3028},[3022,7272,7273],{"class":3039},"\"X-Correlation-ID\"",[3022,7275,4315],{"class":3028},[3022,7277,7278],{"class":3024,"line":3170},[3022,7279,4042],{"emptyLinePlaceholder":4041},[3022,7281,7282,7284,7286,7288,7290],{"class":3024,"line":3175},[3022,7283,4005],{"class":3049},[3022,7285,3053],{"class":3049},[3022,7287,3056],{"class":3032},[3022,7289,4116],{"class":3068},[3022,7291,4119],{"class":3028},[3022,7293,7294,7296,7298],{"class":3024,"line":3181},[3022,7295,4124],{"class":3032},[3022,7297,4019],{"class":3077},[3022,7299,3527],{"class":3028},[3022,7301,7302,7304,7306],{"class":3024,"line":3203},[3022,7303,4133],{"class":3032},[3022,7305,4136],{"class":3077},[3022,7307,3081],{"class":3028},[3022,7309,7310],{"class":3024,"line":3208},[3022,7311,4026],{"class":3028},[3022,7313,7314],{"class":3024,"line":3214},[3022,7315,7316],{"class":3093},"        // Отримуємо або генеруємо Correlation ID\n",[3022,7318,7319,7321,7324,7326,7328,7330,7332,7334,7336,7338,7340,7342,7345],{"class":3024,"line":3236},[3022,7320,4156],{"class":3049},[3022,7322,7323],{"class":3077}," correlationId",[3022,7325,3223],{"class":3028},[3022,7327,4996],{"class":3077},[3022,7329,3103],{"class":3028},[3022,7331,5001],{"class":3077},[3022,7333,3103],{"class":3028},[3022,7335,5006],{"class":3077},[3022,7337,3103],{"class":3028},[3022,7339,5011],{"class":3077},[3022,7341,3029],{"class":3028},[3022,7343,7344],{"class":3077},"CorrelationIdHeader",[3022,7346,4390],{"class":3028},[3022,7348,7349,7352,7355,7358,7361,7363,7366,7369,7371],{"class":3024,"line":3241},[3022,7350,7351],{"class":3028},"            .",[3022,7353,7354],{"class":3068},"FirstOrDefault",[3022,7356,7357],{"class":3028},"() ?? ",[3022,7359,7360],{"class":3077},"Guid",[3022,7362,3103],{"class":3028},[3022,7364,7365],{"class":3068},"NewGuid",[3022,7367,7368],{"class":3028},"().",[3022,7370,5298],{"class":3068},[3022,7372,3200],{"class":3028},[3022,7374,7375],{"class":3024,"line":3247},[3022,7376,4042],{"emptyLinePlaceholder":4041},[3022,7378,7379],{"class":3024,"line":3279},[3022,7380,7381],{"class":3093},"        // Додаємо до response headers\n",[3022,7383,7384,7386,7388,7390,7392,7395,7397,7399,7401,7404,7406,7408,7410,7413],{"class":3024,"line":3284},[3022,7385,5488],{"class":3077},[3022,7387,3103],{"class":3028},[3022,7389,5001],{"class":3077},[3022,7391,3103],{"class":3028},[3022,7393,7394],{"class":3077},"Response",[3022,7396,3103],{"class":3028},[3022,7398,5011],{"class":3077},[3022,7400,3103],{"class":3028},[3022,7402,7403],{"class":3068},"Append",[3022,7405,3036],{"class":3028},[3022,7407,7344],{"class":3077},[3022,7409,3114],{"class":3028},[3022,7411,7412],{"class":3077},"correlationId",[3022,7414,3120],{"class":3028},[3022,7416,7417],{"class":3024,"line":3290},[3022,7418,4042],{"emptyLinePlaceholder":4041},[3022,7420,7421],{"class":3024,"line":3303},[3022,7422,7423],{"class":3093},"        // Додаємо до Activity для distributed tracing\n",[3022,7425,7426,7429,7431,7434,7436,7439,7441,7444,7446,7448],{"class":3024,"line":3329},[3022,7427,7428],{"class":3077},"        Activity",[3022,7430,3103],{"class":3028},[3022,7432,7433],{"class":3077},"Current",[3022,7435,5964],{"class":3028},[3022,7437,7438],{"class":3068},"SetTag",[3022,7440,3036],{"class":3028},[3022,7442,7443],{"class":3039},"\"correlation.id\"",[3022,7445,3114],{"class":3028},[3022,7447,7412],{"class":3077},[3022,7449,3120],{"class":3028},[3022,7451,7452],{"class":3024,"line":3334},[3022,7453,4042],{"emptyLinePlaceholder":4041},[3022,7455,7456],{"class":3024,"line":3340},[3022,7457,7458],{"class":3093},"        // Додаємо до HttpContext для доступу в контролерах\n",[3022,7460,7461,7463,7465,7467,7469,7471,7473,7476,7478,7480],{"class":3024,"line":3378},[3022,7462,5488],{"class":3077},[3022,7464,3103],{"class":3028},[3022,7466,5001],{"class":3077},[3022,7468,3103],{"class":3028},[3022,7470,5497],{"class":3077},[3022,7472,3029],{"class":3028},[3022,7474,7475],{"class":3039},"\"CorrelationId\"",[3022,7477,5505],{"class":3028},[3022,7479,7412],{"class":3077},[3022,7481,4315],{"class":3028},[3022,7483,7484],{"class":3024,"line":3803},[3022,7485,4042],{"emptyLinePlaceholder":4041},[3022,7487,7488,7490,7492],{"class":3024,"line":3809},[3022,7489,5524],{"class":3049},[3022,7491,4136],{"class":3068},[3022,7493,3200],{"class":3028},[3022,7495,7496],{"class":3024,"line":4632},[3022,7497,4036],{"class":3028},[3022,7499,7500],{"class":3024,"line":4658},[3022,7501,3381],{"class":3028},[4741,7503,7505],{"id":7504},"_5-performancemonitoringfilter-метрики","5. PerformanceMonitoringFilter — Метрики",[2964,7507,4286,7508,3437],{},[3019,7509,7510],{},"Filters/PerformanceMonitoringFilter.cs",[3012,7512,7514],{"className":3014,"code":7513,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc.Filters;\nusing System.Diagnostics;\n\nnamespace EcommerceFiltersApi.Filters;\n\npublic class PerformanceMonitoringFilter : IAsyncActionFilter\n{\n    private readonly ILogger\u003CPerformanceMonitoringFilter> _logger;\n    private readonly int _warningThresholdMs;\n\n    public PerformanceMonitoringFilter(\n        ILogger\u003CPerformanceMonitoringFilter> logger,\n        IConfiguration configuration)\n    {\n        _logger = logger;\n        _warningThresholdMs = configuration.GetValue\u003Cint>(\"Performance:WarningThresholdMs\", 1000);\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var stopwatch = Stopwatch.StartNew();\n        var actionName = context.ActionDescriptor.DisplayName;\n        var method = context.HttpContext.Request.Method;\n        var path = context.HttpContext.Request.Path;\n\n        _logger.LogInformation(\n            \"Starting {Method} {Path} ({Action})\",\n            method,\n            path,\n            actionName);\n\n        // Виконуємо action\n        var resultContext = await next();\n\n        stopwatch.Stop();\n        var elapsedMs = stopwatch.ElapsedMilliseconds;\n\n        // Додаємо час виконання до response headers\n        context.HttpContext.Response.Headers.Append(\"X-Response-Time-Ms\", elapsedMs.ToString());\n\n        // Логуємо з різними рівнями залежно від часу виконання\n        if (elapsedMs > _warningThresholdMs)\n        {\n            _logger.LogWarning(\n                \"SLOW REQUEST: {Method} {Path} completed in {ElapsedMs}ms (threshold: {Threshold}ms)\",\n                method,\n                path,\n                elapsedMs,\n                _warningThresholdMs);\n        }\n        else\n        {\n            _logger.LogInformation(\n                \"{Method} {Path} completed in {ElapsedMs}ms\",\n                method,\n                path,\n                elapsedMs);\n        }\n\n        // Додаємо метрики до HttpContext для можливого використання в інших фільтрах\n        context.HttpContext.Items[\"PerformanceMetrics\"] = new\n        {\n            ElapsedMilliseconds = elapsedMs,\n            ActionName = actionName,\n            IsSlowRequest = elapsedMs > _warningThresholdMs\n        };\n    }\n}\n",[3019,7515,7516,7536,7548,7552,7564,7568,7581,7585,7604,7617,7621,7629,7644,7653,7657,7667,7698,7702,7706,7718,7726,7734,7738,7754,7775,7801,7826,7830,7840,7847,7854,7861,7868,7872,7877,7891,7895,7906,7923,7927,7932,7969,7973,7978,7993,7997,8007,8014,8021,8028,8035,8042,8046,8051,8055,8065,8072,8078,8084,8090,8094,8098,8103,8125,8129,8140,8152,8166,8171,8175],{"__ignoreMap":3017},[3022,7517,7518,7520,7522,7524,7526,7528,7530,7532,7534],{"class":3024,"line":3025},[3022,7519,4299],{"class":3138},[3022,7521,4761],{"class":3032},[3022,7523,3103],{"class":3028},[3022,7525,4766],{"class":3032},[3022,7527,3103],{"class":3028},[3022,7529,4771],{"class":3032},[3022,7531,3103],{"class":3028},[3022,7533,4792],{"class":3032},[3022,7535,4315],{"class":3028},[3022,7537,7538,7540,7542,7544,7546],{"class":3024,"line":3046},[3022,7539,4299],{"class":3138},[3022,7541,4302],{"class":3032},[3022,7543,3103],{"class":3028},[3022,7545,7218],{"class":3032},[3022,7547,4315],{"class":3028},[3022,7549,7550],{"class":3024,"line":3084},[3022,7551,4042],{"emptyLinePlaceholder":4041},[3022,7553,7554,7556,7558,7560,7562],{"class":3024,"line":3090},[3022,7555,4324],{"class":3049},[3022,7557,4327],{"class":3032},[3022,7559,3103],{"class":3028},[3022,7561,4792],{"class":3032},[3022,7563,4315],{"class":3028},[3022,7565,7566],{"class":3024,"line":3097},[3022,7567,4042],{"emptyLinePlaceholder":4041},[3022,7569,7570,7572,7574,7577,7579],{"class":3024,"line":3123},[3022,7571,3050],{"class":3049},[3022,7573,3987],{"class":3049},[3022,7575,7576],{"class":3032}," PerformanceMonitoringFilter",[3022,7578,3993],{"class":3028},[3022,7580,4101],{"class":3032},[3022,7582,7583],{"class":3024,"line":3129},[3022,7584,3087],{"class":3028},[3022,7586,7587,7589,7591,7593,7595,7598,7600,7602],{"class":3024,"line":3135},[3022,7588,4837],{"class":3049},[3022,7590,4859],{"class":3049},[3022,7592,4876],{"class":3032},[3022,7594,3059],{"class":3028},[3022,7596,7597],{"class":3032},"PerformanceMonitoringFilter",[3022,7599,3065],{"class":3028},[3022,7601,4886],{"class":3077},[3022,7603,4315],{"class":3028},[3022,7605,7606,7608,7610,7612,7615],{"class":3024,"line":3170},[3022,7607,4837],{"class":3049},[3022,7609,4859],{"class":3049},[3022,7611,4358],{"class":3049},[3022,7613,7614],{"class":3077}," _warningThresholdMs",[3022,7616,4315],{"class":3028},[3022,7618,7619],{"class":3024,"line":3175},[3022,7620,4042],{"emptyLinePlaceholder":4041},[3022,7622,7623,7625,7627],{"class":3024,"line":3181},[3022,7624,4005],{"class":3049},[3022,7626,7576],{"class":3068},[3022,7628,4119],{"class":3028},[3022,7630,7631,7634,7636,7638,7640,7642],{"class":3024,"line":3203},[3022,7632,7633],{"class":3032},"        ILogger",[3022,7635,3059],{"class":3028},[3022,7637,7597],{"class":3032},[3022,7639,3065],{"class":3028},[3022,7641,4920],{"class":3077},[3022,7643,3527],{"class":3028},[3022,7645,7646,7649,7651],{"class":3024,"line":3208},[3022,7647,7648],{"class":3032},"        IConfiguration",[3022,7650,4906],{"class":3077},[3022,7652,3081],{"class":3028},[3022,7654,7655],{"class":3024,"line":3214},[3022,7656,4026],{"class":3028},[3022,7658,7659,7661,7663,7665],{"class":3024,"line":3236},[3022,7660,4943],{"class":3077},[3022,7662,3223],{"class":3028},[3022,7664,4920],{"class":3077},[3022,7666,4315],{"class":3028},[3022,7668,7669,7672,7674,7676,7678,7681,7683,7685,7688,7691,7693,7696],{"class":3024,"line":3241},[3022,7670,7671],{"class":3077},"        _warningThresholdMs",[3022,7673,3223],{"class":3028},[3022,7675,4936],{"class":3077},[3022,7677,3103],{"class":3028},[3022,7679,7680],{"class":3068},"GetValue",[3022,7682,3059],{"class":3028},[3022,7684,3074],{"class":3049},[3022,7686,7687],{"class":3028},">(",[3022,7689,7690],{"class":3039},"\"Performance:WarningThresholdMs\"",[3022,7692,3114],{"class":3028},[3022,7694,7695],{"class":3150},"1000",[3022,7697,3120],{"class":3028},[3022,7699,7700],{"class":3024,"line":3247},[3022,7701,4036],{"class":3028},[3022,7703,7704],{"class":3024,"line":3279},[3022,7705,4042],{"emptyLinePlaceholder":4041},[3022,7707,7708,7710,7712,7714,7716],{"class":3024,"line":3284},[3022,7709,4005],{"class":3049},[3022,7711,3053],{"class":3049},[3022,7713,3056],{"class":3032},[3022,7715,4116],{"class":3068},[3022,7717,4119],{"class":3028},[3022,7719,7720,7722,7724],{"class":3024,"line":3290},[3022,7721,4124],{"class":3032},[3022,7723,4019],{"class":3077},[3022,7725,3527],{"class":3028},[3022,7727,7728,7730,7732],{"class":3024,"line":3303},[3022,7729,4133],{"class":3032},[3022,7731,4136],{"class":3077},[3022,7733,3081],{"class":3028},[3022,7735,7736],{"class":3024,"line":3329},[3022,7737,4026],{"class":3028},[3022,7739,7740,7742,7744,7746,7748,7750,7752],{"class":3024,"line":3334},[3022,7741,4156],{"class":3049},[3022,7743,3220],{"class":3077},[3022,7745,3223],{"class":3028},[3022,7747,3226],{"class":3077},[3022,7749,3103],{"class":3028},[3022,7751,3231],{"class":3068},[3022,7753,3200],{"class":3028},[3022,7755,7756,7758,7761,7763,7765,7767,7769,7771,7773],{"class":3024,"line":3340},[3022,7757,4156],{"class":3049},[3022,7759,7760],{"class":3077}," actionName",[3022,7762,3223],{"class":3028},[3022,7764,4996],{"class":3077},[3022,7766,3103],{"class":3028},[3022,7768,5835],{"class":3077},[3022,7770,3103],{"class":3028},[3022,7772,5840],{"class":3077},[3022,7774,4315],{"class":3028},[3022,7776,7777,7779,7782,7784,7786,7788,7790,7792,7794,7796,7799],{"class":3024,"line":3378},[3022,7778,4156],{"class":3049},[3022,7780,7781],{"class":3077}," method",[3022,7783,3223],{"class":3028},[3022,7785,4996],{"class":3077},[3022,7787,3103],{"class":3028},[3022,7789,5001],{"class":3077},[3022,7791,3103],{"class":3028},[3022,7793,5006],{"class":3077},[3022,7795,3103],{"class":3028},[3022,7797,7798],{"class":3077},"Method",[3022,7800,4315],{"class":3028},[3022,7802,7803,7805,7808,7810,7812,7814,7816,7818,7820,7822,7824],{"class":3024,"line":3803},[3022,7804,4156],{"class":3049},[3022,7806,7807],{"class":3077}," path",[3022,7809,3223],{"class":3028},[3022,7811,4996],{"class":3077},[3022,7813,3103],{"class":3028},[3022,7815,5001],{"class":3077},[3022,7817,3103],{"class":3028},[3022,7819,5006],{"class":3077},[3022,7821,3103],{"class":3028},[3022,7823,5072],{"class":3077},[3022,7825,4315],{"class":3028},[3022,7827,7828],{"class":3024,"line":3809},[3022,7829,4042],{"emptyLinePlaceholder":4041},[3022,7831,7832,7834,7836,7838],{"class":3024,"line":4632},[3022,7833,4943],{"class":3077},[3022,7835,3103],{"class":3028},[3022,7837,3106],{"class":3068},[3022,7839,4119],{"class":3028},[3022,7841,7842,7845],{"class":3024,"line":4658},[3022,7843,7844],{"class":3039},"            \"Starting {Method} {Path} ({Action})\"",[3022,7846,3527],{"class":3028},[3022,7848,7849,7852],{"class":3024,"line":4677},[3022,7850,7851],{"class":3077},"            method",[3022,7853,3527],{"class":3028},[3022,7855,7856,7859],{"class":3024,"line":4682},[3022,7857,7858],{"class":3077},"            path",[3022,7860,3527],{"class":3028},[3022,7862,7863,7866],{"class":3024,"line":4712},[3022,7864,7865],{"class":3077},"            actionName",[3022,7867,3120],{"class":3028},[3022,7869,7870],{"class":3024,"line":4731},[3022,7871,4042],{"emptyLinePlaceholder":4041},[3022,7873,7874],{"class":3024,"line":5202},[3022,7875,7876],{"class":3093},"        // Виконуємо action\n",[3022,7878,7879,7881,7883,7885,7887,7889],{"class":3024,"line":5207},[3022,7880,4156],{"class":3049},[3022,7882,4159],{"class":3077},[3022,7884,3223],{"class":3028},[3022,7886,3257],{"class":3049},[3022,7888,4136],{"class":3068},[3022,7890,3200],{"class":3028},[3022,7892,7893],{"class":3024,"line":5213},[3022,7894,4042],{"emptyLinePlaceholder":4041},[3022,7896,7897,7900,7902,7904],{"class":3024,"line":5265},[3022,7898,7899],{"class":3077},"        stopwatch",[3022,7901,3103],{"class":3028},[3022,7903,3298],{"class":3068},[3022,7905,3200],{"class":3028},[3022,7907,7908,7910,7913,7915,7917,7919,7921],{"class":3024,"line":5270},[3022,7909,4156],{"class":3049},[3022,7911,7912],{"class":3077}," elapsedMs",[3022,7914,3223],{"class":3028},[3022,7916,3319],{"class":3077},[3022,7918,3103],{"class":3028},[3022,7920,3324],{"class":3077},[3022,7922,4315],{"class":3028},[3022,7924,7925],{"class":3024,"line":5276},[3022,7926,4042],{"emptyLinePlaceholder":4041},[3022,7928,7929],{"class":3024,"line":5304},[3022,7930,7931],{"class":3093},"        // Додаємо час виконання до response headers\n",[3022,7933,7934,7936,7938,7940,7942,7944,7946,7948,7950,7952,7954,7957,7959,7962,7964,7966],{"class":3024,"line":5309},[3022,7935,5488],{"class":3077},[3022,7937,3103],{"class":3028},[3022,7939,5001],{"class":3077},[3022,7941,3103],{"class":3028},[3022,7943,7394],{"class":3077},[3022,7945,3103],{"class":3028},[3022,7947,5011],{"class":3077},[3022,7949,3103],{"class":3028},[3022,7951,7403],{"class":3068},[3022,7953,3036],{"class":3028},[3022,7955,7956],{"class":3039},"\"X-Response-Time-Ms\"",[3022,7958,3114],{"class":3028},[3022,7960,7961],{"class":3077},"elapsedMs",[3022,7963,3103],{"class":3028},[3022,7965,5298],{"class":3068},[3022,7967,7968],{"class":3028},"());\n",[3022,7970,7971],{"class":3024,"line":5329},[3022,7972,4042],{"emptyLinePlaceholder":4041},[3022,7974,7975],{"class":3024,"line":5334},[3022,7976,7977],{"class":3093},"        // Логуємо з різними рівнями залежно від часу виконання\n",[3022,7979,7980,7982,7984,7986,7988,7991],{"class":3024,"line":5355},[3022,7981,4991],{"class":3138},[3022,7983,3142],{"class":3028},[3022,7985,7961],{"class":3077},[3022,7987,5974],{"class":3028},[3022,7989,7990],{"class":3077},"_warningThresholdMs",[3022,7992,3081],{"class":3028},[3022,7994,7995],{"class":3024,"line":5360},[3022,7996,5040],{"class":3028},[3022,7998,7999,8001,8003,8005],{"class":3024,"line":5375},[3022,8000,5045],{"class":3077},[3022,8002,3103],{"class":3028},[3022,8004,5050],{"class":3068},[3022,8006,4119],{"class":3028},[3022,8008,8009,8012],{"class":3024,"line":5387},[3022,8010,8011],{"class":3039},"                \"SLOW REQUEST: {Method} {Path} completed in {ElapsedMs}ms (threshold: {Threshold}ms)\"",[3022,8013,3527],{"class":3028},[3022,8015,8016,8019],{"class":3024,"line":5399},[3022,8017,8018],{"class":3077},"                method",[3022,8020,3527],{"class":3028},[3022,8022,8023,8026],{"class":3024,"line":5420},[3022,8024,8025],{"class":3077},"                path",[3022,8027,3527],{"class":3028},[3022,8029,8030,8033],{"class":3024,"line":5425},[3022,8031,8032],{"class":3077},"                elapsedMs",[3022,8034,3527],{"class":3028},[3022,8036,8037,8040],{"class":3024,"line":5432},[3022,8038,8039],{"class":3077},"                _warningThresholdMs",[3022,8041,3120],{"class":3028},[3022,8043,8044],{"class":3024,"line":5437},[3022,8045,5199],{"class":3028},[3022,8047,8048],{"class":3024,"line":5442},[3022,8049,8050],{"class":3138},"        else\n",[3022,8052,8053],{"class":3024,"line":5474},[3022,8054,5040],{"class":3028},[3022,8056,8057,8059,8061,8063],{"class":3024,"line":5479},[3022,8058,5045],{"class":3077},[3022,8060,3103],{"class":3028},[3022,8062,3106],{"class":3068},[3022,8064,4119],{"class":3028},[3022,8066,8067,8070],{"class":3024,"line":5485},[3022,8068,8069],{"class":3039},"                \"{Method} {Path} completed in {ElapsedMs}ms\"",[3022,8071,3527],{"class":3028},[3022,8073,8074,8076],{"class":3024,"line":5516},[3022,8075,8018],{"class":3077},[3022,8077,3527],{"class":3028},[3022,8079,8080,8082],{"class":3024,"line":5521},[3022,8081,8025],{"class":3077},[3022,8083,3527],{"class":3028},[3022,8085,8086,8088],{"class":3024,"line":5536},[3022,8087,8032],{"class":3077},[3022,8089,3120],{"class":3028},[3022,8091,8092],{"class":3024,"line":5541},[3022,8093,5199],{"class":3028},[3022,8095,8096],{"class":3024,"line":6364},[3022,8097,4042],{"emptyLinePlaceholder":4041},[3022,8099,8100],{"class":3024,"line":6370},[3022,8101,8102],{"class":3093},"        // Додаємо метрики до HttpContext для можливого використання в інших фільтрах\n",[3022,8104,8105,8107,8109,8111,8113,8115,8117,8120,8122],{"class":3024,"line":6378},[3022,8106,5488],{"class":3077},[3022,8108,3103],{"class":3028},[3022,8110,5001],{"class":3077},[3022,8112,3103],{"class":3028},[3022,8114,5497],{"class":3077},[3022,8116,3029],{"class":3028},[3022,8118,8119],{"class":3039},"\"PerformanceMetrics\"",[3022,8121,5505],{"class":3028},[3022,8123,8124],{"class":3049},"new\n",[3022,8126,8127],{"class":3024,"line":6384},[3022,8128,5040],{"class":3028},[3022,8130,8131,8134,8136,8138],{"class":3024,"line":6390},[3022,8132,8133],{"class":3077},"            ElapsedMilliseconds",[3022,8135,3223],{"class":3028},[3022,8137,7961],{"class":3077},[3022,8139,3527],{"class":3028},[3022,8141,8142,8145,8147,8150],{"class":3024,"line":6395},[3022,8143,8144],{"class":3077},"            ActionName",[3022,8146,3223],{"class":3028},[3022,8148,8149],{"class":3077},"actionName",[3022,8151,3527],{"class":3028},[3022,8153,8154,8157,8159,8161,8163],{"class":3024,"line":6400},[3022,8155,8156],{"class":3077},"            IsSlowRequest",[3022,8158,3223],{"class":3028},[3022,8160,7961],{"class":3077},[3022,8162,5974],{"class":3028},[3022,8164,8165],{"class":3077},"_warningThresholdMs\n",[3022,8167,8168],{"class":3024,"line":6428},[3022,8169,8170],{"class":3028},"        };\n",[3022,8172,8173],{"class":3024,"line":6433},[3022,8174,4036],{"class":3028},[3022,8176,8177],{"class":3024,"line":6439},[3022,8178,3381],{"class":3028},[2964,8180,8181],{},[2974,8182,8183],{},"Результат у логах:",[3012,8185,8188],{"className":8186,"code":8187,"language":3628},[3626],"[INFO] Starting GET /api/products/1 (ProductsController.GetById)\n[INFO] GET /api/products/1 completed in 45ms\n[WARN] SLOW REQUEST: GET /api/products/search completed in 1250ms (threshold: 1000ms)\n",[3019,8189,8187],{"__ignoreMap":3017},[3652,8191],{},[3423,8193,8195],{"id":8194},"крок-3-реєстрація-фільтрів","Крок 3: Реєстрація фільтрів",[2964,8197,8198,8199,8202],{},"Є ",[2974,8200,8201],{},"три способи"," реєстрації фільтрів:",[4741,8204,8206],{"id":8205},"спосіб-1-глобальна-реєстрація-для-всіх-endpoints","Спосіб 1: Глобальна реєстрація (для всіх endpoints)",[3012,8208,8210],{"className":3014,"code":8209,"language":3016,"meta":3017,"style":3017},"// Program.cs\nbuilder.Services.AddControllers(options =>\n{\n    // Фільтри виконуються у порядку додавання\n    options.Filters.Add\u003CCorrelationIdFilter>();\n    options.Filters.Add\u003CPerformanceMonitoringFilter>();\n    options.Filters.Add\u003CRequestValidationFilter>();\n    options.Filters.Add\u003CResponseWrapperFilter>();\n});\n\n// Реєструємо фільтри у DI для можливості ін'єкції залежностей\nbuilder.Services.AddScoped\u003CApiKeyAuthFilter>();\nbuilder.Services.AddScoped\u003CRequestValidationFilter>();\nbuilder.Services.AddScoped\u003CResponseWrapperFilter>();\nbuilder.Services.AddScoped\u003CCorrelationIdFilter>();\nbuilder.Services.AddScoped\u003CPerformanceMonitoringFilter>();\n",[3019,8211,8212,8217,8240,8244,8249,8270,8288,8306,8325,8330,8334,8339,8358,8376,8394,8412],{"__ignoreMap":3017},[3022,8213,8214],{"class":3024,"line":3025},[3022,8215,8216],{"class":3093},"// Program.cs\n",[3022,8218,8219,8222,8224,8227,8229,8232,8234,8237],{"class":3024,"line":3046},[3022,8220,8221],{"class":3077},"builder",[3022,8223,3103],{"class":3028},[3022,8225,8226],{"class":3077},"Services",[3022,8228,3103],{"class":3028},[3022,8230,8231],{"class":3068},"AddControllers",[3022,8233,3036],{"class":3028},[3022,8235,8236],{"class":3077},"options",[3022,8238,8239],{"class":3028}," =>\n",[3022,8241,8242],{"class":3024,"line":3084},[3022,8243,3087],{"class":3028},[3022,8245,8246],{"class":3024,"line":3090},[3022,8247,8248],{"class":3093},"    // Фільтри виконуються у порядку додавання\n",[3022,8250,8251,8254,8256,8258,8260,8263,8265,8268],{"class":3024,"line":3097},[3022,8252,8253],{"class":3077},"    options",[3022,8255,3103],{"class":3028},[3022,8257,4792],{"class":3077},[3022,8259,3103],{"class":3028},[3022,8261,8262],{"class":3068},"Add",[3022,8264,3059],{"class":3028},[3022,8266,8267],{"class":3032},"CorrelationIdFilter",[3022,8269,5262],{"class":3028},[3022,8271,8272,8274,8276,8278,8280,8282,8284,8286],{"class":3024,"line":3123},[3022,8273,8253],{"class":3077},[3022,8275,3103],{"class":3028},[3022,8277,4792],{"class":3077},[3022,8279,3103],{"class":3028},[3022,8281,8262],{"class":3068},[3022,8283,3059],{"class":3028},[3022,8285,7597],{"class":3032},[3022,8287,5262],{"class":3028},[3022,8289,8290,8292,8294,8296,8298,8300,8302,8304],{"class":3024,"line":3129},[3022,8291,8253],{"class":3077},[3022,8293,3103],{"class":3028},[3022,8295,4792],{"class":3077},[3022,8297,3103],{"class":3028},[3022,8299,8262],{"class":3068},[3022,8301,3059],{"class":3028},[3022,8303,5695],{"class":3032},[3022,8305,5262],{"class":3028},[3022,8307,8308,8310,8312,8314,8316,8318,8320,8323],{"class":3024,"line":3135},[3022,8309,8253],{"class":3077},[3022,8311,3103],{"class":3028},[3022,8313,4792],{"class":3077},[3022,8315,3103],{"class":3028},[3022,8317,8262],{"class":3068},[3022,8319,3059],{"class":3028},[3022,8321,8322],{"class":3032},"ResponseWrapperFilter",[3022,8324,5262],{"class":3028},[3022,8326,8327],{"class":3024,"line":3170},[3022,8328,8329],{"class":3028},"});\n",[3022,8331,8332],{"class":3024,"line":3175},[3022,8333,4042],{"emptyLinePlaceholder":4041},[3022,8335,8336],{"class":3024,"line":3181},[3022,8337,8338],{"class":3093},"// Реєструємо фільтри у DI для можливості ін'єкції залежностей\n",[3022,8340,8341,8343,8345,8347,8349,8352,8354,8356],{"class":3024,"line":3203},[3022,8342,8221],{"class":3077},[3022,8344,3103],{"class":3028},[3022,8346,8226],{"class":3077},[3022,8348,3103],{"class":3028},[3022,8350,8351],{"class":3068},"AddScoped",[3022,8353,3059],{"class":3028},[3022,8355,4881],{"class":3032},[3022,8357,5262],{"class":3028},[3022,8359,8360,8362,8364,8366,8368,8370,8372,8374],{"class":3024,"line":3208},[3022,8361,8221],{"class":3077},[3022,8363,3103],{"class":3028},[3022,8365,8226],{"class":3077},[3022,8367,3103],{"class":3028},[3022,8369,8351],{"class":3068},[3022,8371,3059],{"class":3028},[3022,8373,5695],{"class":3032},[3022,8375,5262],{"class":3028},[3022,8377,8378,8380,8382,8384,8386,8388,8390,8392],{"class":3024,"line":3214},[3022,8379,8221],{"class":3077},[3022,8381,3103],{"class":3028},[3022,8383,8226],{"class":3077},[3022,8385,3103],{"class":3028},[3022,8387,8351],{"class":3068},[3022,8389,3059],{"class":3028},[3022,8391,8322],{"class":3032},[3022,8393,5262],{"class":3028},[3022,8395,8396,8398,8400,8402,8404,8406,8408,8410],{"class":3024,"line":3236},[3022,8397,8221],{"class":3077},[3022,8399,3103],{"class":3028},[3022,8401,8226],{"class":3077},[3022,8403,3103],{"class":3028},[3022,8405,8351],{"class":3068},[3022,8407,3059],{"class":3028},[3022,8409,8267],{"class":3032},[3022,8411,5262],{"class":3028},[3022,8413,8414,8416,8418,8420,8422,8424,8426,8428],{"class":3024,"line":3241},[3022,8415,8221],{"class":3077},[3022,8417,3103],{"class":3028},[3022,8419,8226],{"class":3077},[3022,8421,3103],{"class":3028},[3022,8423,8351],{"class":3068},[3022,8425,3059],{"class":3028},[3022,8427,7597],{"class":3032},[3022,8429,5262],{"class":3028},[4741,8431,8433],{"id":8432},"спосіб-2-на-рівні-контролера","Спосіб 2: На рівні контролера",[3012,8435,8437],{"className":3014,"code":8436,"language":3016,"meta":3017,"style":3017},"[ApiController]\n[Route(\"api/[controller]\")]\n[ServiceFilter(typeof(ApiKeyAuthFilter))] // Для всіх методів контролера\npublic class ProductsController : ControllerBase\n{\n    // ...\n}\n",[3019,8438,8439,8448,8462,8484,8498,8502,8507],{"__ignoreMap":3017},[3022,8440,8441,8443,8446],{"class":3024,"line":3025},[3022,8442,3029],{"class":3028},[3022,8444,8445],{"class":3032},"ApiController",[3022,8447,4390],{"class":3028},[3022,8449,8450,8452,8455,8457,8460],{"class":3024,"line":3046},[3022,8451,3029],{"class":3028},[3022,8453,8454],{"class":3032},"Route",[3022,8456,3036],{"class":3028},[3022,8458,8459],{"class":3039},"\"api/[controller]\"",[3022,8461,3043],{"class":3028},[3022,8463,8464,8466,8469,8471,8474,8476,8478,8481],{"class":3024,"line":3084},[3022,8465,3029],{"class":3028},[3022,8467,8468],{"class":3032},"ServiceFilter",[3022,8470,3036],{"class":3028},[3022,8472,8473],{"class":3049},"typeof",[3022,8475,3036],{"class":3028},[3022,8477,4881],{"class":3032},[3022,8479,8480],{"class":3028},"))] ",[3022,8482,8483],{"class":3093},"// Для всіх методів контролера\n",[3022,8485,8486,8488,8490,8493,8495],{"class":3024,"line":3090},[3022,8487,3050],{"class":3049},[3022,8489,3987],{"class":3049},[3022,8491,8492],{"class":3032}," ProductsController",[3022,8494,3993],{"class":3028},[3022,8496,8497],{"class":3032},"ControllerBase\n",[3022,8499,8500],{"class":3024,"line":3097},[3022,8501,3087],{"class":3028},[3022,8503,8504],{"class":3024,"line":3123},[3022,8505,8506],{"class":3093},"    // ...\n",[3022,8508,8509],{"class":3024,"line":3129},[3022,8510,3381],{"class":3028},[4741,8512,8514],{"id":8513},"спосіб-3-на-рівні-методу","Спосіб 3: На рівні методу",[3012,8516,8518],{"className":3014,"code":8517,"language":3016,"meta":3017,"style":3017},"[HttpPost]\n[ServiceFilter(typeof(RequestValidationFilter))] // Тільки для цього методу\npublic async Task\u003CIActionResult> Create(CreateProductDto dto)\n{\n    // ...\n}\n",[3019,8519,8520,8529,8548,8574,8578,8582],{"__ignoreMap":3017},[3022,8521,8522,8524,8527],{"class":3024,"line":3025},[3022,8523,3029],{"class":3028},[3022,8525,8526],{"class":3032},"HttpPost",[3022,8528,4390],{"class":3028},[3022,8530,8531,8533,8535,8537,8539,8541,8543,8545],{"class":3024,"line":3046},[3022,8532,3029],{"class":3028},[3022,8534,8468],{"class":3032},[3022,8536,3036],{"class":3028},[3022,8538,8473],{"class":3049},[3022,8540,3036],{"class":3028},[3022,8542,5695],{"class":3032},[3022,8544,8480],{"class":3028},[3022,8546,8547],{"class":3093},"// Тільки для цього методу\n",[3022,8549,8550,8552,8554,8556,8558,8560,8562,8565,8567,8570,8572],{"class":3024,"line":3084},[3022,8551,3050],{"class":3049},[3022,8553,3053],{"class":3049},[3022,8555,3056],{"class":3032},[3022,8557,3059],{"class":3028},[3022,8559,3062],{"class":3032},[3022,8561,3065],{"class":3028},[3022,8563,8564],{"class":3068},"Create",[3022,8566,3036],{"class":3028},[3022,8568,8569],{"class":3032},"CreateProductDto",[3022,8571,6231],{"class":3077},[3022,8573,3081],{"class":3028},[3022,8575,8576],{"class":3024,"line":3090},[3022,8577,3087],{"class":3028},[3022,8579,8580],{"class":3024,"line":3097},[3022,8581,8506],{"class":3093},[3022,8583,8584],{"class":3024,"line":3123},[3022,8585,3381],{"class":3028},[3934,8587,8588,8593,8607],{},[2964,8589,8590],{},[2974,8591,8592],{},"ServiceFilter vs TypeFilter:",[2968,8594,8595,8601],{},[2971,8596,8597,8600],{},[3019,8598,8599],{},"[ServiceFilter(typeof(MyFilter))]"," — фільтр береться з DI (потрібна реєстрація)",[2971,8602,8603,8606],{},[3019,8604,8605],{},"[TypeFilter(typeof(MyFilter))]"," — фільтр створюється щоразу (без DI)",[2964,8608,8609,8610,8612],{},"Використовуйте ",[3019,8611,8468],{}," для фільтрів з залежностями (ILogger, IConfiguration).",[3423,8614,8616],{"id":8615},"повна-конфігурація-programcs","Повна конфігурація Program.cs",[3012,8618,8620],{"className":3014,"code":8619,"language":3016,"meta":3017,"style":3017},"using Microsoft.EntityFrameworkCore;\nusing EcommerceFiltersApi.Data;\nusing EcommerceFiltersApi.Filters;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Реєстрація DbContext\nbuilder.Services.AddDbContext\u003CProductDbContext>(options =>\n    options.UseInMemoryDatabase(\"ProductsDb\"));\n\n// Реєстрація фільтрів у DI\nbuilder.Services.AddScoped\u003CApiKeyAuthFilter>();\nbuilder.Services.AddScoped\u003CRequestValidationFilter>();\nbuilder.Services.AddScoped\u003CResponseWrapperFilter>();\nbuilder.Services.AddScoped\u003CCorrelationIdFilter>();\nbuilder.Services.AddScoped\u003CPerformanceMonitoringFilter>();\n\n// Налаштування Controllers з глобальними фільтрами\nbuilder.Services.AddControllers(options =>\n{\n    // Порядок важливий!\n    options.Filters.Add\u003CCorrelationIdFilter>();        // 1. Додаємо Correlation ID\n    options.Filters.Add\u003CPerformanceMonitoringFilter>(); // 2. Починаємо вимірювання\n    options.Filters.Add\u003CRequestValidationFilter>();     // 3. Валідуємо запит\n    options.Filters.Add\u003CResponseWrapperFilter>();       // 4. Обгортаємо відповідь\n});\n\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\n\nvar app = builder.Build();\n\n// Ініціалізація бази даних\nusing (var scope = app.Services.CreateScope())\n{\n    var db = scope.ServiceProvider.GetRequiredService\u003CProductDbContext>();\n    db.Database.EnsureCreated();\n}\n\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}\n\napp.UseHttpsRedirection();\napp.UseAuthorization();\napp.MapControllers();\n\napp.Run();\n",[3019,8621,8622,8635,8647,8659,8663,8687,8691,8696,8720,8737,8741,8746,8764,8782,8800,8818,8836,8840,8845,8863,8867,8872,8894,8916,8938,8960,8964,8968,8983,8998,9002,9020,9024,9029,9057,9061,9089,9106,9110,9114,9135,9139,9151,9162,9166,9170,9181,9192,9203,9207],{"__ignoreMap":3017},[3022,8623,8624,8626,8628,8630,8633],{"class":3024,"line":3025},[3022,8625,4299],{"class":3138},[3022,8627,4761],{"class":3032},[3022,8629,3103],{"class":3028},[3022,8631,8632],{"class":3032},"EntityFrameworkCore",[3022,8634,4315],{"class":3028},[3022,8636,8637,8639,8641,8643,8645],{"class":3024,"line":3046},[3022,8638,4299],{"class":3138},[3022,8640,4327],{"class":3032},[3022,8642,3103],{"class":3028},[3022,8644,6943],{"class":3032},[3022,8646,4315],{"class":3028},[3022,8648,8649,8651,8653,8655,8657],{"class":3024,"line":3084},[3022,8650,4299],{"class":3138},[3022,8652,4327],{"class":3032},[3022,8654,3103],{"class":3028},[3022,8656,4792],{"class":3032},[3022,8658,4315],{"class":3028},[3022,8660,8661],{"class":3024,"line":3090},[3022,8662,4042],{"emptyLinePlaceholder":4041},[3022,8664,8665,8667,8670,8672,8675,8677,8680,8682,8685],{"class":3024,"line":3097},[3022,8666,6188],{"class":3049},[3022,8668,8669],{"class":3077}," builder",[3022,8671,3223],{"class":3028},[3022,8673,8674],{"class":3077},"WebApplication",[3022,8676,3103],{"class":3028},[3022,8678,8679],{"class":3068},"CreateBuilder",[3022,8681,3036],{"class":3028},[3022,8683,8684],{"class":3077},"args",[3022,8686,3120],{"class":3028},[3022,8688,8689],{"class":3024,"line":3123},[3022,8690,4042],{"emptyLinePlaceholder":4041},[3022,8692,8693],{"class":3024,"line":3129},[3022,8694,8695],{"class":3093},"// Реєстрація DbContext\n",[3022,8697,8698,8700,8702,8704,8706,8709,8711,8714,8716,8718],{"class":3024,"line":3135},[3022,8699,8221],{"class":3077},[3022,8701,3103],{"class":3028},[3022,8703,8226],{"class":3077},[3022,8705,3103],{"class":3028},[3022,8707,8708],{"class":3068},"AddDbContext",[3022,8710,3059],{"class":3028},[3022,8712,8713],{"class":3032},"ProductDbContext",[3022,8715,7687],{"class":3028},[3022,8717,8236],{"class":3077},[3022,8719,8239],{"class":3028},[3022,8721,8722,8724,8726,8729,8731,8734],{"class":3024,"line":3170},[3022,8723,8253],{"class":3077},[3022,8725,3103],{"class":3028},[3022,8727,8728],{"class":3068},"UseInMemoryDatabase",[3022,8730,3036],{"class":3028},[3022,8732,8733],{"class":3039},"\"ProductsDb\"",[3022,8735,8736],{"class":3028},"));\n",[3022,8738,8739],{"class":3024,"line":3175},[3022,8740,4042],{"emptyLinePlaceholder":4041},[3022,8742,8743],{"class":3024,"line":3181},[3022,8744,8745],{"class":3093},"// Реєстрація фільтрів у DI\n",[3022,8747,8748,8750,8752,8754,8756,8758,8760,8762],{"class":3024,"line":3203},[3022,8749,8221],{"class":3077},[3022,8751,3103],{"class":3028},[3022,8753,8226],{"class":3077},[3022,8755,3103],{"class":3028},[3022,8757,8351],{"class":3068},[3022,8759,3059],{"class":3028},[3022,8761,4881],{"class":3032},[3022,8763,5262],{"class":3028},[3022,8765,8766,8768,8770,8772,8774,8776,8778,8780],{"class":3024,"line":3208},[3022,8767,8221],{"class":3077},[3022,8769,3103],{"class":3028},[3022,8771,8226],{"class":3077},[3022,8773,3103],{"class":3028},[3022,8775,8351],{"class":3068},[3022,8777,3059],{"class":3028},[3022,8779,5695],{"class":3032},[3022,8781,5262],{"class":3028},[3022,8783,8784,8786,8788,8790,8792,8794,8796,8798],{"class":3024,"line":3214},[3022,8785,8221],{"class":3077},[3022,8787,3103],{"class":3028},[3022,8789,8226],{"class":3077},[3022,8791,3103],{"class":3028},[3022,8793,8351],{"class":3068},[3022,8795,3059],{"class":3028},[3022,8797,8322],{"class":3032},[3022,8799,5262],{"class":3028},[3022,8801,8802,8804,8806,8808,8810,8812,8814,8816],{"class":3024,"line":3236},[3022,8803,8221],{"class":3077},[3022,8805,3103],{"class":3028},[3022,8807,8226],{"class":3077},[3022,8809,3103],{"class":3028},[3022,8811,8351],{"class":3068},[3022,8813,3059],{"class":3028},[3022,8815,8267],{"class":3032},[3022,8817,5262],{"class":3028},[3022,8819,8820,8822,8824,8826,8828,8830,8832,8834],{"class":3024,"line":3241},[3022,8821,8221],{"class":3077},[3022,8823,3103],{"class":3028},[3022,8825,8226],{"class":3077},[3022,8827,3103],{"class":3028},[3022,8829,8351],{"class":3068},[3022,8831,3059],{"class":3028},[3022,8833,7597],{"class":3032},[3022,8835,5262],{"class":3028},[3022,8837,8838],{"class":3024,"line":3247},[3022,8839,4042],{"emptyLinePlaceholder":4041},[3022,8841,8842],{"class":3024,"line":3279},[3022,8843,8844],{"class":3093},"// Налаштування Controllers з глобальними фільтрами\n",[3022,8846,8847,8849,8851,8853,8855,8857,8859,8861],{"class":3024,"line":3284},[3022,8848,8221],{"class":3077},[3022,8850,3103],{"class":3028},[3022,8852,8226],{"class":3077},[3022,8854,3103],{"class":3028},[3022,8856,8231],{"class":3068},[3022,8858,3036],{"class":3028},[3022,8860,8236],{"class":3077},[3022,8862,8239],{"class":3028},[3022,8864,8865],{"class":3024,"line":3290},[3022,8866,3087],{"class":3028},[3022,8868,8869],{"class":3024,"line":3303},[3022,8870,8871],{"class":3093},"    // Порядок важливий!\n",[3022,8873,8874,8876,8878,8880,8882,8884,8886,8888,8891],{"class":3024,"line":3329},[3022,8875,8253],{"class":3077},[3022,8877,3103],{"class":3028},[3022,8879,4792],{"class":3077},[3022,8881,3103],{"class":3028},[3022,8883,8262],{"class":3068},[3022,8885,3059],{"class":3028},[3022,8887,8267],{"class":3032},[3022,8889,8890],{"class":3028},">();        ",[3022,8892,8893],{"class":3093},"// 1. Додаємо Correlation ID\n",[3022,8895,8896,8898,8900,8902,8904,8906,8908,8910,8913],{"class":3024,"line":3334},[3022,8897,8253],{"class":3077},[3022,8899,3103],{"class":3028},[3022,8901,4792],{"class":3077},[3022,8903,3103],{"class":3028},[3022,8905,8262],{"class":3068},[3022,8907,3059],{"class":3028},[3022,8909,7597],{"class":3032},[3022,8911,8912],{"class":3028},">(); ",[3022,8914,8915],{"class":3093},"// 2. Починаємо вимірювання\n",[3022,8917,8918,8920,8922,8924,8926,8928,8930,8932,8935],{"class":3024,"line":3340},[3022,8919,8253],{"class":3077},[3022,8921,3103],{"class":3028},[3022,8923,4792],{"class":3077},[3022,8925,3103],{"class":3028},[3022,8927,8262],{"class":3068},[3022,8929,3059],{"class":3028},[3022,8931,5695],{"class":3032},[3022,8933,8934],{"class":3028},">();     ",[3022,8936,8937],{"class":3093},"// 3. Валідуємо запит\n",[3022,8939,8940,8942,8944,8946,8948,8950,8952,8954,8957],{"class":3024,"line":3378},[3022,8941,8253],{"class":3077},[3022,8943,3103],{"class":3028},[3022,8945,4792],{"class":3077},[3022,8947,3103],{"class":3028},[3022,8949,8262],{"class":3068},[3022,8951,3059],{"class":3028},[3022,8953,8322],{"class":3032},[3022,8955,8956],{"class":3028},">();       ",[3022,8958,8959],{"class":3093},"// 4. Обгортаємо відповідь\n",[3022,8961,8962],{"class":3024,"line":3803},[3022,8963,8329],{"class":3028},[3022,8965,8966],{"class":3024,"line":3809},[3022,8967,4042],{"emptyLinePlaceholder":4041},[3022,8969,8970,8972,8974,8976,8978,8981],{"class":3024,"line":4632},[3022,8971,8221],{"class":3077},[3022,8973,3103],{"class":3028},[3022,8975,8226],{"class":3077},[3022,8977,3103],{"class":3028},[3022,8979,8980],{"class":3068},"AddEndpointsApiExplorer",[3022,8982,3200],{"class":3028},[3022,8984,8985,8987,8989,8991,8993,8996],{"class":3024,"line":4658},[3022,8986,8221],{"class":3077},[3022,8988,3103],{"class":3028},[3022,8990,8226],{"class":3077},[3022,8992,3103],{"class":3028},[3022,8994,8995],{"class":3068},"AddSwaggerGen",[3022,8997,3200],{"class":3028},[3022,8999,9000],{"class":3024,"line":4677},[3022,9001,4042],{"emptyLinePlaceholder":4041},[3022,9003,9004,9006,9009,9011,9013,9015,9018],{"class":3024,"line":4682},[3022,9005,6188],{"class":3049},[3022,9007,9008],{"class":3077}," app",[3022,9010,3223],{"class":3028},[3022,9012,8221],{"class":3077},[3022,9014,3103],{"class":3028},[3022,9016,9017],{"class":3068},"Build",[3022,9019,3200],{"class":3028},[3022,9021,9022],{"class":3024,"line":4712},[3022,9023,4042],{"emptyLinePlaceholder":4041},[3022,9025,9026],{"class":3024,"line":4731},[3022,9027,9028],{"class":3093},"// Ініціалізація бази даних\n",[3022,9030,9031,9033,9035,9037,9040,9042,9045,9047,9049,9051,9054],{"class":3024,"line":5202},[3022,9032,4299],{"class":3138},[3022,9034,3142],{"class":3028},[3022,9036,6188],{"class":3049},[3022,9038,9039],{"class":3077}," scope",[3022,9041,3223],{"class":3028},[3022,9043,9044],{"class":3077},"app",[3022,9046,3103],{"class":3028},[3022,9048,8226],{"class":3077},[3022,9050,3103],{"class":3028},[3022,9052,9053],{"class":3068},"CreateScope",[3022,9055,9056],{"class":3028},"())\n",[3022,9058,9059],{"class":3024,"line":5207},[3022,9060,3087],{"class":3028},[3022,9062,9063,9065,9068,9070,9073,9075,9078,9080,9083,9085,9087],{"class":3024,"line":5213},[3022,9064,3217],{"class":3049},[3022,9066,9067],{"class":3077}," db",[3022,9069,3223],{"class":3028},[3022,9071,9072],{"class":3077},"scope",[3022,9074,3103],{"class":3028},[3022,9076,9077],{"class":3077},"ServiceProvider",[3022,9079,3103],{"class":3028},[3022,9081,9082],{"class":3068},"GetRequiredService",[3022,9084,3059],{"class":3028},[3022,9086,8713],{"class":3032},[3022,9088,5262],{"class":3028},[3022,9090,9091,9094,9096,9099,9101,9104],{"class":3024,"line":5265},[3022,9092,9093],{"class":3077},"    db",[3022,9095,3103],{"class":3028},[3022,9097,9098],{"class":3077},"Database",[3022,9100,3103],{"class":3028},[3022,9102,9103],{"class":3068},"EnsureCreated",[3022,9105,3200],{"class":3028},[3022,9107,9108],{"class":3024,"line":5270},[3022,9109,3381],{"class":3028},[3022,9111,9112],{"class":3024,"line":5276},[3022,9113,4042],{"emptyLinePlaceholder":4041},[3022,9115,9116,9119,9121,9123,9125,9128,9130,9133],{"class":3024,"line":5304},[3022,9117,9118],{"class":3138},"if",[3022,9120,3142],{"class":3028},[3022,9122,9044],{"class":3077},[3022,9124,3103],{"class":3028},[3022,9126,9127],{"class":3077},"Environment",[3022,9129,3103],{"class":3028},[3022,9131,9132],{"class":3068},"IsDevelopment",[3022,9134,9056],{"class":3028},[3022,9136,9137],{"class":3024,"line":5309},[3022,9138,3087],{"class":3028},[3022,9140,9141,9144,9146,9149],{"class":3024,"line":5329},[3022,9142,9143],{"class":3077},"    app",[3022,9145,3103],{"class":3028},[3022,9147,9148],{"class":3068},"UseSwagger",[3022,9150,3200],{"class":3028},[3022,9152,9153,9155,9157,9160],{"class":3024,"line":5334},[3022,9154,9143],{"class":3077},[3022,9156,3103],{"class":3028},[3022,9158,9159],{"class":3068},"UseSwaggerUI",[3022,9161,3200],{"class":3028},[3022,9163,9164],{"class":3024,"line":5355},[3022,9165,3381],{"class":3028},[3022,9167,9168],{"class":3024,"line":5360},[3022,9169,4042],{"emptyLinePlaceholder":4041},[3022,9171,9172,9174,9176,9179],{"class":3024,"line":5375},[3022,9173,9044],{"class":3077},[3022,9175,3103],{"class":3028},[3022,9177,9178],{"class":3068},"UseHttpsRedirection",[3022,9180,3200],{"class":3028},[3022,9182,9183,9185,9187,9190],{"class":3024,"line":5387},[3022,9184,9044],{"class":3077},[3022,9186,3103],{"class":3028},[3022,9188,9189],{"class":3068},"UseAuthorization",[3022,9191,3200],{"class":3028},[3022,9193,9194,9196,9198,9201],{"class":3024,"line":5399},[3022,9195,9044],{"class":3077},[3022,9197,3103],{"class":3028},[3022,9199,9200],{"class":3068},"MapControllers",[3022,9202,3200],{"class":3028},[3022,9204,9205],{"class":3024,"line":5420},[3022,9206,4042],{"emptyLinePlaceholder":4041},[3022,9208,9209,9211,9213,9216],{"class":3024,"line":5425},[3022,9210,9044],{"class":3077},[3022,9212,3103],{"class":3028},[3022,9214,9215],{"class":3068},"Run",[3022,9217,3200],{"class":3028},[3423,9219,9221],{"id":9220},"конфігурація-appsettingsjson","Конфігурація appsettings.json",[3012,9223,9225],{"className":3505,"code":9224,"language":3507,"meta":3017,"style":3017},"{\n  \"ApiKeys\": [\n    \"sk_test_abc123xyz\",\n    \"sk_live_def456uvw\"\n  ],\n  \"Performance\": {\n    \"WarningThresholdMs\": 1000\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"EcommerceFiltersApi.Filters\": \"Information\"\n    }\n  }\n}\n",[3019,9226,9227,9231,9239,9246,9251,9256,9263,9273,9277,9284,9291,9303,9313,9317,9321],{"__ignoreMap":3017},[3022,9228,9229],{"class":3024,"line":3025},[3022,9230,3087],{"class":3028},[3022,9232,9233,9236],{"class":3024,"line":3046},[3022,9234,9235],{"class":3518},"  \"ApiKeys\"",[3022,9237,9238],{"class":3028},": [\n",[3022,9240,9241,9244],{"class":3024,"line":3084},[3022,9242,9243],{"class":3039},"    \"sk_test_abc123xyz\"",[3022,9245,3527],{"class":3028},[3022,9247,9248],{"class":3024,"line":3090},[3022,9249,9250],{"class":3039},"    \"sk_live_def456uvw\"\n",[3022,9252,9253],{"class":3024,"line":3097},[3022,9254,9255],{"class":3028},"  ],\n",[3022,9257,9258,9261],{"class":3024,"line":3123},[3022,9259,9260],{"class":3518},"  \"Performance\"",[3022,9262,3564],{"class":3028},[3022,9264,9265,9268,9270],{"class":3024,"line":3129},[3022,9266,9267],{"class":3518},"    \"WarningThresholdMs\"",[3022,9269,3522],{"class":3028},[3022,9271,9272],{"class":3150},"1000\n",[3022,9274,9275],{"class":3024,"line":3135},[3022,9276,7129],{"class":3028},[3022,9278,9279,9282],{"class":3024,"line":3170},[3022,9280,9281],{"class":3518},"  \"Logging\"",[3022,9283,3564],{"class":3028},[3022,9285,9286,9289],{"class":3024,"line":3175},[3022,9287,9288],{"class":3518},"    \"LogLevel\"",[3022,9290,3564],{"class":3028},[3022,9292,9293,9296,9298,9301],{"class":3024,"line":3181},[3022,9294,9295],{"class":3518},"      \"Default\"",[3022,9297,3522],{"class":3028},[3022,9299,9300],{"class":3039},"\"Information\"",[3022,9302,3527],{"class":3028},[3022,9304,9305,9308,9310],{"class":3024,"line":3203},[3022,9306,9307],{"class":3518},"      \"EcommerceFiltersApi.Filters\"",[3022,9309,3522],{"class":3028},[3022,9311,9312],{"class":3039},"\"Information\"\n",[3022,9314,9315],{"class":3024,"line":3208},[3022,9316,4036],{"class":3028},[3022,9318,9319],{"class":3024,"line":3214},[3022,9320,3591],{"class":3028},[3022,9322,9323],{"class":3024,"line":3236},[3022,9324,3381],{"class":3028},[3652,9326],{},[3423,9328,9330],{"id":9329},"крок-4-створення-контролера","Крок 4: Створення контролера",[2964,9332,4286,9333,3437],{},[3019,9334,9335],{},"Controllers/ProductsController.cs",[3012,9337,9339],{"className":3014,"code":9338,"language":3016,"meta":3017,"style":3017},"using Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing EcommerceFiltersApi.Data;\nusing EcommerceFiltersApi.Models;\nusing EcommerceFiltersApi.Filters;\n\nnamespace EcommerceFiltersApi.Controllers;\n\n[ApiController]\n[Route(\"api/[controller]\")]\n[ServiceFilter(typeof(ApiKeyAuthFilter))] // API key для всього контролера\npublic class ProductsController : ControllerBase\n{\n    private readonly ProductDbContext _db;\n    private readonly ILogger\u003CProductsController> _logger;\n\n    public ProductsController(ProductDbContext db, ILogger\u003CProductsController> logger)\n    {\n        _db = db;\n        _logger = logger;\n    }\n\n    /// \u003Csummary>\n    /// Отримати всі продукти\n    /// \u003C/summary>\n    [HttpGet]\n    [ProducesResponseType(typeof(ApiResponse\u003CIEnumerable\u003CProduct>>), StatusCodes.Status200OK)]\n    public async Task\u003CActionResult\u003CIEnumerable\u003CProduct>>> GetAll()\n    {\n        // Отримуємо Correlation ID з HttpContext (додано фільтром)\n        var correlationId = HttpContext.Items[\"CorrelationId\"]?.ToString();\n        _logger.LogInformation(\"Fetching all products. CorrelationId: {CorrelationId}\", correlationId);\n\n        var products = await _db.Products\n            .Where(p => p.IsActive)\n            .ToListAsync();\n\n        return Ok(products);\n    }\n\n    /// \u003Csummary>\n    /// Отримати продукт за ID\n    /// \u003C/summary>\n    [HttpGet(\"{id:int}\")]\n    [ProducesResponseType(typeof(ApiResponse\u003CProduct>), StatusCodes.Status200OK)]\n    [ProducesResponseType(StatusCodes.Status404NotFound)]\n    public async Task\u003CActionResult\u003CProduct>> GetById(int id)\n    {\n        var product = await _db.Products.FindAsync(id);\n\n        if (product is null)\n            return NotFound();\n\n        return Ok(product);\n    }\n\n    /// \u003Csummary>\n    /// Створити новий продукт\n    /// \u003C/summary>\n    [HttpPost]\n    [ProducesResponseType(typeof(ApiResponse\u003CProduct>), StatusCodes.Status201Created)]\n    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]\n    public async Task\u003CActionResult\u003CProduct>> Create(CreateProductDto dto)\n    {\n        // Валідація вже пройшла через RequestValidationFilter\n        \n        var product = new Product\n        {\n            Name = dto.Name,\n            Price = dto.Price,\n            Stock = dto.Stock\n        };\n\n        _db.Products.Add(product);\n        await _db.SaveChangesAsync();\n\n        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);\n    }\n\n    /// \u003Csummary>\n    /// Видалити продукт\n    /// \u003C/summary>\n    [HttpDelete(\"{id:int}\")]\n    [ProducesResponseType(StatusCodes.Status204NoContent)]\n    [ProducesResponseType(StatusCodes.Status404NotFound)]\n    public async Task\u003CIActionResult> Delete(int id)\n    {\n        var product = await _db.Products.FindAsync(id);\n\n        if (product is null)\n            return NotFound();\n\n        _db.Products.Remove(product);\n        await _db.SaveChangesAsync();\n\n        return NoContent();\n    }\n}\n",[3019,9340,9341,9357,9369,9381,9393,9405,9409,9422,9426,9434,9446,9465,9477,9481,9494,9513,9517,9543,9547,9559,9569,9573,9577,9591,9596,9607,9615,9653,9682,9686,9691,9716,9735,9739,9757,9778,9787,9791,9805,9809,9813,9823,9828,9838,9851,9880,9897,9927,9931,9957,9961,9976,9985,9989,10001,10005,10009,10019,10024,10034,10042,10071,10097,10125,10129,10134,10138,10150,10154,10170,10185,10199,10203,10208,10227,10241,10246,10287,10292,10297,10308,10314,10325,10339,10357,10374,10400,10405,10432,10437,10452,10461,10466,10486,10499,10504,10514,10519],{"__ignoreMap":3017},[3022,9342,9343,9345,9347,9349,9351,9353,9355],{"class":3024,"line":3025},[3022,9344,4299],{"class":3138},[3022,9346,4761],{"class":3032},[3022,9348,3103],{"class":3028},[3022,9350,4766],{"class":3032},[3022,9352,3103],{"class":3028},[3022,9354,4771],{"class":3032},[3022,9356,4315],{"class":3028},[3022,9358,9359,9361,9363,9365,9367],{"class":3024,"line":3046},[3022,9360,4299],{"class":3138},[3022,9362,4761],{"class":3032},[3022,9364,3103],{"class":3028},[3022,9366,8632],{"class":3032},[3022,9368,4315],{"class":3028},[3022,9370,9371,9373,9375,9377,9379],{"class":3024,"line":3084},[3022,9372,4299],{"class":3138},[3022,9374,4327],{"class":3032},[3022,9376,3103],{"class":3028},[3022,9378,6943],{"class":3032},[3022,9380,4315],{"class":3028},[3022,9382,9383,9385,9387,9389,9391],{"class":3024,"line":3090},[3022,9384,4299],{"class":3138},[3022,9386,4327],{"class":3032},[3022,9388,3103],{"class":3028},[3022,9390,4332],{"class":3032},[3022,9392,4315],{"class":3028},[3022,9394,9395,9397,9399,9401,9403],{"class":3024,"line":3097},[3022,9396,4299],{"class":3138},[3022,9398,4327],{"class":3032},[3022,9400,3103],{"class":3028},[3022,9402,4792],{"class":3032},[3022,9404,4315],{"class":3028},[3022,9406,9407],{"class":3024,"line":3123},[3022,9408,4042],{"emptyLinePlaceholder":4041},[3022,9410,9411,9413,9415,9417,9420],{"class":3024,"line":3129},[3022,9412,4324],{"class":3049},[3022,9414,4327],{"class":3032},[3022,9416,3103],{"class":3028},[3022,9418,9419],{"class":3032},"Controllers",[3022,9421,4315],{"class":3028},[3022,9423,9424],{"class":3024,"line":3135},[3022,9425,4042],{"emptyLinePlaceholder":4041},[3022,9427,9428,9430,9432],{"class":3024,"line":3170},[3022,9429,3029],{"class":3028},[3022,9431,8445],{"class":3032},[3022,9433,4390],{"class":3028},[3022,9435,9436,9438,9440,9442,9444],{"class":3024,"line":3175},[3022,9437,3029],{"class":3028},[3022,9439,8454],{"class":3032},[3022,9441,3036],{"class":3028},[3022,9443,8459],{"class":3039},[3022,9445,3043],{"class":3028},[3022,9447,9448,9450,9452,9454,9456,9458,9460,9462],{"class":3024,"line":3181},[3022,9449,3029],{"class":3028},[3022,9451,8468],{"class":3032},[3022,9453,3036],{"class":3028},[3022,9455,8473],{"class":3049},[3022,9457,3036],{"class":3028},[3022,9459,4881],{"class":3032},[3022,9461,8480],{"class":3028},[3022,9463,9464],{"class":3093},"// API key для всього контролера\n",[3022,9466,9467,9469,9471,9473,9475],{"class":3024,"line":3203},[3022,9468,3050],{"class":3049},[3022,9470,3987],{"class":3049},[3022,9472,8492],{"class":3032},[3022,9474,3993],{"class":3028},[3022,9476,8497],{"class":3032},[3022,9478,9479],{"class":3024,"line":3208},[3022,9480,3087],{"class":3028},[3022,9482,9483,9485,9487,9490,9492],{"class":3024,"line":3214},[3022,9484,4837],{"class":3049},[3022,9486,4859],{"class":3049},[3022,9488,9489],{"class":3032}," ProductDbContext",[3022,9491,3260],{"class":3077},[3022,9493,4315],{"class":3028},[3022,9495,9496,9498,9500,9502,9504,9507,9509,9511],{"class":3024,"line":3236},[3022,9497,4837],{"class":3049},[3022,9499,4859],{"class":3049},[3022,9501,4876],{"class":3032},[3022,9503,3059],{"class":3028},[3022,9505,9506],{"class":3032},"ProductsController",[3022,9508,3065],{"class":3028},[3022,9510,4886],{"class":3077},[3022,9512,4315],{"class":3028},[3022,9514,9515],{"class":3024,"line":3241},[3022,9516,4042],{"emptyLinePlaceholder":4041},[3022,9518,9519,9521,9523,9525,9527,9529,9531,9533,9535,9537,9539,9541],{"class":3024,"line":3247},[3022,9520,4005],{"class":3049},[3022,9522,8492],{"class":3068},[3022,9524,3036],{"class":3028},[3022,9526,8713],{"class":3032},[3022,9528,9067],{"class":3077},[3022,9530,3114],{"class":3028},[3022,9532,4911],{"class":3032},[3022,9534,3059],{"class":3028},[3022,9536,9506],{"class":3032},[3022,9538,3065],{"class":3028},[3022,9540,4920],{"class":3077},[3022,9542,3081],{"class":3028},[3022,9544,9545],{"class":3024,"line":3279},[3022,9546,4026],{"class":3028},[3022,9548,9549,9552,9554,9557],{"class":3024,"line":3284},[3022,9550,9551],{"class":3077},"        _db",[3022,9553,3223],{"class":3028},[3022,9555,9556],{"class":3077},"db",[3022,9558,4315],{"class":3028},[3022,9560,9561,9563,9565,9567],{"class":3024,"line":3290},[3022,9562,4943],{"class":3077},[3022,9564,3223],{"class":3028},[3022,9566,4920],{"class":3077},[3022,9568,4315],{"class":3028},[3022,9570,9571],{"class":3024,"line":3303},[3022,9572,4036],{"class":3028},[3022,9574,9575],{"class":3024,"line":3329},[3022,9576,4042],{"emptyLinePlaceholder":4041},[3022,9578,9579,9582,9585,9589],{"class":3024,"line":3334},[3022,9580,9581],{"class":3093},"    /// ",[3022,9583,3059],{"class":9584},"s0P7L",[3022,9586,9588],{"class":9587},"sKtos","summary",[3022,9590,6707],{"class":9584},[3022,9592,9593],{"class":3024,"line":3340},[3022,9594,9595],{"class":3093},"    /// Отримати всі продукти\n",[3022,9597,9598,9600,9603,9605],{"class":3024,"line":3378},[3022,9599,9581],{"class":3093},[3022,9601,9602],{"class":9584},"\u003C/",[3022,9604,9588],{"class":9587},[3022,9606,6707],{"class":9584},[3022,9608,9609,9611,9613],{"class":3024,"line":3803},[3022,9610,4384],{"class":3028},[3022,9612,3033],{"class":3032},[3022,9614,4390],{"class":3028},[3022,9616,9617,9619,9622,9624,9626,9628,9631,9633,9636,9638,9641,9644,9646,9648,9651],{"class":3024,"line":3809},[3022,9618,4384],{"class":3028},[3022,9620,9621],{"class":3032},"ProducesResponseType",[3022,9623,3036],{"class":3028},[3022,9625,8473],{"class":3049},[3022,9627,3036],{"class":3028},[3022,9629,9630],{"class":3032},"ApiResponse",[3022,9632,3059],{"class":3028},[3022,9634,9635],{"class":3032},"IEnumerable",[3022,9637,3059],{"class":3028},[3022,9639,9640],{"class":3032},"Product",[3022,9642,9643],{"class":3028},">>), ",[3022,9645,5117],{"class":3077},[3022,9647,3103],{"class":3028},[3022,9649,9650],{"class":3077},"Status200OK",[3022,9652,3043],{"class":3028},[3022,9654,9655,9657,9659,9661,9663,9666,9668,9670,9672,9674,9677,9680],{"class":3024,"line":4632},[3022,9656,4005],{"class":3049},[3022,9658,3053],{"class":3049},[3022,9660,3056],{"class":3032},[3022,9662,3059],{"class":3028},[3022,9664,9665],{"class":3032},"ActionResult",[3022,9667,3059],{"class":3028},[3022,9669,9635],{"class":3032},[3022,9671,3059],{"class":3028},[3022,9673,9640],{"class":3032},[3022,9675,9676],{"class":3028},">>> ",[3022,9678,9679],{"class":3068},"GetAll",[3022,9681,6045],{"class":3028},[3022,9683,9684],{"class":3024,"line":4658},[3022,9685,4026],{"class":3028},[3022,9687,9688],{"class":3024,"line":4677},[3022,9689,9690],{"class":3093},"        // Отримуємо Correlation ID з HttpContext (додано фільтром)\n",[3022,9692,9693,9695,9697,9699,9701,9703,9705,9707,9709,9712,9714],{"class":3024,"line":4682},[3022,9694,4156],{"class":3049},[3022,9696,7323],{"class":3077},[3022,9698,3223],{"class":3028},[3022,9700,5001],{"class":3077},[3022,9702,3103],{"class":3028},[3022,9704,5497],{"class":3077},[3022,9706,3029],{"class":3028},[3022,9708,7475],{"class":3039},[3022,9710,9711],{"class":3028},"]?.",[3022,9713,5298],{"class":3068},[3022,9715,3200],{"class":3028},[3022,9717,9718,9720,9722,9724,9726,9729,9731,9733],{"class":3024,"line":4712},[3022,9719,4943],{"class":3077},[3022,9721,3103],{"class":3028},[3022,9723,3106],{"class":3068},[3022,9725,3036],{"class":3028},[3022,9727,9728],{"class":3039},"\"Fetching all products. CorrelationId: {CorrelationId}\"",[3022,9730,3114],{"class":3028},[3022,9732,7412],{"class":3077},[3022,9734,3120],{"class":3028},[3022,9736,9737],{"class":3024,"line":4731},[3022,9738,4042],{"emptyLinePlaceholder":4041},[3022,9740,9741,9743,9746,9748,9750,9752,9754],{"class":3024,"line":5202},[3022,9742,4156],{"class":3049},[3022,9744,9745],{"class":3077}," products",[3022,9747,3223],{"class":3028},[3022,9749,3257],{"class":3049},[3022,9751,3260],{"class":3077},[3022,9753,3103],{"class":3028},[3022,9755,9756],{"class":3077},"Products\n",[3022,9758,9759,9761,9763,9765,9767,9769,9771,9773,9776],{"class":3024,"line":5207},[3022,9760,7351],{"class":3028},[3022,9762,5947],{"class":3068},[3022,9764,3036],{"class":3028},[3022,9766,2964],{"class":3077},[3022,9768,5886],{"class":3028},[3022,9770,2964],{"class":3077},[3022,9772,3103],{"class":3028},[3022,9774,9775],{"class":3077},"IsActive",[3022,9777,3081],{"class":3028},[3022,9779,9780,9782,9785],{"class":3024,"line":5213},[3022,9781,7351],{"class":3028},[3022,9783,9784],{"class":3068},"ToListAsync",[3022,9786,3200],{"class":3028},[3022,9788,9789],{"class":3024,"line":5265},[3022,9790,4042],{"emptyLinePlaceholder":4041},[3022,9792,9793,9796,9798,9800,9803],{"class":3024,"line":5270},[3022,9794,9795],{"class":3138},"        return",[3022,9797,3346],{"class":3068},[3022,9799,3036],{"class":3028},[3022,9801,9802],{"class":3077},"products",[3022,9804,3120],{"class":3028},[3022,9806,9807],{"class":3024,"line":5276},[3022,9808,4036],{"class":3028},[3022,9810,9811],{"class":3024,"line":5304},[3022,9812,4042],{"emptyLinePlaceholder":4041},[3022,9814,9815,9817,9819,9821],{"class":3024,"line":5309},[3022,9816,9581],{"class":3093},[3022,9818,3059],{"class":9584},[3022,9820,9588],{"class":9587},[3022,9822,6707],{"class":9584},[3022,9824,9825],{"class":3024,"line":5329},[3022,9826,9827],{"class":3093},"    /// Отримати продукт за ID\n",[3022,9829,9830,9832,9834,9836],{"class":3024,"line":5334},[3022,9831,9581],{"class":3093},[3022,9833,9602],{"class":9584},[3022,9835,9588],{"class":9587},[3022,9837,6707],{"class":9584},[3022,9839,9840,9842,9844,9846,9849],{"class":3024,"line":5355},[3022,9841,4384],{"class":3028},[3022,9843,3033],{"class":3032},[3022,9845,3036],{"class":3028},[3022,9847,9848],{"class":3039},"\"{id:int}\"",[3022,9850,3043],{"class":3028},[3022,9852,9853,9855,9857,9859,9861,9863,9865,9867,9869,9872,9874,9876,9878],{"class":3024,"line":5360},[3022,9854,4384],{"class":3028},[3022,9856,9621],{"class":3032},[3022,9858,3036],{"class":3028},[3022,9860,8473],{"class":3049},[3022,9862,3036],{"class":3028},[3022,9864,9630],{"class":3032},[3022,9866,3059],{"class":3028},[3022,9868,9640],{"class":3032},[3022,9870,9871],{"class":3028},">), ",[3022,9873,5117],{"class":3077},[3022,9875,3103],{"class":3028},[3022,9877,9650],{"class":3077},[3022,9879,3043],{"class":3028},[3022,9881,9882,9884,9886,9888,9890,9892,9895],{"class":3024,"line":5375},[3022,9883,4384],{"class":3028},[3022,9885,9621],{"class":3032},[3022,9887,3036],{"class":3028},[3022,9889,5117],{"class":3077},[3022,9891,3103],{"class":3028},[3022,9893,9894],{"class":3077},"Status404NotFound",[3022,9896,3043],{"class":3028},[3022,9898,9899,9901,9903,9905,9907,9909,9911,9913,9916,9919,9921,9923,9925],{"class":3024,"line":5387},[3022,9900,4005],{"class":3049},[3022,9902,3053],{"class":3049},[3022,9904,3056],{"class":3032},[3022,9906,3059],{"class":3028},[3022,9908,9665],{"class":3032},[3022,9910,3059],{"class":3028},[3022,9912,9640],{"class":3032},[3022,9914,9915],{"class":3028},">> ",[3022,9917,9918],{"class":3068},"GetById",[3022,9920,3036],{"class":3028},[3022,9922,3074],{"class":3049},[3022,9924,3078],{"class":3077},[3022,9926,3081],{"class":3028},[3022,9928,9929],{"class":3024,"line":5399},[3022,9930,4026],{"class":3028},[3022,9932,9933,9935,9937,9939,9941,9943,9945,9947,9949,9951,9953,9955],{"class":3024,"line":5420},[3022,9934,4156],{"class":3049},[3022,9936,3252],{"class":3077},[3022,9938,3223],{"class":3028},[3022,9940,3257],{"class":3049},[3022,9942,3260],{"class":3077},[3022,9944,3103],{"class":3028},[3022,9946,3265],{"class":3077},[3022,9948,3103],{"class":3028},[3022,9950,3270],{"class":3068},[3022,9952,3036],{"class":3028},[3022,9954,3117],{"class":3077},[3022,9956,3120],{"class":3028},[3022,9958,9959],{"class":3024,"line":5425},[3022,9960,4042],{"emptyLinePlaceholder":4041},[3022,9962,9963,9965,9967,9969,9971,9974],{"class":3024,"line":5432},[3022,9964,4991],{"class":3138},[3022,9966,3142],{"class":3028},[3022,9968,3372],{"class":3077},[3022,9970,6225],{"class":3049},[3022,9972,9973],{"class":3049}," null",[3022,9975,3081],{"class":3028},[3022,9977,9978,9980,9983],{"class":3024,"line":5437},[3022,9979,5192],{"class":3138},[3022,9981,9982],{"class":3068}," NotFound",[3022,9984,3200],{"class":3028},[3022,9986,9987],{"class":3024,"line":5442},[3022,9988,4042],{"emptyLinePlaceholder":4041},[3022,9990,9991,9993,9995,9997,9999],{"class":3024,"line":5474},[3022,9992,9795],{"class":3138},[3022,9994,3346],{"class":3068},[3022,9996,3036],{"class":3028},[3022,9998,3372],{"class":3077},[3022,10000,3120],{"class":3028},[3022,10002,10003],{"class":3024,"line":5479},[3022,10004,4036],{"class":3028},[3022,10006,10007],{"class":3024,"line":5485},[3022,10008,4042],{"emptyLinePlaceholder":4041},[3022,10010,10011,10013,10015,10017],{"class":3024,"line":5516},[3022,10012,9581],{"class":3093},[3022,10014,3059],{"class":9584},[3022,10016,9588],{"class":9587},[3022,10018,6707],{"class":9584},[3022,10020,10021],{"class":3024,"line":5521},[3022,10022,10023],{"class":3093},"    /// Створити новий продукт\n",[3022,10025,10026,10028,10030,10032],{"class":3024,"line":5536},[3022,10027,9581],{"class":3093},[3022,10029,9602],{"class":9584},[3022,10031,9588],{"class":9587},[3022,10033,6707],{"class":9584},[3022,10035,10036,10038,10040],{"class":3024,"line":5541},[3022,10037,4384],{"class":3028},[3022,10039,8526],{"class":3032},[3022,10041,4390],{"class":3028},[3022,10043,10044,10046,10048,10050,10052,10054,10056,10058,10060,10062,10064,10066,10069],{"class":3024,"line":6364},[3022,10045,4384],{"class":3028},[3022,10047,9621],{"class":3032},[3022,10049,3036],{"class":3028},[3022,10051,8473],{"class":3049},[3022,10053,3036],{"class":3028},[3022,10055,9630],{"class":3032},[3022,10057,3059],{"class":3028},[3022,10059,9640],{"class":3032},[3022,10061,9871],{"class":3028},[3022,10063,5117],{"class":3077},[3022,10065,3103],{"class":3028},[3022,10067,10068],{"class":3077},"Status201Created",[3022,10070,3043],{"class":3028},[3022,10072,10073,10075,10077,10079,10081,10083,10086,10089,10091,10093,10095],{"class":3024,"line":6370},[3022,10074,4384],{"class":3028},[3022,10076,9621],{"class":3032},[3022,10078,3036],{"class":3028},[3022,10080,8473],{"class":3049},[3022,10082,3036],{"class":3028},[3022,10084,10085],{"class":3032},"ValidationProblemDetails",[3022,10087,10088],{"class":3028},"), ",[3022,10090,5117],{"class":3077},[3022,10092,3103],{"class":3028},[3022,10094,6092],{"class":3077},[3022,10096,3043],{"class":3028},[3022,10098,10099,10101,10103,10105,10107,10109,10111,10113,10115,10117,10119,10121,10123],{"class":3024,"line":6378},[3022,10100,4005],{"class":3049},[3022,10102,3053],{"class":3049},[3022,10104,3056],{"class":3032},[3022,10106,3059],{"class":3028},[3022,10108,9665],{"class":3032},[3022,10110,3059],{"class":3028},[3022,10112,9640],{"class":3032},[3022,10114,9915],{"class":3028},[3022,10116,8564],{"class":3068},[3022,10118,3036],{"class":3028},[3022,10120,8569],{"class":3032},[3022,10122,6231],{"class":3077},[3022,10124,3081],{"class":3028},[3022,10126,10127],{"class":3024,"line":6384},[3022,10128,4026],{"class":3028},[3022,10130,10131],{"class":3024,"line":6390},[3022,10132,10133],{"class":3093},"        // Валідація вже пройшла через RequestValidationFilter\n",[3022,10135,10136],{"class":3024,"line":6395},[3022,10137,4151],{"class":3028},[3022,10139,10140,10142,10144,10146,10148],{"class":3024,"line":6400},[3022,10141,4156],{"class":3049},[3022,10143,3252],{"class":3077},[3022,10145,3223],{"class":3028},[3022,10147,3351],{"class":3049},[3022,10149,4347],{"class":3032},[3022,10151,10152],{"class":3024,"line":6428},[3022,10153,5040],{"class":3028},[3022,10155,10156,10159,10161,10163,10165,10168],{"class":3024,"line":6433},[3022,10157,10158],{"class":3077},"            Name",[3022,10160,3223],{"class":3028},[3022,10162,6252],{"class":3077},[3022,10164,3103],{"class":3028},[3022,10166,10167],{"class":3077},"Name",[3022,10169,3527],{"class":3028},[3022,10171,10172,10175,10177,10179,10181,10183],{"class":3024,"line":6439},[3022,10173,10174],{"class":3077},"            Price",[3022,10176,3223],{"class":3028},[3022,10178,6252],{"class":3077},[3022,10180,3103],{"class":3028},[3022,10182,6257],{"class":3077},[3022,10184,3527],{"class":3028},[3022,10186,10187,10190,10192,10194,10196],{"class":3024,"line":6448},[3022,10188,10189],{"class":3077},"            Stock",[3022,10191,3223],{"class":3028},[3022,10193,6252],{"class":3077},[3022,10195,3103],{"class":3028},[3022,10197,10198],{"class":3077},"Stock\n",[3022,10200,10201],{"class":3024,"line":6453},[3022,10202,8170],{"class":3028},[3022,10204,10206],{"class":3024,"line":10205},73,[3022,10207,4042],{"emptyLinePlaceholder":4041},[3022,10209,10211,10213,10215,10217,10219,10221,10223,10225],{"class":3024,"line":10210},74,[3022,10212,9551],{"class":3077},[3022,10214,3103],{"class":3028},[3022,10216,3265],{"class":3077},[3022,10218,3103],{"class":3028},[3022,10220,8262],{"class":3068},[3022,10222,3036],{"class":3028},[3022,10224,3372],{"class":3077},[3022,10226,3120],{"class":3028},[3022,10228,10230,10232,10234,10236,10239],{"class":3024,"line":10229},75,[3022,10231,5524],{"class":3049},[3022,10233,3260],{"class":3077},[3022,10235,3103],{"class":3028},[3022,10237,10238],{"class":3068},"SaveChangesAsync",[3022,10240,3200],{"class":3028},[3022,10242,10244],{"class":3024,"line":10243},76,[3022,10245,4042],{"emptyLinePlaceholder":4041},[3022,10247,10249,10251,10254,10256,10259,10261,10263,10265,10267,10269,10271,10273,10275,10277,10280,10283,10285],{"class":3024,"line":10248},77,[3022,10250,9795],{"class":3138},[3022,10252,10253],{"class":3068}," CreatedAtAction",[3022,10255,3036],{"class":3028},[3022,10257,10258],{"class":3049},"nameof",[3022,10260,3036],{"class":3028},[3022,10262,9918],{"class":3077},[3022,10264,10088],{"class":3028},[3022,10266,3351],{"class":3049},[3022,10268,3354],{"class":3028},[3022,10270,3117],{"class":3077},[3022,10272,3223],{"class":3028},[3022,10274,3372],{"class":3077},[3022,10276,3103],{"class":3028},[3022,10278,10279],{"class":3077},"Id",[3022,10281,10282],{"class":3028}," }, ",[3022,10284,3372],{"class":3077},[3022,10286,3120],{"class":3028},[3022,10288,10290],{"class":3024,"line":10289},78,[3022,10291,4036],{"class":3028},[3022,10293,10295],{"class":3024,"line":10294},79,[3022,10296,4042],{"emptyLinePlaceholder":4041},[3022,10298,10300,10302,10304,10306],{"class":3024,"line":10299},80,[3022,10301,9581],{"class":3093},[3022,10303,3059],{"class":9584},[3022,10305,9588],{"class":9587},[3022,10307,6707],{"class":9584},[3022,10309,10311],{"class":3024,"line":10310},81,[3022,10312,10313],{"class":3093},"    /// Видалити продукт\n",[3022,10315,10317,10319,10321,10323],{"class":3024,"line":10316},82,[3022,10318,9581],{"class":3093},[3022,10320,9602],{"class":9584},[3022,10322,9588],{"class":9587},[3022,10324,6707],{"class":9584},[3022,10326,10328,10330,10333,10335,10337],{"class":3024,"line":10327},83,[3022,10329,4384],{"class":3028},[3022,10331,10332],{"class":3032},"HttpDelete",[3022,10334,3036],{"class":3028},[3022,10336,9848],{"class":3039},[3022,10338,3043],{"class":3028},[3022,10340,10342,10344,10346,10348,10350,10352,10355],{"class":3024,"line":10341},84,[3022,10343,4384],{"class":3028},[3022,10345,9621],{"class":3032},[3022,10347,3036],{"class":3028},[3022,10349,5117],{"class":3077},[3022,10351,3103],{"class":3028},[3022,10353,10354],{"class":3077},"Status204NoContent",[3022,10356,3043],{"class":3028},[3022,10358,10360,10362,10364,10366,10368,10370,10372],{"class":3024,"line":10359},85,[3022,10361,4384],{"class":3028},[3022,10363,9621],{"class":3032},[3022,10365,3036],{"class":3028},[3022,10367,5117],{"class":3077},[3022,10369,3103],{"class":3028},[3022,10371,9894],{"class":3077},[3022,10373,3043],{"class":3028},[3022,10375,10377,10379,10381,10383,10385,10387,10389,10392,10394,10396,10398],{"class":3024,"line":10376},86,[3022,10378,4005],{"class":3049},[3022,10380,3053],{"class":3049},[3022,10382,3056],{"class":3032},[3022,10384,3059],{"class":3028},[3022,10386,3062],{"class":3032},[3022,10388,3065],{"class":3028},[3022,10390,10391],{"class":3068},"Delete",[3022,10393,3036],{"class":3028},[3022,10395,3074],{"class":3049},[3022,10397,3078],{"class":3077},[3022,10399,3081],{"class":3028},[3022,10401,10403],{"class":3024,"line":10402},87,[3022,10404,4026],{"class":3028},[3022,10406,10408,10410,10412,10414,10416,10418,10420,10422,10424,10426,10428,10430],{"class":3024,"line":10407},88,[3022,10409,4156],{"class":3049},[3022,10411,3252],{"class":3077},[3022,10413,3223],{"class":3028},[3022,10415,3257],{"class":3049},[3022,10417,3260],{"class":3077},[3022,10419,3103],{"class":3028},[3022,10421,3265],{"class":3077},[3022,10423,3103],{"class":3028},[3022,10425,3270],{"class":3068},[3022,10427,3036],{"class":3028},[3022,10429,3117],{"class":3077},[3022,10431,3120],{"class":3028},[3022,10433,10435],{"class":3024,"line":10434},89,[3022,10436,4042],{"emptyLinePlaceholder":4041},[3022,10438,10440,10442,10444,10446,10448,10450],{"class":3024,"line":10439},90,[3022,10441,4991],{"class":3138},[3022,10443,3142],{"class":3028},[3022,10445,3372],{"class":3077},[3022,10447,6225],{"class":3049},[3022,10449,9973],{"class":3049},[3022,10451,3081],{"class":3028},[3022,10453,10455,10457,10459],{"class":3024,"line":10454},91,[3022,10456,5192],{"class":3138},[3022,10458,9982],{"class":3068},[3022,10460,3200],{"class":3028},[3022,10462,10464],{"class":3024,"line":10463},92,[3022,10465,4042],{"emptyLinePlaceholder":4041},[3022,10467,10469,10471,10473,10475,10477,10480,10482,10484],{"class":3024,"line":10468},93,[3022,10470,9551],{"class":3077},[3022,10472,3103],{"class":3028},[3022,10474,3265],{"class":3077},[3022,10476,3103],{"class":3028},[3022,10478,10479],{"class":3068},"Remove",[3022,10481,3036],{"class":3028},[3022,10483,3372],{"class":3077},[3022,10485,3120],{"class":3028},[3022,10487,10489,10491,10493,10495,10497],{"class":3024,"line":10488},94,[3022,10490,5524],{"class":3049},[3022,10492,3260],{"class":3077},[3022,10494,3103],{"class":3028},[3022,10496,10238],{"class":3068},[3022,10498,3200],{"class":3028},[3022,10500,10502],{"class":3024,"line":10501},95,[3022,10503,4042],{"emptyLinePlaceholder":4041},[3022,10505,10507,10509,10512],{"class":3024,"line":10506},96,[3022,10508,9795],{"class":3138},[3022,10510,10511],{"class":3068}," NoContent",[3022,10513,3200],{"class":3028},[3022,10515,10517],{"class":3024,"line":10516},97,[3022,10518,4036],{"class":3028},[3022,10520,10522],{"class":3024,"line":10521},98,[3022,10523,3381],{"class":3028},[2964,10525,10526],{},[2974,10527,6460],{},[5550,10529,10530,10536,10545,10551],{},[2971,10531,10532,10535],{},[2974,10533,10534],{},"Чистий код"," — контролер не захаращений логікою валідації, логування, обгортання",[2971,10537,10538,10541,10542,10544],{},[2974,10539,10540],{},"Доступ до даних фільтрів"," — через ",[3019,10543,5594],{}," (CorrelationId, ApiKey)",[2971,10546,10547,10550],{},[2974,10548,10549],{},"Автоматична обробка"," — всі фільтри виконуються автоматично",[2971,10552,10553,3406,10556,10559],{},[2974,10554,10555],{},"Типізовані відповіді",[3019,10557,10558],{},"ApiResponse\u003CT>"," для Swagger документації",[3652,10561],{},[3423,10563,10565],{"id":10564},"крок-5-тестування-фільтрів","Крок 5: Тестування фільтрів",[4223,10567,10568,10578,10585,10588,10595,10605,10613,10630,10633,10640,10650,10657,10664,10671,10677,10686,10699,10710,10716,10719,10726,10736,10743,10750,10757,10764,10770,10780,10790,10801,10807],{"title":4225},[4227,10569,10571,4201,10574],{"className":10570},[3024],[3022,10572,4234],{"className":10573},[4233],[2974,10575,10577],{"className":10576},[4238],"dotnet run",[4227,10579,10581,10584],{"className":10580},[3024],[3022,10582,4278],{"className":10583},[4246,4238],": Now listening on: https://localhost:5001",[4227,10586],{"className":10587},[3024],[4227,10589,10591],{"className":10590},[3024],[3022,10592,10594],{"className":10593},[4233],"# Тест 1: Без API ключа (401)",[4227,10596,10598,4201,10601],{"className":10597},[3024],[3022,10599,4234],{"className":10600},[4233],[2974,10602,10604],{"className":10603},[4238],"curl https://localhost:5001/api/products",[4227,10606,10608],{"className":10607},[3024],[3022,10609,10612],{"className":10610},[10611,4238],"text-rose-400","HTTP/1.1 401 Unauthorized",[4227,10614,10616,4201,10619,3522,10623,4201,10627],{"className":10615},[3024],[3022,10617,5150],{"className":10618},[4277],[3022,10620,10622],{"className":10621},[4246],"\"title\"",[3022,10624,5134],{"className":10625},[10626],"text-yellow-400",[3022,10628,5155],{"className":10629},[4277],[4227,10631],{"className":10632},[3024],[4227,10634,10636],{"className":10635},[3024],[3022,10637,10639],{"className":10638},[4233],"# Тест 2: З валідним API ключем (200)",[4227,10641,10643,4201,10646],{"className":10642},[3024],[3022,10644,4234],{"className":10645},[4233],[2974,10647,10649],{"className":10648},[4238],"curl -H \"X-Api-Key: sk_test_abc123xyz\" https://localhost:5001/api/products",[4227,10651,10653],{"className":10652},[3024],[3022,10654,10656],{"className":10655},[4246,4238],"HTTP/1.1 200 OK",[4227,10658,10660],{"className":10659},[3024],[3022,10661,10663],{"className":10662},[4277,4238],"X-Correlation-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890",[4227,10665,10667],{"className":10666},[3024],[3022,10668,10670],{"className":10669},[4277,4238],"X-Response-Time-Ms: 45",[4227,10672,10674],{"className":10673},[3024],[3022,10675,5150],{"className":10676},[4277],[4227,10678,10680,10681,10685],{"className":10679},[3024],"  ",[3022,10682,10684],{"className":10683},[4246],"\"success\"",": true,",[4227,10687,10680,10689,10693,10694,10698],{"className":10688},[3024],[3022,10690,10692],{"className":10691},[4246],"\"data\"",": [",[3022,10695,10697],{"className":10696},[4277],"{ \"id\": 1, \"name\": \"Laptop\" }","],",[4227,10700,10680,10702,3522,10706],{"className":10701},[3024],[3022,10703,10705],{"className":10704},[4246],"\"meta\"",[3022,10707,10709],{"className":10708},[4277],"{ \"timestamp\": \"2024-01-15T10:30:00Z\" }",[4227,10711,10713],{"className":10712},[3024],[3022,10714,5155],{"className":10715},[4277],[4227,10717],{"className":10718},[3024],[4227,10720,10722],{"className":10721},[3024],[3022,10723,10725],{"className":10724},[4233],"# Тест 3: Валідаційна помилка (400)",[4227,10727,10729,4201,10732],{"className":10728},[3024],[3022,10730,4234],{"className":10731},[4233],[2974,10733,10735],{"className":10734},[4238],"curl -X POST https://localhost:5001/api/products \\",[4227,10737,10680,10739],{"className":10738},[3024],[2974,10740,10742],{"className":10741},[4238],"-H \"X-Api-Key: sk_test_abc123xyz\" \\",[4227,10744,10680,10746],{"className":10745},[3024],[2974,10747,10749],{"className":10748},[4238],"-H \"Content-Type: application/json\" \\",[4227,10751,10680,10753],{"className":10752},[3024],[2974,10754,10756],{"className":10755},[4238],"-d '{\"name\":\"\",\"price\":-10}'",[4227,10758,10760],{"className":10759},[3024],[3022,10761,10763],{"className":10762},[10611,4238],"HTTP/1.1 400 Bad Request",[4227,10765,10767],{"className":10766},[3024],[3022,10768,5150],{"className":10769},[4277],[4227,10771,10680,10773,3522,10777],{"className":10772},[3024],[3022,10774,10776],{"className":10775},[4246],"\"errors\"",[3022,10778,5150],{"className":10779},[4277],[4227,10781,10783,10784,10693,10787,10698],{"className":10782},[3024],"    ",[3022,10785,3548],{"className":10786},[4246],[3022,10788,4581],{"className":10789},[10626],[4227,10791,10783,10793,10693,10797,10800],{"className":10792},[3024],[3022,10794,10796],{"className":10795},[4246],"\"price\"",[3022,10798,4653],{"className":10799},[10626],"]",[4227,10802,10680,10804],{"className":10803},[3024],[3022,10805,5155],{"className":10806},[4277],[4227,10808,10810],{"className":10809},[3024],[3022,10811,5155],{"className":10812},[4277],[3652,10814],{},[2959,10816,10818],{"id":10817},"просунуті-техніки","Просунуті техніки",[3423,10820,10822],{"id":10821},"order-контроль-порядку-виконання","Order — Контроль порядку виконання",[2964,10824,10825],{},"Фільтри виконуються у порядку їх реєстрації, але можна явно вказати порядок:",[3012,10827,10829],{"className":3014,"code":10828,"language":3016,"meta":3017,"style":3017},"public class HighPriorityFilter : IAsyncActionFilter, IOrderedFilter\n{\n    public int Order => -100; // Виконається раніше (менше число = вища пріоритет)\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // ...\n        await next();\n    }\n}\n\npublic class LowPriorityFilter : IAsyncActionFilter, IOrderedFilter\n{\n    public int Order => 100; // Виконається пізніше\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // ...\n        await next();\n    }\n}\n",[3019,10830,10831,10849,10853,10873,10877,10889,10897,10905,10909,10914,10922,10926,10930,10934,10951,10955,10972,10976,10988,10996,11004,11008,11012,11020,11024],{"__ignoreMap":3017},[3022,10832,10833,10835,10837,10840,10842,10844,10846],{"class":3024,"line":3025},[3022,10834,3050],{"class":3049},[3022,10836,3987],{"class":3049},[3022,10838,10839],{"class":3032}," HighPriorityFilter",[3022,10841,3993],{"class":3028},[3022,10843,4197],{"class":3032},[3022,10845,3114],{"class":3028},[3022,10847,10848],{"class":3032},"IOrderedFilter\n",[3022,10850,10851],{"class":3024,"line":3046},[3022,10852,3087],{"class":3028},[3022,10854,10855,10857,10859,10862,10865,10868,10870],{"class":3024,"line":3084},[3022,10856,4005],{"class":3049},[3022,10858,4358],{"class":3049},[3022,10860,10861],{"class":3077}," Order",[3022,10863,10864],{"class":3028}," => -",[3022,10866,10867],{"class":3150},"100",[3022,10869,4369],{"class":3028},[3022,10871,10872],{"class":3093},"// Виконається раніше (менше число = вища пріоритет)\n",[3022,10874,10875],{"class":3024,"line":3090},[3022,10876,4042],{"emptyLinePlaceholder":4041},[3022,10878,10879,10881,10883,10885,10887],{"class":3024,"line":3097},[3022,10880,4005],{"class":3049},[3022,10882,3053],{"class":3049},[3022,10884,3056],{"class":3032},[3022,10886,4116],{"class":3068},[3022,10888,4119],{"class":3028},[3022,10890,10891,10893,10895],{"class":3024,"line":3123},[3022,10892,4124],{"class":3032},[3022,10894,4019],{"class":3077},[3022,10896,3527],{"class":3028},[3022,10898,10899,10901,10903],{"class":3024,"line":3129},[3022,10900,4133],{"class":3032},[3022,10902,4136],{"class":3077},[3022,10904,3081],{"class":3028},[3022,10906,10907],{"class":3024,"line":3135},[3022,10908,4026],{"class":3028},[3022,10910,10911],{"class":3024,"line":3170},[3022,10912,10913],{"class":3093},"        // ...\n",[3022,10915,10916,10918,10920],{"class":3024,"line":3175},[3022,10917,5524],{"class":3049},[3022,10919,4136],{"class":3068},[3022,10921,3200],{"class":3028},[3022,10923,10924],{"class":3024,"line":3181},[3022,10925,4036],{"class":3028},[3022,10927,10928],{"class":3024,"line":3203},[3022,10929,3381],{"class":3028},[3022,10931,10932],{"class":3024,"line":3208},[3022,10933,4042],{"emptyLinePlaceholder":4041},[3022,10935,10936,10938,10940,10943,10945,10947,10949],{"class":3024,"line":3214},[3022,10937,3050],{"class":3049},[3022,10939,3987],{"class":3049},[3022,10941,10942],{"class":3032}," LowPriorityFilter",[3022,10944,3993],{"class":3028},[3022,10946,4197],{"class":3032},[3022,10948,3114],{"class":3028},[3022,10950,10848],{"class":3032},[3022,10952,10953],{"class":3024,"line":3236},[3022,10954,3087],{"class":3028},[3022,10956,10957,10959,10961,10963,10965,10967,10969],{"class":3024,"line":3241},[3022,10958,4005],{"class":3049},[3022,10960,4358],{"class":3049},[3022,10962,10861],{"class":3077},[3022,10964,5886],{"class":3028},[3022,10966,10867],{"class":3150},[3022,10968,4369],{"class":3028},[3022,10970,10971],{"class":3093},"// Виконається пізніше\n",[3022,10973,10974],{"class":3024,"line":3247},[3022,10975,4042],{"emptyLinePlaceholder":4041},[3022,10977,10978,10980,10982,10984,10986],{"class":3024,"line":3279},[3022,10979,4005],{"class":3049},[3022,10981,3053],{"class":3049},[3022,10983,3056],{"class":3032},[3022,10985,4116],{"class":3068},[3022,10987,4119],{"class":3028},[3022,10989,10990,10992,10994],{"class":3024,"line":3284},[3022,10991,4124],{"class":3032},[3022,10993,4019],{"class":3077},[3022,10995,3527],{"class":3028},[3022,10997,10998,11000,11002],{"class":3024,"line":3290},[3022,10999,4133],{"class":3032},[3022,11001,4136],{"class":3077},[3022,11003,3081],{"class":3028},[3022,11005,11006],{"class":3024,"line":3303},[3022,11007,4026],{"class":3028},[3022,11009,11010],{"class":3024,"line":3329},[3022,11011,10913],{"class":3093},[3022,11013,11014,11016,11018],{"class":3024,"line":3334},[3022,11015,5524],{"class":3049},[3022,11017,4136],{"class":3068},[3022,11019,3200],{"class":3028},[3022,11021,11022],{"class":3024,"line":3340},[3022,11023,4036],{"class":3028},[3022,11025,11026],{"class":3024,"line":3378},[3022,11027,3381],{"class":3028},[2964,11029,11030],{},[2974,11031,11032],{},"Порядок виконання:",[3012,11034,11037],{"className":11035,"code":11036,"language":3628},[3626],"HighPriorityFilter (Order: -100)\n  → NormalFilter (Order: 0, за замовчуванням)\n    → LowPriorityFilter (Order: 100)\n      → Action Method\n    ← LowPriorityFilter\n  ← NormalFilter\n← HighPriorityFilter\n",[3019,11038,11036],{"__ignoreMap":3017},[3423,11040,11042],{"id":11041},"short-circuiting-припинення-pipeline","Short-Circuiting — Припинення pipeline",[2964,11044,11045,11046,11049,11050,3437],{},"Фільтр може ",[2974,11047,11048],{},"зупинити виконання"," pipeline, встановивши ",[3019,11051,5581],{},[3012,11053,11055],{"className":3014,"code":11054,"language":3016,"meta":3017,"style":3017},"public class CacheFilter : IAsyncActionFilter\n{\n    private readonly IMemoryCache _cache;\n\n    public CacheFilter(IMemoryCache cache)\n    {\n        _cache = cache;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var cacheKey = context.HttpContext.Request.Path.ToString();\n\n        // Перевіряємо кеш\n        if (_cache.TryGetValue(cacheKey, out object? cachedResult))\n        {\n            // Short-circuit: повертаємо з кешу, action не виконується\n            context.Result = new OkObjectResult(cachedResult);\n            return; // НЕ викликаємо next()\n        }\n\n        // Виконуємо action\n        var resultContext = await next();\n\n        // Кешуємо результат\n        if (resultContext.Result is OkObjectResult okResult)\n        {\n            _cache.Set(cacheKey, okResult.Value, TimeSpan.FromMinutes(5));\n        }\n    }\n}\n",[3019,11056,11057,11070,11074,11088,11092,11108,11112,11124,11128,11132,11144,11152,11160,11164,11193,11197,11202,11234,11238,11243,11264,11273,11277,11281,11285,11299,11303,11308,11330,11334,11374,11378,11382],{"__ignoreMap":3017},[3022,11058,11059,11061,11063,11066,11068],{"class":3024,"line":3025},[3022,11060,3050],{"class":3049},[3022,11062,3987],{"class":3049},[3022,11064,11065],{"class":3032}," CacheFilter",[3022,11067,3993],{"class":3028},[3022,11069,4101],{"class":3032},[3022,11071,11072],{"class":3024,"line":3046},[3022,11073,3087],{"class":3028},[3022,11075,11076,11078,11080,11083,11086],{"class":3024,"line":3084},[3022,11077,4837],{"class":3049},[3022,11079,4859],{"class":3049},[3022,11081,11082],{"class":3032}," IMemoryCache",[3022,11084,11085],{"class":3077}," _cache",[3022,11087,4315],{"class":3028},[3022,11089,11090],{"class":3024,"line":3090},[3022,11091,4042],{"emptyLinePlaceholder":4041},[3022,11093,11094,11096,11098,11100,11103,11106],{"class":3024,"line":3097},[3022,11095,4005],{"class":3049},[3022,11097,11065],{"class":3068},[3022,11099,3036],{"class":3028},[3022,11101,11102],{"class":3032},"IMemoryCache",[3022,11104,11105],{"class":3077}," cache",[3022,11107,3081],{"class":3028},[3022,11109,11110],{"class":3024,"line":3123},[3022,11111,4026],{"class":3028},[3022,11113,11114,11117,11119,11122],{"class":3024,"line":3129},[3022,11115,11116],{"class":3077},"        _cache",[3022,11118,3223],{"class":3028},[3022,11120,11121],{"class":3077},"cache",[3022,11123,4315],{"class":3028},[3022,11125,11126],{"class":3024,"line":3135},[3022,11127,4036],{"class":3028},[3022,11129,11130],{"class":3024,"line":3170},[3022,11131,4042],{"emptyLinePlaceholder":4041},[3022,11133,11134,11136,11138,11140,11142],{"class":3024,"line":3175},[3022,11135,4005],{"class":3049},[3022,11137,3053],{"class":3049},[3022,11139,3056],{"class":3032},[3022,11141,4116],{"class":3068},[3022,11143,4119],{"class":3028},[3022,11145,11146,11148,11150],{"class":3024,"line":3181},[3022,11147,4124],{"class":3032},[3022,11149,4019],{"class":3077},[3022,11151,3527],{"class":3028},[3022,11153,11154,11156,11158],{"class":3024,"line":3203},[3022,11155,4133],{"class":3032},[3022,11157,4136],{"class":3077},[3022,11159,3081],{"class":3028},[3022,11161,11162],{"class":3024,"line":3208},[3022,11163,4026],{"class":3028},[3022,11165,11166,11168,11171,11173,11175,11177,11179,11181,11183,11185,11187,11189,11191],{"class":3024,"line":3214},[3022,11167,4156],{"class":3049},[3022,11169,11170],{"class":3077}," cacheKey",[3022,11172,3223],{"class":3028},[3022,11174,4996],{"class":3077},[3022,11176,3103],{"class":3028},[3022,11178,5001],{"class":3077},[3022,11180,3103],{"class":3028},[3022,11182,5006],{"class":3077},[3022,11184,3103],{"class":3028},[3022,11186,5072],{"class":3077},[3022,11188,3103],{"class":3028},[3022,11190,5298],{"class":3068},[3022,11192,3200],{"class":3028},[3022,11194,11195],{"class":3024,"line":3236},[3022,11196,4042],{"emptyLinePlaceholder":4041},[3022,11198,11199],{"class":3024,"line":3241},[3022,11200,11201],{"class":3093},"        // Перевіряємо кеш\n",[3022,11203,11204,11206,11208,11211,11213,11215,11217,11220,11222,11224,11227,11229,11232],{"class":3024,"line":3247},[3022,11205,4991],{"class":3138},[3022,11207,3142],{"class":3028},[3022,11209,11210],{"class":3077},"_cache",[3022,11212,3103],{"class":3028},[3022,11214,5016],{"class":3068},[3022,11216,3036],{"class":3028},[3022,11218,11219],{"class":3077},"cacheKey",[3022,11221,3114],{"class":3028},[3022,11223,5026],{"class":3049},[3022,11225,11226],{"class":3049}," object",[3022,11228,6940],{"class":3028},[3022,11230,11231],{"class":3077},"cachedResult",[3022,11233,5035],{"class":3028},[3022,11235,11236],{"class":3024,"line":3279},[3022,11237,5040],{"class":3028},[3022,11239,11240],{"class":3024,"line":3284},[3022,11241,11242],{"class":3093},"            // Short-circuit: повертаємо з кешу, action не виконується\n",[3022,11244,11245,11247,11249,11251,11253,11255,11258,11260,11262],{"class":3024,"line":3290},[3022,11246,5084],{"class":3077},[3022,11248,3103],{"class":3028},[3022,11250,3921],{"class":3077},[3022,11252,3223],{"class":3028},[3022,11254,3351],{"class":3049},[3022,11256,11257],{"class":3032}," OkObjectResult",[3022,11259,3036],{"class":3028},[3022,11261,11231],{"class":3077},[3022,11263,3120],{"class":3028},[3022,11265,11266,11268,11270],{"class":3024,"line":3303},[3022,11267,5192],{"class":3138},[3022,11269,4369],{"class":3028},[3022,11271,11272],{"class":3093},"// НЕ викликаємо next()\n",[3022,11274,11275],{"class":3024,"line":3329},[3022,11276,5199],{"class":3028},[3022,11278,11279],{"class":3024,"line":3334},[3022,11280,4042],{"emptyLinePlaceholder":4041},[3022,11282,11283],{"class":3024,"line":3340},[3022,11284,7876],{"class":3093},[3022,11286,11287,11289,11291,11293,11295,11297],{"class":3024,"line":3378},[3022,11288,4156],{"class":3049},[3022,11290,4159],{"class":3077},[3022,11292,3223],{"class":3028},[3022,11294,3257],{"class":3049},[3022,11296,4136],{"class":3068},[3022,11298,3200],{"class":3028},[3022,11300,11301],{"class":3024,"line":3803},[3022,11302,4042],{"emptyLinePlaceholder":4041},[3022,11304,11305],{"class":3024,"line":3809},[3022,11306,11307],{"class":3093},"        // Кешуємо результат\n",[3022,11309,11310,11312,11314,11317,11319,11321,11323,11325,11328],{"class":3024,"line":4632},[3022,11311,4991],{"class":3138},[3022,11313,3142],{"class":3028},[3022,11315,11316],{"class":3077},"resultContext",[3022,11318,3103],{"class":3028},[3022,11320,3921],{"class":3077},[3022,11322,6225],{"class":3049},[3022,11324,11257],{"class":3032},[3022,11326,11327],{"class":3077}," okResult",[3022,11329,3081],{"class":3028},[3022,11331,11332],{"class":3024,"line":4658},[3022,11333,5040],{"class":3028},[3022,11335,11336,11339,11341,11344,11346,11348,11350,11353,11355,11357,11359,11362,11364,11367,11369,11372],{"class":3024,"line":4677},[3022,11337,11338],{"class":3077},"            _cache",[3022,11340,3103],{"class":3028},[3022,11342,11343],{"class":3068},"Set",[3022,11345,3036],{"class":3028},[3022,11347,11219],{"class":3077},[3022,11349,3114],{"class":3028},[3022,11351,11352],{"class":3077},"okResult",[3022,11354,3103],{"class":3028},[3022,11356,5961],{"class":3077},[3022,11358,3114],{"class":3028},[3022,11360,11361],{"class":3077},"TimeSpan",[3022,11363,3103],{"class":3028},[3022,11365,11366],{"class":3068},"FromMinutes",[3022,11368,3036],{"class":3028},[3022,11370,11371],{"class":3150},"5",[3022,11373,8736],{"class":3028},[3022,11375,11376],{"class":3024,"line":4682},[3022,11377,5199],{"class":3028},[3022,11379,11380],{"class":3024,"line":4712},[3022,11381,4036],{"class":3028},[3022,11383,11384],{"class":3024,"line":4731},[3022,11385,3381],{"class":3028},[3423,11387,11389],{"id":11388},"conditional-filters-умовне-застосування","Conditional Filters — Умовне застосування",[2964,11391,11392],{},"Фільтр може вирішувати, чи застосовувати логіку:",[3012,11394,11396],{"className":3014,"code":11395,"language":3016,"meta":3017,"style":3017},"public class ConditionalLoggingFilter : IAsyncActionFilter\n{\n    private readonly ILogger\u003CConditionalLoggingFilter> _logger;\n\n    public ConditionalLoggingFilter(ILogger\u003CConditionalLoggingFilter> logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // Логуємо тільки POST/PUT/DELETE\n        var shouldLog = context.HttpContext.Request.Method != \"GET\";\n\n        if (shouldLog)\n        {\n            _logger.LogInformation(\n                \"Modifying request: {Method} {Path}\",\n                context.HttpContext.Request.Method,\n                context.HttpContext.Request.Path);\n        }\n\n        await next();\n    }\n}\n",[3019,11397,11398,11411,11415,11434,11438,11458,11462,11472,11476,11480,11492,11500,11508,11512,11517,11547,11551,11562,11566,11576,11583,11601,11619,11623,11627,11635,11639],{"__ignoreMap":3017},[3022,11399,11400,11402,11404,11407,11409],{"class":3024,"line":3025},[3022,11401,3050],{"class":3049},[3022,11403,3987],{"class":3049},[3022,11405,11406],{"class":3032}," ConditionalLoggingFilter",[3022,11408,3993],{"class":3028},[3022,11410,4101],{"class":3032},[3022,11412,11413],{"class":3024,"line":3046},[3022,11414,3087],{"class":3028},[3022,11416,11417,11419,11421,11423,11425,11428,11430,11432],{"class":3024,"line":3084},[3022,11418,4837],{"class":3049},[3022,11420,4859],{"class":3049},[3022,11422,4876],{"class":3032},[3022,11424,3059],{"class":3028},[3022,11426,11427],{"class":3032},"ConditionalLoggingFilter",[3022,11429,3065],{"class":3028},[3022,11431,4886],{"class":3077},[3022,11433,4315],{"class":3028},[3022,11435,11436],{"class":3024,"line":3090},[3022,11437,4042],{"emptyLinePlaceholder":4041},[3022,11439,11440,11442,11444,11446,11448,11450,11452,11454,11456],{"class":3024,"line":3097},[3022,11441,4005],{"class":3049},[3022,11443,11406],{"class":3068},[3022,11445,3036],{"class":3028},[3022,11447,4911],{"class":3032},[3022,11449,3059],{"class":3028},[3022,11451,11427],{"class":3032},[3022,11453,3065],{"class":3028},[3022,11455,4920],{"class":3077},[3022,11457,3081],{"class":3028},[3022,11459,11460],{"class":3024,"line":3123},[3022,11461,4026],{"class":3028},[3022,11463,11464,11466,11468,11470],{"class":3024,"line":3129},[3022,11465,4943],{"class":3077},[3022,11467,3223],{"class":3028},[3022,11469,4920],{"class":3077},[3022,11471,4315],{"class":3028},[3022,11473,11474],{"class":3024,"line":3135},[3022,11475,4036],{"class":3028},[3022,11477,11478],{"class":3024,"line":3170},[3022,11479,4042],{"emptyLinePlaceholder":4041},[3022,11481,11482,11484,11486,11488,11490],{"class":3024,"line":3175},[3022,11483,4005],{"class":3049},[3022,11485,3053],{"class":3049},[3022,11487,3056],{"class":3032},[3022,11489,4116],{"class":3068},[3022,11491,4119],{"class":3028},[3022,11493,11494,11496,11498],{"class":3024,"line":3181},[3022,11495,4124],{"class":3032},[3022,11497,4019],{"class":3077},[3022,11499,3527],{"class":3028},[3022,11501,11502,11504,11506],{"class":3024,"line":3203},[3022,11503,4133],{"class":3032},[3022,11505,4136],{"class":3077},[3022,11507,3081],{"class":3028},[3022,11509,11510],{"class":3024,"line":3208},[3022,11511,4026],{"class":3028},[3022,11513,11514],{"class":3024,"line":3214},[3022,11515,11516],{"class":3093},"        // Логуємо тільки POST/PUT/DELETE\n",[3022,11518,11519,11521,11524,11526,11528,11530,11532,11534,11536,11538,11540,11542,11545],{"class":3024,"line":3236},[3022,11520,4156],{"class":3049},[3022,11522,11523],{"class":3077}," shouldLog",[3022,11525,3223],{"class":3028},[3022,11527,4996],{"class":3077},[3022,11529,3103],{"class":3028},[3022,11531,5001],{"class":3077},[3022,11533,3103],{"class":3028},[3022,11535,5006],{"class":3077},[3022,11537,3103],{"class":3028},[3022,11539,7798],{"class":3077},[3022,11541,6266],{"class":3028},[3022,11543,11544],{"class":3039},"\"GET\"",[3022,11546,4315],{"class":3028},[3022,11548,11549],{"class":3024,"line":3241},[3022,11550,4042],{"emptyLinePlaceholder":4041},[3022,11552,11553,11555,11557,11560],{"class":3024,"line":3247},[3022,11554,4991],{"class":3138},[3022,11556,3142],{"class":3028},[3022,11558,11559],{"class":3077},"shouldLog",[3022,11561,3081],{"class":3028},[3022,11563,11564],{"class":3024,"line":3279},[3022,11565,5040],{"class":3028},[3022,11567,11568,11570,11572,11574],{"class":3024,"line":3284},[3022,11569,5045],{"class":3077},[3022,11571,3103],{"class":3028},[3022,11573,3106],{"class":3068},[3022,11575,4119],{"class":3028},[3022,11577,11578,11581],{"class":3024,"line":3290},[3022,11579,11580],{"class":3039},"                \"Modifying request: {Method} {Path}\"",[3022,11582,3527],{"class":3028},[3022,11584,11585,11587,11589,11591,11593,11595,11597,11599],{"class":3024,"line":3303},[3022,11586,5830],{"class":3077},[3022,11588,3103],{"class":3028},[3022,11590,5001],{"class":3077},[3022,11592,3103],{"class":3028},[3022,11594,5006],{"class":3077},[3022,11596,3103],{"class":3028},[3022,11598,7798],{"class":3077},[3022,11600,3527],{"class":3028},[3022,11602,11603,11605,11607,11609,11611,11613,11615,11617],{"class":3024,"line":3329},[3022,11604,5830],{"class":3077},[3022,11606,3103],{"class":3028},[3022,11608,5001],{"class":3077},[3022,11610,3103],{"class":3028},[3022,11612,5006],{"class":3077},[3022,11614,3103],{"class":3028},[3022,11616,5072],{"class":3077},[3022,11618,3120],{"class":3028},[3022,11620,11621],{"class":3024,"line":3334},[3022,11622,5199],{"class":3028},[3022,11624,11625],{"class":3024,"line":3340},[3022,11626,4042],{"emptyLinePlaceholder":4041},[3022,11628,11629,11631,11633],{"class":3024,"line":3378},[3022,11630,5524],{"class":3049},[3022,11632,4136],{"class":3068},[3022,11634,3200],{"class":3028},[3022,11636,11637],{"class":3024,"line":3803},[3022,11638,4036],{"class":3028},[3022,11640,11641],{"class":3024,"line":3809},[3022,11642,3381],{"class":3028},[3423,11644,11646],{"id":11645},"attribute-based-configuration","Attribute-Based Configuration",[2964,11648,11649],{},"Створення кастомного атрибута для конфігурації фільтра:",[3012,11651,11653],{"className":3014,"code":11652,"language":3016,"meta":3017,"style":3017},"[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]\npublic class RequireApiKeyAttribute : Attribute, IFilterFactory\n{\n    public bool IsReusable => false;\n\n    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)\n    {\n        return serviceProvider.GetRequiredService\u003CApiKeyAuthFilter>();\n    }\n}\n\n// Використання\n[RequireApiKey] // Замість [ServiceFilter(typeof(ApiKeyAuthFilter))]\npublic class ProductsController : ControllerBase\n{\n    // ...\n}\n",[3019,11654,11655,11683,11702,11706,11722,11726,11746,11750,11766,11770,11774,11778,11783,11796,11808,11812,11816],{"__ignoreMap":3017},[3022,11656,11657,11659,11662,11664,11667,11669,11672,11675,11677,11679,11681],{"class":3024,"line":3025},[3022,11658,3029],{"class":3028},[3022,11660,11661],{"class":3032},"AttributeUsage",[3022,11663,3036],{"class":3028},[3022,11665,11666],{"class":3077},"AttributeTargets",[3022,11668,3103],{"class":3028},[3022,11670,11671],{"class":3077},"Class",[3022,11673,11674],{"class":3028}," | ",[3022,11676,11666],{"class":3077},[3022,11678,3103],{"class":3028},[3022,11680,7798],{"class":3077},[3022,11682,3043],{"class":3028},[3022,11684,11685,11687,11689,11692,11694,11697,11699],{"class":3024,"line":3046},[3022,11686,3050],{"class":3049},[3022,11688,3987],{"class":3049},[3022,11690,11691],{"class":3032}," RequireApiKeyAttribute",[3022,11693,3993],{"class":3028},[3022,11695,11696],{"class":3032},"Attribute",[3022,11698,3114],{"class":3028},[3022,11700,11701],{"class":3032},"IFilterFactory\n",[3022,11703,11704],{"class":3024,"line":3084},[3022,11705,3087],{"class":3028},[3022,11707,11708,11710,11712,11715,11717,11720],{"class":3024,"line":3090},[3022,11709,4005],{"class":3049},[3022,11711,4525],{"class":3049},[3022,11713,11714],{"class":3077}," IsReusable",[3022,11716,5886],{"class":3028},[3022,11718,11719],{"class":3049},"false",[3022,11721,4315],{"class":3028},[3022,11723,11724],{"class":3024,"line":3097},[3022,11725,4042],{"emptyLinePlaceholder":4041},[3022,11727,11728,11730,11733,11736,11738,11741,11744],{"class":3024,"line":3123},[3022,11729,4005],{"class":3049},[3022,11731,11732],{"class":3032}," IFilterMetadata",[3022,11734,11735],{"class":3068}," CreateInstance",[3022,11737,3036],{"class":3028},[3022,11739,11740],{"class":3032},"IServiceProvider",[3022,11742,11743],{"class":3077}," serviceProvider",[3022,11745,3081],{"class":3028},[3022,11747,11748],{"class":3024,"line":3129},[3022,11749,4026],{"class":3028},[3022,11751,11752,11754,11756,11758,11760,11762,11764],{"class":3024,"line":3135},[3022,11753,9795],{"class":3138},[3022,11755,11743],{"class":3077},[3022,11757,3103],{"class":3028},[3022,11759,9082],{"class":3068},[3022,11761,3059],{"class":3028},[3022,11763,4881],{"class":3032},[3022,11765,5262],{"class":3028},[3022,11767,11768],{"class":3024,"line":3170},[3022,11769,4036],{"class":3028},[3022,11771,11772],{"class":3024,"line":3175},[3022,11773,3381],{"class":3028},[3022,11775,11776],{"class":3024,"line":3181},[3022,11777,4042],{"emptyLinePlaceholder":4041},[3022,11779,11780],{"class":3024,"line":3203},[3022,11781,11782],{"class":3093},"// Використання\n",[3022,11784,11785,11787,11790,11793],{"class":3024,"line":3208},[3022,11786,3029],{"class":3028},[3022,11788,11789],{"class":3032},"RequireApiKey",[3022,11791,11792],{"class":3028},"] ",[3022,11794,11795],{"class":3093},"// Замість [ServiceFilter(typeof(ApiKeyAuthFilter))]\n",[3022,11797,11798,11800,11802,11804,11806],{"class":3024,"line":3214},[3022,11799,3050],{"class":3049},[3022,11801,3987],{"class":3049},[3022,11803,8492],{"class":3032},[3022,11805,3993],{"class":3028},[3022,11807,8497],{"class":3032},[3022,11809,11810],{"class":3024,"line":3236},[3022,11811,3087],{"class":3028},[3022,11813,11814],{"class":3024,"line":3241},[3022,11815,8506],{"class":3093},[3022,11817,11818],{"class":3024,"line":3247},[3022,11819,3381],{"class":3028},[3652,11821],{},[2959,11823,11825],{"id":11824},"практичні-завдання","Практичні завдання",[3423,11827,11829],{"id":11828},"рівень-1-базове-розуміння","Рівень 1: Базове розуміння",[4216,11831,11832,11836,11839,11910,11945,11949,11952,11966],{},[3423,11833,11835],{"id":11834},"завдання-11-порядок-виконання","Завдання 1.1: Порядок виконання",[2964,11837,11838],{},"У якому порядку виконаються наступні фільтри?",[3012,11840,11842],{"className":3014,"code":11841,"language":3016,"meta":3017,"style":3017},"options.Filters.Add\u003CFilterA>(); // Order: 0\noptions.Filters.Add\u003CFilterB>(); // Order: -50\noptions.Filters.Add\u003CFilterC>(); // Order: 100\n",[3019,11843,11844,11866,11888],{"__ignoreMap":3017},[3022,11845,11846,11848,11850,11852,11854,11856,11858,11861,11863],{"class":3024,"line":3025},[3022,11847,8236],{"class":3077},[3022,11849,3103],{"class":3028},[3022,11851,4792],{"class":3077},[3022,11853,3103],{"class":3028},[3022,11855,8262],{"class":3068},[3022,11857,3059],{"class":3028},[3022,11859,11860],{"class":3032},"FilterA",[3022,11862,8912],{"class":3028},[3022,11864,11865],{"class":3093},"// Order: 0\n",[3022,11867,11868,11870,11872,11874,11876,11878,11880,11883,11885],{"class":3024,"line":3046},[3022,11869,8236],{"class":3077},[3022,11871,3103],{"class":3028},[3022,11873,4792],{"class":3077},[3022,11875,3103],{"class":3028},[3022,11877,8262],{"class":3068},[3022,11879,3059],{"class":3028},[3022,11881,11882],{"class":3032},"FilterB",[3022,11884,8912],{"class":3028},[3022,11886,11887],{"class":3093},"// Order: -50\n",[3022,11889,11890,11892,11894,11896,11898,11900,11902,11905,11907],{"class":3024,"line":3084},[3022,11891,8236],{"class":3077},[3022,11893,3103],{"class":3028},[3022,11895,4792],{"class":3077},[3022,11897,3103],{"class":3028},[3022,11899,8262],{"class":3068},[3022,11901,3059],{"class":3028},[3022,11903,11904],{"class":3032},"FilterC",[3022,11906,8912],{"class":3028},[3022,11908,11909],{"class":3093},"// Order: 100\n",[11911,11912,11914,11919,11942],"collapsible",{"title":11913},"Показати відповідь",[2964,11915,11916],{},[2974,11917,11918],{},"Порядок виконання (до action):",[5550,11920,11921,11924,11927,11930,11933,11936,11939],{},[2971,11922,11923],{},"FilterB (Order: -50)",[2971,11925,11926],{},"FilterA (Order: 0)",[2971,11928,11929],{},"FilterC (Order: 100)",[2971,11931,11932],{},"Action Method",[2971,11934,11935],{},"FilterC (після action)",[2971,11937,11938],{},"FilterA (після action)",[2971,11940,11941],{},"FilterB (після action)",[2964,11943,11944],{},"Фільтри з меншим Order виконуються раніше.",[3423,11946,11948],{"id":11947},"завдання-12-вибір-типу-фільтра","Завдання 1.2: Вибір типу фільтра",[2964,11950,11951],{},"Який тип фільтра використати для кожного сценарію?",[5550,11953,11954,11957,11960,11963],{},[2971,11955,11956],{},"Валідація API-ключа",[2971,11958,11959],{},"Логування часу виконання",[2971,11961,11962],{},"Обгортання відповіді у envelope",[2971,11964,11965],{},"Перевірка прав доступу до ресурсу",[11911,11967,11969],{"title":11968},"Показати відповіді",[5550,11970,11971,11979,11987,11995],{},[2971,11972,11973,3142,11976,11978],{},[2974,11974,11975],{},"Authorization Filter",[3019,11977,3854],{},") — виконується першим",[2971,11980,11981,3142,11984,11986],{},[2974,11982,11983],{},"Action Filter",[3019,11985,3890],{},") — до/після action",[2971,11988,11989,3142,11992,11994],{},[2974,11990,11991],{},"Result Filter",[3019,11993,3926],{},") — модифікація результату",[2971,11996,11997,3142,12000,12002],{},[2974,11998,11999],{},"Resource Filter",[3019,12001,3872],{},") — після авторизації, до model binding",[3652,12004],{},[3423,12006,12008],{"id":12007},"рівень-2-логіка-та-розширення","Рівень 2: Логіка та розширення",[4216,12010,12011,12015,12018],{},[3423,12012,12014],{"id":12013},"завдання-21-rate-limiting-filter","Завдання 2.1: Rate Limiting Filter",[2964,12016,12017],{},"Створіть фільтр, що обмежує кількість запитів до 10 на хвилину для кожного API-ключа:",[11911,12019,12021,12693],{"title":12020},"Показати рішення",[3012,12022,12024],{"className":3014,"code":12023,"language":3016,"meta":3017,"style":3017},"public class RateLimitingFilter : IAsyncActionFilter\n{\n    private static readonly Dictionary\u003Cstring, Queue\u003CDateTime>> _requestHistory = new();\n    private static readonly object _lock = new();\n    private const int MaxRequestsPerMinute = 10;\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var apiKey = context.HttpContext.Items[\"ApiKey\"]?.ToString();\n        \n        if (string.IsNullOrEmpty(apiKey))\n        {\n            await next();\n            return;\n        }\n\n        lock (_lock)\n        {\n            if (!_requestHistory.ContainsKey(apiKey))\n            {\n                _requestHistory[apiKey] = new Queue\u003CDateTime>();\n            }\n\n            var history = _requestHistory[apiKey];\n            var now = DateTime.UtcNow;\n            var oneMinuteAgo = now.AddMinutes(-1);\n\n            // Видаляємо старі записи\n            while (history.Count > 0 && history.Peek() \u003C oneMinuteAgo)\n            {\n                history.Dequeue();\n            }\n\n            if (history.Count >= MaxRequestsPerMinute)\n            {\n                var retryAfter = (int)(history.Peek().AddMinutes(1) - now).TotalSeconds;\n                \n                context.HttpContext.Response.Headers.Append(\"Retry-After\", retryAfter.ToString());\n                \n                context.Result = new ObjectResult(new ProblemDetails\n                {\n                    Status = StatusCodes.Status429TooManyRequests,\n                    Title = \"Rate Limit Exceeded\",\n                    Detail = $\"Maximum {MaxRequestsPerMinute} requests per minute allowed\"\n                })\n                {\n                    StatusCode = StatusCodes.Status429TooManyRequests\n                };\n                return;\n            }\n\n            history.Enqueue(now);\n        }\n\n        await next();\n    }\n}\n",[3019,12025,12026,12039,12043,12079,12098,12116,12120,12132,12140,12148,12152,12181,12185,12205,12209,12218,12224,12228,12232,12244,12248,12267,12271,12293,12297,12301,12319,12336,12360,12364,12369,12405,12409,12421,12425,12429,12448,12452,12494,12499,12535,12539,12559,12563,12579,12591,12610,12615,12619,12633,12638,12645,12649,12653,12669,12673,12677,12685,12689],{"__ignoreMap":3017},[3022,12027,12028,12030,12032,12035,12037],{"class":3024,"line":3025},[3022,12029,3050],{"class":3049},[3022,12031,3987],{"class":3049},[3022,12033,12034],{"class":3032}," RateLimitingFilter",[3022,12036,3993],{"class":3028},[3022,12038,4101],{"class":3032},[3022,12040,12041],{"class":3024,"line":3046},[3022,12042,3087],{"class":3028},[3022,12044,12045,12047,12050,12052,12055,12057,12059,12061,12064,12066,12068,12070,12073,12075,12077],{"class":3024,"line":3084},[3022,12046,4837],{"class":3049},[3022,12048,12049],{"class":3049}," static",[3022,12051,4859],{"class":3049},[3022,12053,12054],{"class":3032}," Dictionary",[3022,12056,3059],{"class":3028},[3022,12058,5244],{"class":3049},[3022,12060,3114],{"class":3028},[3022,12062,12063],{"class":3032},"Queue",[3022,12065,3059],{"class":3028},[3022,12067,6764],{"class":3032},[3022,12069,9915],{"class":3028},[3022,12071,12072],{"class":3077},"_requestHistory",[3022,12074,3223],{"class":3028},[3022,12076,3351],{"class":3049},[3022,12078,3200],{"class":3028},[3022,12080,12081,12083,12085,12087,12089,12092,12094,12096],{"class":3024,"line":3090},[3022,12082,4837],{"class":3049},[3022,12084,12049],{"class":3049},[3022,12086,4859],{"class":3049},[3022,12088,11226],{"class":3049},[3022,12090,12091],{"class":3077}," _lock",[3022,12093,3223],{"class":3028},[3022,12095,3351],{"class":3049},[3022,12097,3200],{"class":3028},[3022,12099,12100,12102,12104,12106,12109,12111,12114],{"class":3024,"line":3097},[3022,12101,4837],{"class":3049},[3022,12103,4840],{"class":3049},[3022,12105,4358],{"class":3049},[3022,12107,12108],{"class":3077}," MaxRequestsPerMinute",[3022,12110,3223],{"class":3028},[3022,12112,12113],{"class":3150},"10",[3022,12115,4315],{"class":3028},[3022,12117,12118],{"class":3024,"line":3123},[3022,12119,4042],{"emptyLinePlaceholder":4041},[3022,12121,12122,12124,12126,12128,12130],{"class":3024,"line":3129},[3022,12123,4005],{"class":3049},[3022,12125,3053],{"class":3049},[3022,12127,3056],{"class":3032},[3022,12129,4116],{"class":3068},[3022,12131,4119],{"class":3028},[3022,12133,12134,12136,12138],{"class":3024,"line":3135},[3022,12135,4124],{"class":3032},[3022,12137,4019],{"class":3077},[3022,12139,3527],{"class":3028},[3022,12141,12142,12144,12146],{"class":3024,"line":3170},[3022,12143,4133],{"class":3032},[3022,12145,4136],{"class":3077},[3022,12147,3081],{"class":3028},[3022,12149,12150],{"class":3024,"line":3175},[3022,12151,4026],{"class":3028},[3022,12153,12154,12156,12159,12161,12163,12165,12167,12169,12171,12173,12175,12177,12179],{"class":3024,"line":3181},[3022,12155,4156],{"class":3049},[3022,12157,12158],{"class":3077}," apiKey",[3022,12160,3223],{"class":3028},[3022,12162,4996],{"class":3077},[3022,12164,3103],{"class":3028},[3022,12166,5001],{"class":3077},[3022,12168,3103],{"class":3028},[3022,12170,5497],{"class":3077},[3022,12172,3029],{"class":3028},[3022,12174,5502],{"class":3039},[3022,12176,9711],{"class":3028},[3022,12178,5298],{"class":3068},[3022,12180,3200],{"class":3028},[3022,12182,12183],{"class":3024,"line":3203},[3022,12184,4151],{"class":3028},[3022,12186,12187,12189,12191,12193,12195,12198,12200,12203],{"class":3024,"line":3208},[3022,12188,4991],{"class":3138},[3022,12190,3142],{"class":3028},[3022,12192,5244],{"class":3049},[3022,12194,3103],{"class":3028},[3022,12196,12197],{"class":3068},"IsNullOrEmpty",[3022,12199,3036],{"class":3028},[3022,12201,12202],{"class":3077},"apiKey",[3022,12204,5035],{"class":3028},[3022,12206,12207],{"class":3024,"line":3214},[3022,12208,5040],{"class":3028},[3022,12210,12211,12214,12216],{"class":3024,"line":3236},[3022,12212,12213],{"class":3049},"            await",[3022,12215,4136],{"class":3068},[3022,12217,3200],{"class":3028},[3022,12219,12220,12222],{"class":3024,"line":3241},[3022,12221,5192],{"class":3138},[3022,12223,4315],{"class":3028},[3022,12225,12226],{"class":3024,"line":3247},[3022,12227,5199],{"class":3028},[3022,12229,12230],{"class":3024,"line":3279},[3022,12231,4042],{"emptyLinePlaceholder":4041},[3022,12233,12234,12237,12239,12242],{"class":3024,"line":3284},[3022,12235,12236],{"class":3138},"        lock",[3022,12238,3142],{"class":3028},[3022,12240,12241],{"class":3077},"_lock",[3022,12243,3081],{"class":3028},[3022,12245,12246],{"class":3024,"line":3290},[3022,12247,5040],{"class":3028},[3022,12249,12250,12252,12254,12256,12258,12261,12263,12265],{"class":3024,"line":3303},[3022,12251,6217],{"class":3138},[3022,12253,3186],{"class":3028},[3022,12255,12072],{"class":3077},[3022,12257,3103],{"class":3028},[3022,12259,12260],{"class":3068},"ContainsKey",[3022,12262,3036],{"class":3028},[3022,12264,12202],{"class":3077},[3022,12266,5035],{"class":3028},[3022,12268,12269],{"class":3024,"line":3329},[3022,12270,5107],{"class":3028},[3022,12272,12273,12276,12278,12280,12282,12284,12287,12289,12291],{"class":3024,"line":3334},[3022,12274,12275],{"class":3077},"                _requestHistory",[3022,12277,3029],{"class":3028},[3022,12279,12202],{"class":3077},[3022,12281,5505],{"class":3028},[3022,12283,3351],{"class":3049},[3022,12285,12286],{"class":3032}," Queue",[3022,12288,3059],{"class":3028},[3022,12290,6764],{"class":3032},[3022,12292,5262],{"class":3028},[3022,12294,12295],{"class":3024,"line":3340},[3022,12296,6387],{"class":3028},[3022,12298,12299],{"class":3024,"line":3378},[3022,12300,4042],{"emptyLinePlaceholder":4041},[3022,12302,12303,12305,12308,12310,12312,12314,12316],{"class":3024,"line":3803},[3022,12304,5927],{"class":3049},[3022,12306,12307],{"class":3077}," history",[3022,12309,3223],{"class":3028},[3022,12311,12072],{"class":3077},[3022,12313,3029],{"class":3028},[3022,12315,12202],{"class":3077},[3022,12317,12318],{"class":3028},"];\n",[3022,12320,12321,12323,12326,12328,12330,12332,12334],{"class":3024,"line":3809},[3022,12322,5927],{"class":3049},[3022,12324,12325],{"class":3077}," now",[3022,12327,3223],{"class":3028},[3022,12329,6764],{"class":3077},[3022,12331,3103],{"class":3028},[3022,12333,6769],{"class":3077},[3022,12335,4315],{"class":3028},[3022,12337,12338,12340,12343,12345,12348,12350,12353,12356,12358],{"class":3024,"line":4632},[3022,12339,5927],{"class":3049},[3022,12341,12342],{"class":3077}," oneMinuteAgo",[3022,12344,3223],{"class":3028},[3022,12346,12347],{"class":3077},"now",[3022,12349,3103],{"class":3028},[3022,12351,12352],{"class":3068},"AddMinutes",[3022,12354,12355],{"class":3028},"(-",[3022,12357,3543],{"class":3150},[3022,12359,3120],{"class":3028},[3022,12361,12362],{"class":3024,"line":4658},[3022,12363,4042],{"emptyLinePlaceholder":4041},[3022,12365,12366],{"class":3024,"line":4677},[3022,12367,12368],{"class":3093},"            // Видаляємо старі записи\n",[3022,12370,12371,12374,12376,12379,12381,12383,12385,12387,12390,12392,12394,12397,12400,12403],{"class":3024,"line":4682},[3022,12372,12373],{"class":3138},"            while",[3022,12375,3142],{"class":3028},[3022,12377,12378],{"class":3077},"history",[3022,12380,3103],{"class":3028},[3022,12382,5971],{"class":3077},[3022,12384,5974],{"class":3028},[3022,12386,3151],{"class":3150},[3022,12388,12389],{"class":3028}," && ",[3022,12391,12378],{"class":3077},[3022,12393,3103],{"class":3028},[3022,12395,12396],{"class":3068},"Peek",[3022,12398,12399],{"class":3028},"() \u003C ",[3022,12401,12402],{"class":3077},"oneMinuteAgo",[3022,12404,3081],{"class":3028},[3022,12406,12407],{"class":3024,"line":4712},[3022,12408,5107],{"class":3028},[3022,12410,12411,12414,12416,12419],{"class":3024,"line":4731},[3022,12412,12413],{"class":3077},"                history",[3022,12415,3103],{"class":3028},[3022,12417,12418],{"class":3068},"Dequeue",[3022,12420,3200],{"class":3028},[3022,12422,12423],{"class":3024,"line":5202},[3022,12424,6387],{"class":3028},[3022,12426,12427],{"class":3024,"line":5207},[3022,12428,4042],{"emptyLinePlaceholder":4041},[3022,12430,12431,12433,12435,12437,12439,12441,12443,12446],{"class":3024,"line":5213},[3022,12432,6217],{"class":3138},[3022,12434,3142],{"class":3028},[3022,12436,12378],{"class":3077},[3022,12438,3103],{"class":3028},[3022,12440,5971],{"class":3077},[3022,12442,6661],{"class":3028},[3022,12444,12445],{"class":3077},"MaxRequestsPerMinute",[3022,12447,3081],{"class":3028},[3022,12449,12450],{"class":3024,"line":5265},[3022,12451,5107],{"class":3028},[3022,12453,12454,12457,12460,12463,12465,12468,12470,12472,12474,12476,12478,12480,12482,12485,12487,12489,12492],{"class":3024,"line":5270},[3022,12455,12456],{"class":3049},"                var",[3022,12458,12459],{"class":3077}," retryAfter",[3022,12461,12462],{"class":3028}," = (",[3022,12464,3074],{"class":3049},[3022,12466,12467],{"class":3028},")(",[3022,12469,12378],{"class":3077},[3022,12471,3103],{"class":3028},[3022,12473,12396],{"class":3068},[3022,12475,7368],{"class":3028},[3022,12477,12352],{"class":3068},[3022,12479,3036],{"class":3028},[3022,12481,3543],{"class":3150},[3022,12483,12484],{"class":3028},") - ",[3022,12486,12347],{"class":3077},[3022,12488,5236],{"class":3028},[3022,12490,12491],{"class":3077},"TotalSeconds",[3022,12493,4315],{"class":3028},[3022,12495,12496],{"class":3024,"line":5276},[3022,12497,12498],{"class":3028},"                \n",[3022,12500,12501,12503,12505,12507,12509,12511,12513,12515,12517,12519,12521,12524,12526,12529,12531,12533],{"class":3024,"line":5304},[3022,12502,5830],{"class":3077},[3022,12504,3103],{"class":3028},[3022,12506,5001],{"class":3077},[3022,12508,3103],{"class":3028},[3022,12510,7394],{"class":3077},[3022,12512,3103],{"class":3028},[3022,12514,5011],{"class":3077},[3022,12516,3103],{"class":3028},[3022,12518,7403],{"class":3068},[3022,12520,3036],{"class":3028},[3022,12522,12523],{"class":3039},"\"Retry-After\"",[3022,12525,3114],{"class":3028},[3022,12527,12528],{"class":3077},"retryAfter",[3022,12530,3103],{"class":3028},[3022,12532,5298],{"class":3068},[3022,12534,7968],{"class":3028},[3022,12536,12537],{"class":3024,"line":5309},[3022,12538,12498],{"class":3028},[3022,12540,12541,12543,12545,12547,12549,12551,12553,12555,12557],{"class":3024,"line":5329},[3022,12542,5830],{"class":3077},[3022,12544,3103],{"class":3028},[3022,12546,3921],{"class":3077},[3022,12548,3223],{"class":3028},[3022,12550,3351],{"class":3049},[3022,12552,6642],{"class":3032},[3022,12554,3036],{"class":3028},[3022,12556,3351],{"class":3049},[3022,12558,5102],{"class":3032},[3022,12560,12561],{"class":3024,"line":5334},[3022,12562,6275],{"class":3028},[3022,12564,12565,12568,12570,12572,12574,12577],{"class":3024,"line":5355},[3022,12566,12567],{"class":3077},"                    Status",[3022,12569,3223],{"class":3028},[3022,12571,5117],{"class":3077},[3022,12573,3103],{"class":3028},[3022,12575,12576],{"class":3077},"Status429TooManyRequests",[3022,12578,3527],{"class":3028},[3022,12580,12581,12584,12586,12589],{"class":3024,"line":5360},[3022,12582,12583],{"class":3077},"                    Title",[3022,12585,3223],{"class":3028},[3022,12587,12588],{"class":3039},"\"Rate Limit Exceeded\"",[3022,12590,3527],{"class":3028},[3022,12592,12593,12596,12598,12601,12603,12605,12607],{"class":3024,"line":5375},[3022,12594,12595],{"class":3077},"                    Detail",[3022,12597,3223],{"class":3028},[3022,12599,12600],{"class":3039},"$\"Maximum ",[3022,12602,5150],{"class":5149},[3022,12604,12445],{"class":3077},[3022,12606,5155],{"class":5149},[3022,12608,12609],{"class":3039}," requests per minute allowed\"\n",[3022,12611,12612],{"class":3024,"line":5387},[3022,12613,12614],{"class":3028},"                })\n",[3022,12616,12617],{"class":3024,"line":5399},[3022,12618,6275],{"class":3028},[3022,12620,12621,12624,12626,12628,12630],{"class":3024,"line":5420},[3022,12622,12623],{"class":3077},"                    StatusCode",[3022,12625,3223],{"class":3028},[3022,12627,5117],{"class":3077},[3022,12629,3103],{"class":3028},[3022,12631,12632],{"class":3077},"Status429TooManyRequests\n",[3022,12634,12635],{"class":3024,"line":5425},[3022,12636,12637],{"class":3028},"                };\n",[3022,12639,12640,12643],{"class":3024,"line":5432},[3022,12641,12642],{"class":3138},"                return",[3022,12644,4315],{"class":3028},[3022,12646,12647],{"class":3024,"line":5437},[3022,12648,6387],{"class":3028},[3022,12650,12651],{"class":3024,"line":5442},[3022,12652,4042],{"emptyLinePlaceholder":4041},[3022,12654,12655,12658,12660,12663,12665,12667],{"class":3024,"line":5474},[3022,12656,12657],{"class":3077},"            history",[3022,12659,3103],{"class":3028},[3022,12661,12662],{"class":3068},"Enqueue",[3022,12664,3036],{"class":3028},[3022,12666,12347],{"class":3077},[3022,12668,3120],{"class":3028},[3022,12670,12671],{"class":3024,"line":5479},[3022,12672,5199],{"class":3028},[3022,12674,12675],{"class":3024,"line":5485},[3022,12676,4042],{"emptyLinePlaceholder":4041},[3022,12678,12679,12681,12683],{"class":3024,"line":5516},[3022,12680,5524],{"class":3049},[3022,12682,4136],{"class":3068},[3022,12684,3200],{"class":3028},[3022,12686,12687],{"class":3024,"line":5521},[3022,12688,4036],{"class":3028},[3022,12690,12691],{"class":3024,"line":5536},[3022,12692,3381],{"class":3028},[3416,12694,12695],{},"У production використовуйте Redis або спеціалізовані бібліотеки (AspNetCoreRateLimit) замість in-memory словника.",[2964,12697,12698],{},"::",[2964,12700,12698],{},[3423,12702,12704],{"id":12703},"завдання-22-requestresponse-logging-filter","Завдання 2.2: Request/Response Logging Filter",[2964,12706,12707],{},"Створіть фільтр, що логує тіло запиту та відповіді для аудиту:",[11911,12709,12710,13407,13411,13441],{"title":12020},[3012,12711,12713],{"className":3014,"code":12712,"language":3016,"meta":3017,"style":3017},"public class RequestResponseLoggingFilter : IAsyncActionFilter\n{\n    private readonly ILogger\u003CRequestResponseLoggingFilter> _logger;\n\n    public RequestResponseLoggingFilter(ILogger\u003CRequestResponseLoggingFilter> logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var correlationId = context.HttpContext.Items[\"CorrelationId\"]?.ToString() ?? \"N/A\";\n        var method = context.HttpContext.Request.Method;\n        var path = context.HttpContext.Request.Path;\n\n        // Логуємо запит\n        var requestBody = await ReadRequestBody(context.HttpContext.Request);\n        _logger.LogInformation(\n            \"[{CorrelationId}] Request: {Method} {Path} | Body: {Body}\",\n            correlationId,\n            method,\n            path,\n            requestBody);\n\n        // Виконуємо action\n        var resultContext = await next();\n\n        // Логуємо відповідь\n        if (resultContext.Result is ObjectResult objectResult)\n        {\n            var responseBody = System.Text.Json.JsonSerializer.Serialize(objectResult.Value);\n            _logger.LogInformation(\n                \"[{CorrelationId}] Response: {StatusCode} | Body: {Body}\",\n                correlationId,\n                objectResult.StatusCode ?? 200,\n                responseBody);\n        }\n    }\n\n    private async Task\u003Cstring> ReadRequestBody(HttpRequest request)\n    {\n        if (request.ContentLength == null || request.ContentLength == 0)\n            return \"empty\";\n\n        request.EnableBuffering(); // Дозволяє читати Body кілька разів\n        \n        using var reader = new StreamReader(\n            request.Body,\n            encoding: System.Text.Encoding.UTF8,\n            detectEncodingFromByteOrderMarks: false,\n            leaveOpen: true);\n        \n        var body = await reader.ReadToEndAsync();\n        request.Body.Position = 0; // Повертаємо позицію для наступного читання\n        \n        return string.IsNullOrEmpty(body) ? \"empty\" : body;\n    }\n}\n",[3019,12714,12715,12728,12732,12751,12755,12775,12779,12789,12793,12797,12809,12817,12825,12829,12862,12886,12910,12914,12919,12947,12957,12964,12971,12977,12983,12990,12994,12998,13012,13016,13021,13041,13045,13087,13097,13104,13111,13127,13134,13138,13142,13146,13173,13177,13212,13221,13225,13240,13244,13263,13275,13300,13311,13322,13326,13346,13368,13372,13399,13403],{"__ignoreMap":3017},[3022,12716,12717,12719,12721,12724,12726],{"class":3024,"line":3025},[3022,12718,3050],{"class":3049},[3022,12720,3987],{"class":3049},[3022,12722,12723],{"class":3032}," RequestResponseLoggingFilter",[3022,12725,3993],{"class":3028},[3022,12727,4101],{"class":3032},[3022,12729,12730],{"class":3024,"line":3046},[3022,12731,3087],{"class":3028},[3022,12733,12734,12736,12738,12740,12742,12745,12747,12749],{"class":3024,"line":3084},[3022,12735,4837],{"class":3049},[3022,12737,4859],{"class":3049},[3022,12739,4876],{"class":3032},[3022,12741,3059],{"class":3028},[3022,12743,12744],{"class":3032},"RequestResponseLoggingFilter",[3022,12746,3065],{"class":3028},[3022,12748,4886],{"class":3077},[3022,12750,4315],{"class":3028},[3022,12752,12753],{"class":3024,"line":3090},[3022,12754,4042],{"emptyLinePlaceholder":4041},[3022,12756,12757,12759,12761,12763,12765,12767,12769,12771,12773],{"class":3024,"line":3097},[3022,12758,4005],{"class":3049},[3022,12760,12723],{"class":3068},[3022,12762,3036],{"class":3028},[3022,12764,4911],{"class":3032},[3022,12766,3059],{"class":3028},[3022,12768,12744],{"class":3032},[3022,12770,3065],{"class":3028},[3022,12772,4920],{"class":3077},[3022,12774,3081],{"class":3028},[3022,12776,12777],{"class":3024,"line":3123},[3022,12778,4026],{"class":3028},[3022,12780,12781,12783,12785,12787],{"class":3024,"line":3129},[3022,12782,4943],{"class":3077},[3022,12784,3223],{"class":3028},[3022,12786,4920],{"class":3077},[3022,12788,4315],{"class":3028},[3022,12790,12791],{"class":3024,"line":3135},[3022,12792,4036],{"class":3028},[3022,12794,12795],{"class":3024,"line":3170},[3022,12796,4042],{"emptyLinePlaceholder":4041},[3022,12798,12799,12801,12803,12805,12807],{"class":3024,"line":3175},[3022,12800,4005],{"class":3049},[3022,12802,3053],{"class":3049},[3022,12804,3056],{"class":3032},[3022,12806,4116],{"class":3068},[3022,12808,4119],{"class":3028},[3022,12810,12811,12813,12815],{"class":3024,"line":3181},[3022,12812,4124],{"class":3032},[3022,12814,4019],{"class":3077},[3022,12816,3527],{"class":3028},[3022,12818,12819,12821,12823],{"class":3024,"line":3203},[3022,12820,4133],{"class":3032},[3022,12822,4136],{"class":3077},[3022,12824,3081],{"class":3028},[3022,12826,12827],{"class":3024,"line":3208},[3022,12828,4026],{"class":3028},[3022,12830,12831,12833,12835,12837,12839,12841,12843,12845,12847,12849,12851,12853,12855,12857,12860],{"class":3024,"line":3214},[3022,12832,4156],{"class":3049},[3022,12834,7323],{"class":3077},[3022,12836,3223],{"class":3028},[3022,12838,4996],{"class":3077},[3022,12840,3103],{"class":3028},[3022,12842,5001],{"class":3077},[3022,12844,3103],{"class":3028},[3022,12846,5497],{"class":3077},[3022,12848,3029],{"class":3028},[3022,12850,7475],{"class":3039},[3022,12852,9711],{"class":3028},[3022,12854,5298],{"class":3068},[3022,12856,7357],{"class":3028},[3022,12858,12859],{"class":3039},"\"N/A\"",[3022,12861,4315],{"class":3028},[3022,12863,12864,12866,12868,12870,12872,12874,12876,12878,12880,12882,12884],{"class":3024,"line":3236},[3022,12865,4156],{"class":3049},[3022,12867,7781],{"class":3077},[3022,12869,3223],{"class":3028},[3022,12871,4996],{"class":3077},[3022,12873,3103],{"class":3028},[3022,12875,5001],{"class":3077},[3022,12877,3103],{"class":3028},[3022,12879,5006],{"class":3077},[3022,12881,3103],{"class":3028},[3022,12883,7798],{"class":3077},[3022,12885,4315],{"class":3028},[3022,12887,12888,12890,12892,12894,12896,12898,12900,12902,12904,12906,12908],{"class":3024,"line":3241},[3022,12889,4156],{"class":3049},[3022,12891,7807],{"class":3077},[3022,12893,3223],{"class":3028},[3022,12895,4996],{"class":3077},[3022,12897,3103],{"class":3028},[3022,12899,5001],{"class":3077},[3022,12901,3103],{"class":3028},[3022,12903,5006],{"class":3077},[3022,12905,3103],{"class":3028},[3022,12907,5072],{"class":3077},[3022,12909,4315],{"class":3028},[3022,12911,12912],{"class":3024,"line":3247},[3022,12913,4042],{"emptyLinePlaceholder":4041},[3022,12915,12916],{"class":3024,"line":3279},[3022,12917,12918],{"class":3093},"        // Логуємо запит\n",[3022,12920,12921,12923,12926,12928,12930,12933,12935,12937,12939,12941,12943,12945],{"class":3024,"line":3284},[3022,12922,4156],{"class":3049},[3022,12924,12925],{"class":3077}," requestBody",[3022,12927,3223],{"class":3028},[3022,12929,3257],{"class":3049},[3022,12931,12932],{"class":3068}," ReadRequestBody",[3022,12934,3036],{"class":3028},[3022,12936,4996],{"class":3077},[3022,12938,3103],{"class":3028},[3022,12940,5001],{"class":3077},[3022,12942,3103],{"class":3028},[3022,12944,5006],{"class":3077},[3022,12946,3120],{"class":3028},[3022,12948,12949,12951,12953,12955],{"class":3024,"line":3290},[3022,12950,4943],{"class":3077},[3022,12952,3103],{"class":3028},[3022,12954,3106],{"class":3068},[3022,12956,4119],{"class":3028},[3022,12958,12959,12962],{"class":3024,"line":3303},[3022,12960,12961],{"class":3039},"            \"[{CorrelationId}] Request: {Method} {Path} | Body: {Body}\"",[3022,12963,3527],{"class":3028},[3022,12965,12966,12969],{"class":3024,"line":3329},[3022,12967,12968],{"class":3077},"            correlationId",[3022,12970,3527],{"class":3028},[3022,12972,12973,12975],{"class":3024,"line":3334},[3022,12974,7851],{"class":3077},[3022,12976,3527],{"class":3028},[3022,12978,12979,12981],{"class":3024,"line":3340},[3022,12980,7858],{"class":3077},[3022,12982,3527],{"class":3028},[3022,12984,12985,12988],{"class":3024,"line":3378},[3022,12986,12987],{"class":3077},"            requestBody",[3022,12989,3120],{"class":3028},[3022,12991,12992],{"class":3024,"line":3803},[3022,12993,4042],{"emptyLinePlaceholder":4041},[3022,12995,12996],{"class":3024,"line":3809},[3022,12997,7876],{"class":3093},[3022,12999,13000,13002,13004,13006,13008,13010],{"class":3024,"line":4632},[3022,13001,4156],{"class":3049},[3022,13003,4159],{"class":3077},[3022,13005,3223],{"class":3028},[3022,13007,3257],{"class":3049},[3022,13009,4136],{"class":3068},[3022,13011,3200],{"class":3028},[3022,13013,13014],{"class":3024,"line":4658},[3022,13015,4042],{"emptyLinePlaceholder":4041},[3022,13017,13018],{"class":3024,"line":4677},[3022,13019,13020],{"class":3093},"        // Логуємо відповідь\n",[3022,13022,13023,13025,13027,13029,13031,13033,13035,13037,13039],{"class":3024,"line":4682},[3022,13024,4991],{"class":3138},[3022,13026,3142],{"class":3028},[3022,13028,11316],{"class":3077},[3022,13030,3103],{"class":3028},[3022,13032,3921],{"class":3077},[3022,13034,6225],{"class":3049},[3022,13036,6642],{"class":3032},[3022,13038,6645],{"class":3077},[3022,13040,3081],{"class":3028},[3022,13042,13043],{"class":3024,"line":4712},[3022,13044,5040],{"class":3028},[3022,13046,13047,13049,13052,13054,13057,13059,13062,13064,13067,13069,13072,13074,13077,13079,13081,13083,13085],{"class":3024,"line":4731},[3022,13048,5927],{"class":3049},[3022,13050,13051],{"class":3077}," responseBody",[3022,13053,3223],{"class":3028},[3022,13055,13056],{"class":3077},"System",[3022,13058,3103],{"class":3028},[3022,13060,13061],{"class":3077},"Text",[3022,13063,3103],{"class":3028},[3022,13065,13066],{"class":3077},"Json",[3022,13068,3103],{"class":3028},[3022,13070,13071],{"class":3077},"JsonSerializer",[3022,13073,3103],{"class":3028},[3022,13075,13076],{"class":3068},"Serialize",[3022,13078,3036],{"class":3028},[3022,13080,6732],{"class":3077},[3022,13082,3103],{"class":3028},[3022,13084,5961],{"class":3077},[3022,13086,3120],{"class":3028},[3022,13088,13089,13091,13093,13095],{"class":3024,"line":5202},[3022,13090,5045],{"class":3077},[3022,13092,3103],{"class":3028},[3022,13094,3106],{"class":3068},[3022,13096,4119],{"class":3028},[3022,13098,13099,13102],{"class":3024,"line":5207},[3022,13100,13101],{"class":3039},"                \"[{CorrelationId}] Response: {StatusCode} | Body: {Body}\"",[3022,13103,3527],{"class":3028},[3022,13105,13106,13109],{"class":3024,"line":5213},[3022,13107,13108],{"class":3077},"                correlationId",[3022,13110,3527],{"class":3028},[3022,13112,13113,13116,13118,13120,13123,13125],{"class":3024,"line":5265},[3022,13114,13115],{"class":3077},"                objectResult",[3022,13117,3103],{"class":3028},[3022,13119,6658],{"class":3077},[3022,13121,13122],{"class":3028}," ?? ",[3022,13124,4402],{"class":3150},[3022,13126,3527],{"class":3028},[3022,13128,13129,13132],{"class":3024,"line":5270},[3022,13130,13131],{"class":3077},"                responseBody",[3022,13133,3120],{"class":3028},[3022,13135,13136],{"class":3024,"line":5276},[3022,13137,5199],{"class":3028},[3022,13139,13140],{"class":3024,"line":5304},[3022,13141,4036],{"class":3028},[3022,13143,13144],{"class":3024,"line":5309},[3022,13145,4042],{"emptyLinePlaceholder":4041},[3022,13147,13148,13150,13152,13154,13156,13158,13160,13163,13165,13168,13171],{"class":3024,"line":5329},[3022,13149,4837],{"class":3049},[3022,13151,3053],{"class":3049},[3022,13153,3056],{"class":3032},[3022,13155,3059],{"class":3028},[3022,13157,5244],{"class":3049},[3022,13159,3065],{"class":3028},[3022,13161,13162],{"class":3068},"ReadRequestBody",[3022,13164,3036],{"class":3028},[3022,13166,13167],{"class":3032},"HttpRequest",[3022,13169,13170],{"class":3077}," request",[3022,13172,3081],{"class":3028},[3022,13174,13175],{"class":3024,"line":5334},[3022,13176,4026],{"class":3028},[3022,13178,13179,13181,13183,13186,13188,13191,13194,13197,13200,13202,13204,13206,13208,13210],{"class":3024,"line":5355},[3022,13180,4991],{"class":3138},[3022,13182,3142],{"class":3028},[3022,13184,13185],{"class":3077},"request",[3022,13187,3103],{"class":3028},[3022,13189,13190],{"class":3077},"ContentLength",[3022,13192,13193],{"class":3028}," == ",[3022,13195,13196],{"class":3049},"null",[3022,13198,13199],{"class":3028}," || ",[3022,13201,13185],{"class":3077},[3022,13203,3103],{"class":3028},[3022,13205,13190],{"class":3077},[3022,13207,13193],{"class":3028},[3022,13209,3151],{"class":3150},[3022,13211,3081],{"class":3028},[3022,13213,13214,13216,13219],{"class":3024,"line":5360},[3022,13215,5192],{"class":3138},[3022,13217,13218],{"class":3039}," \"empty\"",[3022,13220,4315],{"class":3028},[3022,13222,13223],{"class":3024,"line":5375},[3022,13224,4042],{"emptyLinePlaceholder":4041},[3022,13226,13227,13230,13232,13235,13237],{"class":3024,"line":5387},[3022,13228,13229],{"class":3077},"        request",[3022,13231,3103],{"class":3028},[3022,13233,13234],{"class":3068},"EnableBuffering",[3022,13236,4168],{"class":3028},[3022,13238,13239],{"class":3093},"// Дозволяє читати Body кілька разів\n",[3022,13241,13242],{"class":3024,"line":5399},[3022,13243,4151],{"class":3028},[3022,13245,13246,13249,13251,13254,13256,13258,13261],{"class":3024,"line":5420},[3022,13247,13248],{"class":3138},"        using",[3022,13250,5029],{"class":3049},[3022,13252,13253],{"class":3077}," reader",[3022,13255,3223],{"class":3028},[3022,13257,3351],{"class":3049},[3022,13259,13260],{"class":3032}," StreamReader",[3022,13262,4119],{"class":3028},[3022,13264,13265,13268,13270,13273],{"class":3024,"line":5425},[3022,13266,13267],{"class":3077},"            request",[3022,13269,3103],{"class":3028},[3022,13271,13272],{"class":3077},"Body",[3022,13274,3527],{"class":3028},[3022,13276,13277,13280,13282,13284,13286,13288,13290,13293,13295,13298],{"class":3024,"line":5432},[3022,13278,13279],{"class":3077},"            encoding",[3022,13281,3522],{"class":3028},[3022,13283,13056],{"class":3077},[3022,13285,3103],{"class":3028},[3022,13287,13061],{"class":3077},[3022,13289,3103],{"class":3028},[3022,13291,13292],{"class":3077},"Encoding",[3022,13294,3103],{"class":3028},[3022,13296,13297],{"class":3077},"UTF8",[3022,13299,3527],{"class":3028},[3022,13301,13302,13305,13307,13309],{"class":3024,"line":5437},[3022,13303,13304],{"class":3077},"            detectEncodingFromByteOrderMarks",[3022,13306,3522],{"class":3028},[3022,13308,11719],{"class":3049},[3022,13310,3527],{"class":3028},[3022,13312,13313,13316,13318,13320],{"class":3024,"line":5442},[3022,13314,13315],{"class":3077},"            leaveOpen",[3022,13317,3522],{"class":3028},[3022,13319,3362],{"class":3049},[3022,13321,3120],{"class":3028},[3022,13323,13324],{"class":3024,"line":5474},[3022,13325,4151],{"class":3028},[3022,13327,13328,13330,13333,13335,13337,13339,13341,13344],{"class":3024,"line":5479},[3022,13329,4156],{"class":3049},[3022,13331,13332],{"class":3077}," body",[3022,13334,3223],{"class":3028},[3022,13336,3257],{"class":3049},[3022,13338,13253],{"class":3077},[3022,13340,3103],{"class":3028},[3022,13342,13343],{"class":3068},"ReadToEndAsync",[3022,13345,3200],{"class":3028},[3022,13347,13348,13350,13352,13354,13356,13359,13361,13363,13365],{"class":3024,"line":5485},[3022,13349,13229],{"class":3077},[3022,13351,3103],{"class":3028},[3022,13353,13272],{"class":3077},[3022,13355,3103],{"class":3028},[3022,13357,13358],{"class":3077},"Position",[3022,13360,3223],{"class":3028},[3022,13362,3151],{"class":3150},[3022,13364,4369],{"class":3028},[3022,13366,13367],{"class":3093},"// Повертаємо позицію для наступного читання\n",[3022,13369,13370],{"class":3024,"line":5516},[3022,13371,4151],{"class":3028},[3022,13373,13374,13376,13378,13380,13382,13384,13387,13390,13393,13395,13397],{"class":3024,"line":5521},[3022,13375,9795],{"class":3138},[3022,13377,4414],{"class":3049},[3022,13379,3103],{"class":3028},[3022,13381,12197],{"class":3068},[3022,13383,3036],{"class":3028},[3022,13385,13386],{"class":3077},"body",[3022,13388,13389],{"class":3028},") ? ",[3022,13391,13392],{"class":3039},"\"empty\"",[3022,13394,3993],{"class":3028},[3022,13396,13386],{"class":3077},[3022,13398,4315],{"class":3028},[3022,13400,13401],{"class":3024,"line":5536},[3022,13402,4036],{"class":3028},[3022,13404,13405],{"class":3024,"line":5541},[3022,13406,3381],{"class":3028},[2964,13408,13409],{},[2974,13410,6460],{},[5550,13412,13413,13421,13429,13435],{},[2971,13414,13415,13420],{},[2974,13416,13417],{},[3019,13418,13419],{},"request.EnableBuffering()"," — дозволяє читати Body кілька разів (за замовчуванням можна лише один раз)",[2971,13422,13423,13428],{},[2974,13424,13425],{},[3019,13426,13427],{},"request.Body.Position = 0"," — повертаємо позицію після читання",[2971,13430,13431,13434],{},[2974,13432,13433],{},"Correlation ID"," — використовуємо для зв'язку запиту та відповіді",[2971,13436,13437,13440],{},[2974,13438,13439],{},"Structured logging"," — використовуємо параметри замість інтерполяції",[4189,13442,13443,13446],{},[2974,13444,13445],{},"Безпека:"," Не логуйте чутливі дані (паролі, токени, номери карток). Додайте фільтрацію або маскування.",[3423,13448,13450],{"id":13449},"завдання-23-conditional-response-compression","Завдання 2.3: Conditional Response Compression",[2964,13452,13453,13454,13457],{},"Створіть фільтр, що додає заголовок ",[3019,13455,13456],{},"Content-Encoding: gzip"," для великих відповідей:",[11911,13459,13460,13765],{"title":12020},[3012,13461,13463],{"className":3014,"code":13462,"language":3016,"meta":3017,"style":3017},"public class ConditionalCompressionFilter : IAsyncResultFilter\n{\n    private const int CompressionThresholdBytes = 1024; // 1 KB\n\n    public async Task OnResultExecutionAsync(\n        ResultExecutingContext context,\n        ResultExecutionDelegate next)\n    {\n        if (context.Result is ObjectResult objectResult && objectResult.Value != null)\n        {\n            var json = System.Text.Json.JsonSerializer.Serialize(objectResult.Value);\n            var sizeBytes = System.Text.Encoding.UTF8.GetByteCount(json);\n\n            if (sizeBytes > CompressionThresholdBytes)\n            {\n                context.HttpContext.Response.Headers.Append(\"X-Original-Size\", sizeBytes.ToString());\n                context.HttpContext.Response.Headers.Append(\"X-Compression-Eligible\", \"true\");\n            }\n        }\n\n        await next();\n    }\n}\n",[3019,13464,13465,13478,13482,13503,13507,13519,13527,13535,13539,13571,13575,13612,13646,13650,13666,13670,13705,13737,13741,13745,13749,13757,13761],{"__ignoreMap":3017},[3022,13466,13467,13469,13471,13474,13476],{"class":3024,"line":3025},[3022,13468,3050],{"class":3049},[3022,13470,3987],{"class":3049},[3022,13472,13473],{"class":3032}," ConditionalCompressionFilter",[3022,13475,3993],{"class":3028},[3022,13477,6581],{"class":3032},[3022,13479,13480],{"class":3024,"line":3046},[3022,13481,3087],{"class":3028},[3022,13483,13484,13486,13488,13490,13493,13495,13498,13500],{"class":3024,"line":3084},[3022,13485,4837],{"class":3049},[3022,13487,4840],{"class":3049},[3022,13489,4358],{"class":3049},[3022,13491,13492],{"class":3077}," CompressionThresholdBytes",[3022,13494,3223],{"class":3028},[3022,13496,13497],{"class":3150},"1024",[3022,13499,4369],{"class":3028},[3022,13501,13502],{"class":3093},"// 1 KB\n",[3022,13504,13505],{"class":3024,"line":3090},[3022,13506,4042],{"emptyLinePlaceholder":4041},[3022,13508,13509,13511,13513,13515,13517],{"class":3024,"line":3097},[3022,13510,4005],{"class":3049},[3022,13512,3053],{"class":3049},[3022,13514,3056],{"class":3032},[3022,13516,6596],{"class":3068},[3022,13518,4119],{"class":3028},[3022,13520,13521,13523,13525],{"class":3024,"line":3123},[3022,13522,6603],{"class":3032},[3022,13524,4019],{"class":3077},[3022,13526,3527],{"class":3028},[3022,13528,13529,13531,13533],{"class":3024,"line":3129},[3022,13530,6612],{"class":3032},[3022,13532,4136],{"class":3077},[3022,13534,3081],{"class":3028},[3022,13536,13537],{"class":3024,"line":3135},[3022,13538,4026],{"class":3028},[3022,13540,13541,13543,13545,13547,13549,13551,13553,13555,13557,13559,13561,13563,13565,13567,13569],{"class":3024,"line":3170},[3022,13542,4991],{"class":3138},[3022,13544,3142],{"class":3028},[3022,13546,4996],{"class":3077},[3022,13548,3103],{"class":3028},[3022,13550,3921],{"class":3077},[3022,13552,6225],{"class":3049},[3022,13554,6642],{"class":3032},[3022,13556,6645],{"class":3077},[3022,13558,12389],{"class":3028},[3022,13560,6732],{"class":3077},[3022,13562,3103],{"class":3028},[3022,13564,5961],{"class":3077},[3022,13566,6266],{"class":3028},[3022,13568,13196],{"class":3049},[3022,13570,3081],{"class":3028},[3022,13572,13573],{"class":3024,"line":3175},[3022,13574,5040],{"class":3028},[3022,13576,13577,13579,13582,13584,13586,13588,13590,13592,13594,13596,13598,13600,13602,13604,13606,13608,13610],{"class":3024,"line":3181},[3022,13578,5927],{"class":3049},[3022,13580,13581],{"class":3077}," json",[3022,13583,3223],{"class":3028},[3022,13585,13056],{"class":3077},[3022,13587,3103],{"class":3028},[3022,13589,13061],{"class":3077},[3022,13591,3103],{"class":3028},[3022,13593,13066],{"class":3077},[3022,13595,3103],{"class":3028},[3022,13597,13071],{"class":3077},[3022,13599,3103],{"class":3028},[3022,13601,13076],{"class":3068},[3022,13603,3036],{"class":3028},[3022,13605,6732],{"class":3077},[3022,13607,3103],{"class":3028},[3022,13609,5961],{"class":3077},[3022,13611,3120],{"class":3028},[3022,13613,13614,13616,13619,13621,13623,13625,13627,13629,13631,13633,13635,13637,13640,13642,13644],{"class":3024,"line":3203},[3022,13615,5927],{"class":3049},[3022,13617,13618],{"class":3077}," sizeBytes",[3022,13620,3223],{"class":3028},[3022,13622,13056],{"class":3077},[3022,13624,3103],{"class":3028},[3022,13626,13061],{"class":3077},[3022,13628,3103],{"class":3028},[3022,13630,13292],{"class":3077},[3022,13632,3103],{"class":3028},[3022,13634,13297],{"class":3077},[3022,13636,3103],{"class":3028},[3022,13638,13639],{"class":3068},"GetByteCount",[3022,13641,3036],{"class":3028},[3022,13643,3507],{"class":3077},[3022,13645,3120],{"class":3028},[3022,13647,13648],{"class":3024,"line":3208},[3022,13649,4042],{"emptyLinePlaceholder":4041},[3022,13651,13652,13654,13656,13659,13661,13664],{"class":3024,"line":3214},[3022,13653,6217],{"class":3138},[3022,13655,3142],{"class":3028},[3022,13657,13658],{"class":3077},"sizeBytes",[3022,13660,5974],{"class":3028},[3022,13662,13663],{"class":3077},"CompressionThresholdBytes",[3022,13665,3081],{"class":3028},[3022,13667,13668],{"class":3024,"line":3236},[3022,13669,5107],{"class":3028},[3022,13671,13672,13674,13676,13678,13680,13682,13684,13686,13688,13690,13692,13695,13697,13699,13701,13703],{"class":3024,"line":3241},[3022,13673,5830],{"class":3077},[3022,13675,3103],{"class":3028},[3022,13677,5001],{"class":3077},[3022,13679,3103],{"class":3028},[3022,13681,7394],{"class":3077},[3022,13683,3103],{"class":3028},[3022,13685,5011],{"class":3077},[3022,13687,3103],{"class":3028},[3022,13689,7403],{"class":3068},[3022,13691,3036],{"class":3028},[3022,13693,13694],{"class":3039},"\"X-Original-Size\"",[3022,13696,3114],{"class":3028},[3022,13698,13658],{"class":3077},[3022,13700,3103],{"class":3028},[3022,13702,5298],{"class":3068},[3022,13704,7968],{"class":3028},[3022,13706,13707,13709,13711,13713,13715,13717,13719,13721,13723,13725,13727,13730,13732,13735],{"class":3024,"line":3247},[3022,13708,5830],{"class":3077},[3022,13710,3103],{"class":3028},[3022,13712,5001],{"class":3077},[3022,13714,3103],{"class":3028},[3022,13716,7394],{"class":3077},[3022,13718,3103],{"class":3028},[3022,13720,5011],{"class":3077},[3022,13722,3103],{"class":3028},[3022,13724,7403],{"class":3068},[3022,13726,3036],{"class":3028},[3022,13728,13729],{"class":3039},"\"X-Compression-Eligible\"",[3022,13731,3114],{"class":3028},[3022,13733,13734],{"class":3039},"\"true\"",[3022,13736,3120],{"class":3028},[3022,13738,13739],{"class":3024,"line":3279},[3022,13740,6387],{"class":3028},[3022,13742,13743],{"class":3024,"line":3284},[3022,13744,5199],{"class":3028},[3022,13746,13747],{"class":3024,"line":3290},[3022,13748,4042],{"emptyLinePlaceholder":4041},[3022,13750,13751,13753,13755],{"class":3024,"line":3303},[3022,13752,5524],{"class":3049},[3022,13754,4136],{"class":3068},[3022,13756,3200],{"class":3028},[3022,13758,13759],{"class":3024,"line":3329},[3022,13760,4036],{"class":3028},[3022,13762,13763],{"class":3024,"line":3334},[3022,13764,3381],{"class":3028},[3416,13766,13767],{},"У production використовуйте вбудований Response Compression Middleware замість кастомного фільтра. Цей приклад демонструє концепцію умовної логіки у Result Filter.",[2964,13769,12698],{},[3652,13771],{},[3423,13773,13775],{"id":13774},"рівень-3-архітектура-та-створення","Рівень 3: Архітектура та створення",[4216,13777,13778,13782,13785,16180,16184,16187,17963,17967,17970],{},[3423,13779,13781],{"id":13780},"завдання-31-multi-tenant-api-key-filter","Завдання 3.1: Multi-Tenant API Key Filter",[2964,13783,13784],{},"Створіть систему фільтрів для multi-tenant API, де кожен API-ключ прив'язаний до tenant:",[11911,13786,13787,13792,13902,13907,14221,14226,15019,15024,15811,15816,15952,15957,16102,16106,16139],{"title":12020},[2964,13788,13789],{},[2974,13790,13791],{},"1. Модель Tenant:",[3012,13793,13795],{"className":3014,"code":13794,"language":3016,"meta":3017,"style":3017},"public record TenantInfo\n{\n    public required string TenantId { get; init; }\n    public required string Name { get; init; }\n    public required string[] AllowedEndpoints { get; init; }\n    public int RateLimitPerMinute { get; init; } = 100;\n}\n",[3019,13796,13797,13806,13810,13831,13851,13875,13898],{"__ignoreMap":3017},[3022,13798,13799,13801,13803],{"class":3024,"line":3025},[3022,13800,3050],{"class":3049},[3022,13802,4558],{"class":3049},[3022,13804,13805],{"class":3032}," TenantInfo\n",[3022,13807,13808],{"class":3024,"line":3046},[3022,13809,3087],{"class":3028},[3022,13811,13812,13814,13816,13818,13821,13823,13825,13827,13829],{"class":3024,"line":3084},[3022,13813,4005],{"class":3049},[3022,13815,4411],{"class":3049},[3022,13817,4414],{"class":3049},[3022,13819,13820],{"class":3077}," TenantId",[3022,13822,3354],{"class":3028},[3022,13824,4366],{"class":3049},[3022,13826,4369],{"class":3028},[3022,13828,4623],{"class":3049},[3022,13830,4375],{"class":3028},[3022,13832,13833,13835,13837,13839,13841,13843,13845,13847,13849],{"class":3024,"line":3090},[3022,13834,4005],{"class":3049},[3022,13836,4411],{"class":3049},[3022,13838,4414],{"class":3049},[3022,13840,4417],{"class":3077},[3022,13842,3354],{"class":3028},[3022,13844,4366],{"class":3049},[3022,13846,4369],{"class":3028},[3022,13848,4623],{"class":3049},[3022,13850,4375],{"class":3028},[3022,13852,13853,13855,13857,13859,13862,13865,13867,13869,13871,13873],{"class":3024,"line":3097},[3022,13854,4005],{"class":3049},[3022,13856,4411],{"class":3049},[3022,13858,4414],{"class":3049},[3022,13860,13861],{"class":3028},"[] ",[3022,13863,13864],{"class":3077},"AllowedEndpoints",[3022,13866,3354],{"class":3028},[3022,13868,4366],{"class":3049},[3022,13870,4369],{"class":3028},[3022,13872,4623],{"class":3049},[3022,13874,4375],{"class":3028},[3022,13876,13877,13879,13881,13884,13886,13888,13890,13892,13894,13896],{"class":3024,"line":3123},[3022,13878,4005],{"class":3049},[3022,13880,4358],{"class":3049},[3022,13882,13883],{"class":3077}," RateLimitPerMinute",[3022,13885,3354],{"class":3028},[3022,13887,4366],{"class":3049},[3022,13889,4369],{"class":3028},[3022,13891,4623],{"class":3049},[3022,13893,4539],{"class":3028},[3022,13895,10867],{"class":3150},[3022,13897,4315],{"class":3028},[3022,13899,13900],{"class":3024,"line":3129},[3022,13901,3381],{"class":3028},[2964,13903,13904],{},[2974,13905,13906],{},"2. Tenant Service:",[3012,13908,13910],{"className":3014,"code":13909,"language":3016,"meta":3017,"style":3017},"public interface ITenantService\n{\n    TenantInfo? GetTenantByApiKey(string apiKey);\n}\n\npublic class TenantService : ITenantService\n{\n    private readonly Dictionary\u003Cstring, TenantInfo> _tenants = new()\n    {\n        [\"sk_tenant_a_abc123\"] = new TenantInfo\n        {\n            TenantId = \"tenant-a\",\n            Name = \"Company A\",\n            AllowedEndpoints = new[] { \"/api/products\", \"/api/orders\" },\n            RateLimitPerMinute = 50\n        },\n        [\"sk_tenant_b_xyz789\"] = new TenantInfo\n        {\n            TenantId = \"tenant-b\",\n            Name = \"Company B\",\n            AllowedEndpoints = new[] { \"/api/products\" },\n            RateLimitPerMinute = 100\n        }\n    };\n\n    public TenantInfo? GetTenantByApiKey(string apiKey)\n    {\n        return _tenants.TryGetValue(apiKey, out var tenant) ? tenant : null;\n    }\n}\n",[3019,13911,13912,13922,13926,13944,13948,13952,13966,13970,13998,14002,14016,14020,14032,14043,14065,14075,14080,14093,14097,14108,14119,14133,14142,14146,14151,14155,14174,14178,14213,14217],{"__ignoreMap":3017},[3022,13913,13914,13916,13919],{"class":3024,"line":3025},[3022,13915,3050],{"class":3049},[3022,13917,13918],{"class":3049}," interface",[3022,13920,13921],{"class":3032}," ITenantService\n",[3022,13923,13924],{"class":3024,"line":3046},[3022,13925,3087],{"class":3028},[3022,13927,13928,13931,13933,13936,13938,13940,13942],{"class":3024,"line":3084},[3022,13929,13930],{"class":3032},"    TenantInfo",[3022,13932,6940],{"class":3028},[3022,13934,13935],{"class":3068},"GetTenantByApiKey",[3022,13937,3036],{"class":3028},[3022,13939,5244],{"class":3049},[3022,13941,12158],{"class":3077},[3022,13943,3120],{"class":3028},[3022,13945,13946],{"class":3024,"line":3090},[3022,13947,3381],{"class":3028},[3022,13949,13950],{"class":3024,"line":3097},[3022,13951,4042],{"emptyLinePlaceholder":4041},[3022,13953,13954,13956,13958,13961,13963],{"class":3024,"line":3123},[3022,13955,3050],{"class":3049},[3022,13957,3987],{"class":3049},[3022,13959,13960],{"class":3032}," TenantService",[3022,13962,3993],{"class":3028},[3022,13964,13965],{"class":3032},"ITenantService\n",[3022,13967,13968],{"class":3024,"line":3129},[3022,13969,3087],{"class":3028},[3022,13971,13972,13974,13976,13978,13980,13982,13984,13987,13989,13992,13994,13996],{"class":3024,"line":3135},[3022,13973,4837],{"class":3049},[3022,13975,4859],{"class":3049},[3022,13977,12054],{"class":3032},[3022,13979,3059],{"class":3028},[3022,13981,5244],{"class":3049},[3022,13983,3114],{"class":3028},[3022,13985,13986],{"class":3032},"TenantInfo",[3022,13988,3065],{"class":3028},[3022,13990,13991],{"class":3077},"_tenants",[3022,13993,3223],{"class":3028},[3022,13995,3351],{"class":3049},[3022,13997,6045],{"class":3028},[3022,13999,14000],{"class":3024,"line":3170},[3022,14001,4026],{"class":3028},[3022,14003,14004,14007,14010,14012,14014],{"class":3024,"line":3175},[3022,14005,14006],{"class":3028},"        [",[3022,14008,14009],{"class":3039},"\"sk_tenant_a_abc123\"",[3022,14011,5505],{"class":3028},[3022,14013,3351],{"class":3049},[3022,14015,13805],{"class":3032},[3022,14017,14018],{"class":3024,"line":3181},[3022,14019,5040],{"class":3028},[3022,14021,14022,14025,14027,14030],{"class":3024,"line":3203},[3022,14023,14024],{"class":3077},"            TenantId",[3022,14026,3223],{"class":3028},[3022,14028,14029],{"class":3039},"\"tenant-a\"",[3022,14031,3527],{"class":3028},[3022,14033,14034,14036,14038,14041],{"class":3024,"line":3208},[3022,14035,10158],{"class":3077},[3022,14037,3223],{"class":3028},[3022,14039,14040],{"class":3039},"\"Company A\"",[3022,14042,3527],{"class":3028},[3022,14044,14045,14048,14050,14052,14055,14058,14060,14063],{"class":3024,"line":3214},[3022,14046,14047],{"class":3077},"            AllowedEndpoints",[3022,14049,3223],{"class":3028},[3022,14051,3351],{"class":3049},[3022,14053,14054],{"class":3028},"[] { ",[3022,14056,14057],{"class":3039},"\"/api/products\"",[3022,14059,3114],{"class":3028},[3022,14061,14062],{"class":3039},"\"/api/orders\"",[3022,14064,3556],{"class":3028},[3022,14066,14067,14070,14072],{"class":3024,"line":3236},[3022,14068,14069],{"class":3077},"            RateLimitPerMinute",[3022,14071,3223],{"class":3028},[3022,14073,14074],{"class":3150},"50\n",[3022,14076,14077],{"class":3024,"line":3241},[3022,14078,14079],{"class":3028},"        },\n",[3022,14081,14082,14084,14087,14089,14091],{"class":3024,"line":3247},[3022,14083,14006],{"class":3028},[3022,14085,14086],{"class":3039},"\"sk_tenant_b_xyz789\"",[3022,14088,5505],{"class":3028},[3022,14090,3351],{"class":3049},[3022,14092,13805],{"class":3032},[3022,14094,14095],{"class":3024,"line":3279},[3022,14096,5040],{"class":3028},[3022,14098,14099,14101,14103,14106],{"class":3024,"line":3284},[3022,14100,14024],{"class":3077},[3022,14102,3223],{"class":3028},[3022,14104,14105],{"class":3039},"\"tenant-b\"",[3022,14107,3527],{"class":3028},[3022,14109,14110,14112,14114,14117],{"class":3024,"line":3290},[3022,14111,10158],{"class":3077},[3022,14113,3223],{"class":3028},[3022,14115,14116],{"class":3039},"\"Company B\"",[3022,14118,3527],{"class":3028},[3022,14120,14121,14123,14125,14127,14129,14131],{"class":3024,"line":3303},[3022,14122,14047],{"class":3077},[3022,14124,3223],{"class":3028},[3022,14126,3351],{"class":3049},[3022,14128,14054],{"class":3028},[3022,14130,14057],{"class":3039},[3022,14132,3556],{"class":3028},[3022,14134,14135,14137,14139],{"class":3024,"line":3329},[3022,14136,14069],{"class":3077},[3022,14138,3223],{"class":3028},[3022,14140,14141],{"class":3150},"100\n",[3022,14143,14144],{"class":3024,"line":3334},[3022,14145,5199],{"class":3028},[3022,14147,14148],{"class":3024,"line":3340},[3022,14149,14150],{"class":3028},"    };\n",[3022,14152,14153],{"class":3024,"line":3378},[3022,14154,4042],{"emptyLinePlaceholder":4041},[3022,14156,14157,14159,14162,14164,14166,14168,14170,14172],{"class":3024,"line":3803},[3022,14158,4005],{"class":3049},[3022,14160,14161],{"class":3032}," TenantInfo",[3022,14163,6940],{"class":3028},[3022,14165,13935],{"class":3068},[3022,14167,3036],{"class":3028},[3022,14169,5244],{"class":3049},[3022,14171,12158],{"class":3077},[3022,14173,3081],{"class":3028},[3022,14175,14176],{"class":3024,"line":3809},[3022,14177,4026],{"class":3028},[3022,14179,14180,14182,14185,14187,14189,14191,14193,14195,14197,14199,14202,14204,14207,14209,14211],{"class":3024,"line":4632},[3022,14181,9795],{"class":3138},[3022,14183,14184],{"class":3077}," _tenants",[3022,14186,3103],{"class":3028},[3022,14188,5016],{"class":3068},[3022,14190,3036],{"class":3028},[3022,14192,12202],{"class":3077},[3022,14194,3114],{"class":3028},[3022,14196,5026],{"class":3049},[3022,14198,5029],{"class":3049},[3022,14200,14201],{"class":3077}," tenant",[3022,14203,13389],{"class":3028},[3022,14205,14206],{"class":3077},"tenant",[3022,14208,3993],{"class":3028},[3022,14210,13196],{"class":3049},[3022,14212,4315],{"class":3028},[3022,14214,14215],{"class":3024,"line":4658},[3022,14216,4036],{"class":3028},[3022,14218,14219],{"class":3024,"line":4677},[3022,14220,3381],{"class":3028},[2964,14222,14223],{},[2974,14224,14225],{},"3. Multi-Tenant Auth Filter:",[3012,14227,14229],{"className":3014,"code":14228,"language":3016,"meta":3017,"style":3017},"public class MultiTenantAuthFilter : IAsyncAuthorizationFilter\n{\n    private const string ApiKeyHeaderName = \"X-Api-Key\";\n    private readonly ITenantService _tenantService;\n    private readonly ILogger\u003CMultiTenantAuthFilter> _logger;\n\n    public MultiTenantAuthFilter(ITenantService tenantService, ILogger\u003CMultiTenantAuthFilter> logger)\n    {\n        _tenantService = tenantService;\n        _logger = logger;\n    }\n\n    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)\n    {\n        // Перевіряємо наявність API-ключа\n        if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKey))\n        {\n            context.Result = new UnauthorizedObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status401Unauthorized,\n                Title = \"API Key Required\",\n                Detail = $\"API key must be provided in {ApiKeyHeaderName} header\"\n            });\n            return;\n        }\n\n        // Отримуємо tenant за API-ключем\n        var tenant = _tenantService.GetTenantByApiKey(apiKey!);\n        \n        if (tenant is null)\n        {\n            _logger.LogWarning(\"Invalid API key attempted: {ApiKey}\", apiKey);\n            \n            context.Result = new UnauthorizedObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status401Unauthorized,\n                Title = \"Invalid API Key\",\n                Detail = \"The provided API key is not valid\"\n            });\n            return;\n        }\n\n        // Перевіряємо доступ до endpoint\n        var path = context.HttpContext.Request.Path.Value ?? \"\";\n        var hasAccess = tenant.AllowedEndpoints.Any(endpoint => \n            path.StartsWith(endpoint, StringComparison.OrdinalIgnoreCase));\n\n        if (!hasAccess)\n        {\n            _logger.LogWarning(\n                \"Tenant {TenantId} attempted to access forbidden endpoint: {Path}\",\n                tenant.TenantId,\n                path);\n            \n            context.Result = new ObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status403Forbidden,\n                Title = \"Access Denied\",\n                Detail = $\"Your subscription does not include access to {path}\"\n            })\n            {\n                StatusCode = StatusCodes.Status403Forbidden\n            };\n            return;\n        }\n\n        // Зберігаємо tenant у HttpContext\n        context.HttpContext.Items[\"Tenant\"] = tenant;\n        \n        _logger.LogInformation(\n            \"Tenant {TenantId} ({TenantName}) authenticated for {Path}\",\n            tenant.TenantId,\n            tenant.Name,\n            path);\n\n        await Task.CompletedTask;\n    }\n}\n",[3019,14230,14231,14244,14248,14264,14278,14297,14301,14329,14333,14345,14355,14359,14363,14381,14385,14390,14428,14432,14452,14456,14470,14480,14497,14501,14507,14511,14515,14520,14542,14546,14560,14564,14582,14586,14606,14610,14624,14634,14643,14647,14653,14657,14661,14666,14699,14727,14752,14756,14767,14771,14781,14788,14800,14806,14810,14830,14834,14849,14860,14879,14884,14888,14901,14905,14911,14915,14919,14924,14947,14951,14961,14968,14979,14989,14995,14999,15011,15015],{"__ignoreMap":3017},[3022,14232,14233,14235,14237,14240,14242],{"class":3024,"line":3025},[3022,14234,3050],{"class":3049},[3022,14236,3987],{"class":3049},[3022,14238,14239],{"class":3032}," MultiTenantAuthFilter",[3022,14241,3993],{"class":3028},[3022,14243,4828],{"class":3032},[3022,14245,14246],{"class":3024,"line":3046},[3022,14247,3087],{"class":3028},[3022,14249,14250,14252,14254,14256,14258,14260,14262],{"class":3024,"line":3084},[3022,14251,4837],{"class":3049},[3022,14253,4840],{"class":3049},[3022,14255,4414],{"class":3049},[3022,14257,4845],{"class":3077},[3022,14259,3223],{"class":3028},[3022,14261,4850],{"class":3039},[3022,14263,4315],{"class":3028},[3022,14265,14266,14268,14270,14273,14276],{"class":3024,"line":3090},[3022,14267,4837],{"class":3049},[3022,14269,4859],{"class":3049},[3022,14271,14272],{"class":3032}," ITenantService",[3022,14274,14275],{"class":3077}," _tenantService",[3022,14277,4315],{"class":3028},[3022,14279,14280,14282,14284,14286,14288,14291,14293,14295],{"class":3024,"line":3097},[3022,14281,4837],{"class":3049},[3022,14283,4859],{"class":3049},[3022,14285,4876],{"class":3032},[3022,14287,3059],{"class":3028},[3022,14289,14290],{"class":3032},"MultiTenantAuthFilter",[3022,14292,3065],{"class":3028},[3022,14294,4886],{"class":3077},[3022,14296,4315],{"class":3028},[3022,14298,14299],{"class":3024,"line":3123},[3022,14300,4042],{"emptyLinePlaceholder":4041},[3022,14302,14303,14305,14307,14309,14312,14315,14317,14319,14321,14323,14325,14327],{"class":3024,"line":3129},[3022,14304,4005],{"class":3049},[3022,14306,14239],{"class":3068},[3022,14308,3036],{"class":3028},[3022,14310,14311],{"class":3032},"ITenantService",[3022,14313,14314],{"class":3077}," tenantService",[3022,14316,3114],{"class":3028},[3022,14318,4911],{"class":3032},[3022,14320,3059],{"class":3028},[3022,14322,14290],{"class":3032},[3022,14324,3065],{"class":3028},[3022,14326,4920],{"class":3077},[3022,14328,3081],{"class":3028},[3022,14330,14331],{"class":3024,"line":3135},[3022,14332,4026],{"class":3028},[3022,14334,14335,14338,14340,14343],{"class":3024,"line":3170},[3022,14336,14337],{"class":3077},"        _tenantService",[3022,14339,3223],{"class":3028},[3022,14341,14342],{"class":3077},"tenantService",[3022,14344,4315],{"class":3028},[3022,14346,14347,14349,14351,14353],{"class":3024,"line":3175},[3022,14348,4943],{"class":3077},[3022,14350,3223],{"class":3028},[3022,14352,4920],{"class":3077},[3022,14354,4315],{"class":3028},[3022,14356,14357],{"class":3024,"line":3181},[3022,14358,4036],{"class":3028},[3022,14360,14361],{"class":3024,"line":3203},[3022,14362,4042],{"emptyLinePlaceholder":4041},[3022,14364,14365,14367,14369,14371,14373,14375,14377,14379],{"class":3024,"line":3208},[3022,14366,4005],{"class":3049},[3022,14368,3053],{"class":3049},[3022,14370,3056],{"class":3032},[3022,14372,4968],{"class":3068},[3022,14374,3036],{"class":3028},[3022,14376,4973],{"class":3032},[3022,14378,4019],{"class":3077},[3022,14380,3081],{"class":3028},[3022,14382,14383],{"class":3024,"line":3214},[3022,14384,4026],{"class":3028},[3022,14386,14387],{"class":3024,"line":3236},[3022,14388,14389],{"class":3093},"        // Перевіряємо наявність API-ключа\n",[3022,14391,14392,14394,14396,14398,14400,14402,14404,14406,14408,14410,14412,14414,14416,14418,14420,14422,14424,14426],{"class":3024,"line":3241},[3022,14393,4991],{"class":3138},[3022,14395,3186],{"class":3028},[3022,14397,4996],{"class":3077},[3022,14399,3103],{"class":3028},[3022,14401,5001],{"class":3077},[3022,14403,3103],{"class":3028},[3022,14405,5006],{"class":3077},[3022,14407,3103],{"class":3028},[3022,14409,5011],{"class":3077},[3022,14411,3103],{"class":3028},[3022,14413,5016],{"class":3068},[3022,14415,3036],{"class":3028},[3022,14417,5021],{"class":3077},[3022,14419,3114],{"class":3028},[3022,14421,5026],{"class":3049},[3022,14423,5029],{"class":3049},[3022,14425,12158],{"class":3077},[3022,14427,5035],{"class":3028},[3022,14429,14430],{"class":3024,"line":3247},[3022,14431,5040],{"class":3028},[3022,14433,14434,14436,14438,14440,14442,14444,14446,14448,14450],{"class":3024,"line":3279},[3022,14435,5084],{"class":3077},[3022,14437,3103],{"class":3028},[3022,14439,3921],{"class":3077},[3022,14441,3223],{"class":3028},[3022,14443,3351],{"class":3049},[3022,14445,5095],{"class":3032},[3022,14447,3036],{"class":3028},[3022,14449,3351],{"class":3049},[3022,14451,5102],{"class":3032},[3022,14453,14454],{"class":3024,"line":3284},[3022,14455,5107],{"class":3028},[3022,14457,14458,14460,14462,14464,14466,14468],{"class":3024,"line":3290},[3022,14459,5112],{"class":3077},[3022,14461,3223],{"class":3028},[3022,14463,5117],{"class":3077},[3022,14465,3103],{"class":3028},[3022,14467,5122],{"class":3077},[3022,14469,3527],{"class":3028},[3022,14471,14472,14474,14476,14478],{"class":3024,"line":3303},[3022,14473,5129],{"class":3077},[3022,14475,3223],{"class":3028},[3022,14477,5134],{"class":3039},[3022,14479,3527],{"class":3028},[3022,14481,14482,14484,14486,14488,14490,14492,14494],{"class":3024,"line":3329},[3022,14483,5141],{"class":3077},[3022,14485,3223],{"class":3028},[3022,14487,5146],{"class":3039},[3022,14489,5150],{"class":5149},[3022,14491,5021],{"class":3077},[3022,14493,5155],{"class":5149},[3022,14495,14496],{"class":3039}," header\"\n",[3022,14498,14499],{"class":3024,"line":3334},[3022,14500,5187],{"class":3028},[3022,14502,14503,14505],{"class":3024,"line":3340},[3022,14504,5192],{"class":3138},[3022,14506,4315],{"class":3028},[3022,14508,14509],{"class":3024,"line":3378},[3022,14510,5199],{"class":3028},[3022,14512,14513],{"class":3024,"line":3803},[3022,14514,4042],{"emptyLinePlaceholder":4041},[3022,14516,14517],{"class":3024,"line":3809},[3022,14518,14519],{"class":3093},"        // Отримуємо tenant за API-ключем\n",[3022,14521,14522,14524,14526,14528,14531,14533,14535,14537,14539],{"class":3024,"line":4632},[3022,14523,4156],{"class":3049},[3022,14525,14201],{"class":3077},[3022,14527,3223],{"class":3028},[3022,14529,14530],{"class":3077},"_tenantService",[3022,14532,3103],{"class":3028},[3022,14534,13935],{"class":3068},[3022,14536,3036],{"class":3028},[3022,14538,12202],{"class":3077},[3022,14540,14541],{"class":3028},"!);\n",[3022,14543,14544],{"class":3024,"line":4658},[3022,14545,4151],{"class":3028},[3022,14547,14548,14550,14552,14554,14556,14558],{"class":3024,"line":4677},[3022,14549,4991],{"class":3138},[3022,14551,3142],{"class":3028},[3022,14553,14206],{"class":3077},[3022,14555,6225],{"class":3049},[3022,14557,9973],{"class":3049},[3022,14559,3081],{"class":3028},[3022,14561,14562],{"class":3024,"line":4682},[3022,14563,5040],{"class":3028},[3022,14565,14566,14568,14570,14572,14574,14576,14578,14580],{"class":3024,"line":4712},[3022,14567,5045],{"class":3077},[3022,14569,3103],{"class":3028},[3022,14571,5050],{"class":3068},[3022,14573,3036],{"class":3028},[3022,14575,5320],{"class":3039},[3022,14577,3114],{"class":3028},[3022,14579,12202],{"class":3077},[3022,14581,3120],{"class":3028},[3022,14583,14584],{"class":3024,"line":4731},[3022,14585,5079],{"class":3028},[3022,14587,14588,14590,14592,14594,14596,14598,14600,14602,14604],{"class":3024,"line":5202},[3022,14589,5084],{"class":3077},[3022,14591,3103],{"class":3028},[3022,14593,3921],{"class":3077},[3022,14595,3223],{"class":3028},[3022,14597,3351],{"class":3049},[3022,14599,5095],{"class":3032},[3022,14601,3036],{"class":3028},[3022,14603,3351],{"class":3049},[3022,14605,5102],{"class":3032},[3022,14607,14608],{"class":3024,"line":5207},[3022,14609,5107],{"class":3028},[3022,14611,14612,14614,14616,14618,14620,14622],{"class":3024,"line":5213},[3022,14613,5112],{"class":3077},[3022,14615,3223],{"class":3028},[3022,14617,5117],{"class":3077},[3022,14619,3103],{"class":3028},[3022,14621,5122],{"class":3077},[3022,14623,3527],{"class":3028},[3022,14625,14626,14628,14630,14632],{"class":3024,"line":5265},[3022,14627,5129],{"class":3077},[3022,14629,3223],{"class":3028},[3022,14631,5382],{"class":3039},[3022,14633,3527],{"class":3028},[3022,14635,14636,14638,14640],{"class":3024,"line":5270},[3022,14637,5141],{"class":3077},[3022,14639,3223],{"class":3028},[3022,14641,14642],{"class":3039},"\"The provided API key is not valid\"\n",[3022,14644,14645],{"class":3024,"line":5276},[3022,14646,5187],{"class":3028},[3022,14648,14649,14651],{"class":3024,"line":5304},[3022,14650,5192],{"class":3138},[3022,14652,4315],{"class":3028},[3022,14654,14655],{"class":3024,"line":5309},[3022,14656,5199],{"class":3028},[3022,14658,14659],{"class":3024,"line":5329},[3022,14660,4042],{"emptyLinePlaceholder":4041},[3022,14662,14663],{"class":3024,"line":5334},[3022,14664,14665],{"class":3093},"        // Перевіряємо доступ до endpoint\n",[3022,14667,14668,14670,14672,14674,14676,14678,14680,14682,14684,14686,14688,14690,14692,14694,14697],{"class":3024,"line":5355},[3022,14669,4156],{"class":3049},[3022,14671,7807],{"class":3077},[3022,14673,3223],{"class":3028},[3022,14675,4996],{"class":3077},[3022,14677,3103],{"class":3028},[3022,14679,5001],{"class":3077},[3022,14681,3103],{"class":3028},[3022,14683,5006],{"class":3077},[3022,14685,3103],{"class":3028},[3022,14687,5072],{"class":3077},[3022,14689,3103],{"class":3028},[3022,14691,5961],{"class":3077},[3022,14693,13122],{"class":3028},[3022,14695,14696],{"class":3039},"\"\"",[3022,14698,4315],{"class":3028},[3022,14700,14701,14703,14706,14708,14710,14712,14714,14716,14719,14721,14724],{"class":3024,"line":5360},[3022,14702,4156],{"class":3049},[3022,14704,14705],{"class":3077}," hasAccess",[3022,14707,3223],{"class":3028},[3022,14709,14206],{"class":3077},[3022,14711,3103],{"class":3028},[3022,14713,13864],{"class":3077},[3022,14715,3103],{"class":3028},[3022,14717,14718],{"class":3068},"Any",[3022,14720,3036],{"class":3028},[3022,14722,14723],{"class":3077},"endpoint",[3022,14725,14726],{"class":3028}," => \n",[3022,14728,14729,14731,14733,14736,14738,14740,14742,14745,14747,14750],{"class":3024,"line":5375},[3022,14730,7858],{"class":3077},[3022,14732,3103],{"class":3028},[3022,14734,14735],{"class":3068},"StartsWith",[3022,14737,3036],{"class":3028},[3022,14739,14723],{"class":3077},[3022,14741,3114],{"class":3028},[3022,14743,14744],{"class":3077},"StringComparison",[3022,14746,3103],{"class":3028},[3022,14748,14749],{"class":3077},"OrdinalIgnoreCase",[3022,14751,8736],{"class":3028},[3022,14753,14754],{"class":3024,"line":5387},[3022,14755,4042],{"emptyLinePlaceholder":4041},[3022,14757,14758,14760,14762,14765],{"class":3024,"line":5399},[3022,14759,4991],{"class":3138},[3022,14761,3186],{"class":3028},[3022,14763,14764],{"class":3077},"hasAccess",[3022,14766,3081],{"class":3028},[3022,14768,14769],{"class":3024,"line":5420},[3022,14770,5040],{"class":3028},[3022,14772,14773,14775,14777,14779],{"class":3024,"line":5425},[3022,14774,5045],{"class":3077},[3022,14776,3103],{"class":3028},[3022,14778,5050],{"class":3068},[3022,14780,4119],{"class":3028},[3022,14782,14783,14786],{"class":3024,"line":5432},[3022,14784,14785],{"class":3039},"                \"Tenant {TenantId} attempted to access forbidden endpoint: {Path}\"",[3022,14787,3527],{"class":3028},[3022,14789,14790,14793,14795,14798],{"class":3024,"line":5437},[3022,14791,14792],{"class":3077},"                tenant",[3022,14794,3103],{"class":3028},[3022,14796,14797],{"class":3077},"TenantId",[3022,14799,3527],{"class":3028},[3022,14801,14802,14804],{"class":3024,"line":5442},[3022,14803,8025],{"class":3077},[3022,14805,3120],{"class":3028},[3022,14807,14808],{"class":3024,"line":5474},[3022,14809,5079],{"class":3028},[3022,14811,14812,14814,14816,14818,14820,14822,14824,14826,14828],{"class":3024,"line":5479},[3022,14813,5084],{"class":3077},[3022,14815,3103],{"class":3028},[3022,14817,3921],{"class":3077},[3022,14819,3223],{"class":3028},[3022,14821,3351],{"class":3049},[3022,14823,6642],{"class":3032},[3022,14825,3036],{"class":3028},[3022,14827,3351],{"class":3049},[3022,14829,5102],{"class":3032},[3022,14831,14832],{"class":3024,"line":5485},[3022,14833,5107],{"class":3028},[3022,14835,14836,14838,14840,14842,14844,14847],{"class":3024,"line":5516},[3022,14837,5112],{"class":3077},[3022,14839,3223],{"class":3028},[3022,14841,5117],{"class":3077},[3022,14843,3103],{"class":3028},[3022,14845,14846],{"class":3077},"Status403Forbidden",[3022,14848,3527],{"class":3028},[3022,14850,14851,14853,14855,14858],{"class":3024,"line":5521},[3022,14852,5129],{"class":3077},[3022,14854,3223],{"class":3028},[3022,14856,14857],{"class":3039},"\"Access Denied\"",[3022,14859,3527],{"class":3028},[3022,14861,14862,14864,14866,14869,14871,14874,14876],{"class":3024,"line":5536},[3022,14863,5141],{"class":3077},[3022,14865,3223],{"class":3028},[3022,14867,14868],{"class":3039},"$\"Your subscription does not include access to ",[3022,14870,5150],{"class":5149},[3022,14872,14873],{"class":3077},"path",[3022,14875,5155],{"class":5149},[3022,14877,14878],{"class":3039},"\"\n",[3022,14880,14881],{"class":3024,"line":5541},[3022,14882,14883],{"class":3028},"            })\n",[3022,14885,14886],{"class":3024,"line":6364},[3022,14887,5107],{"class":3028},[3022,14889,14890,14892,14894,14896,14898],{"class":3024,"line":6370},[3022,14891,6846],{"class":3077},[3022,14893,3223],{"class":3028},[3022,14895,5117],{"class":3077},[3022,14897,3103],{"class":3028},[3022,14899,14900],{"class":3077},"Status403Forbidden\n",[3022,14902,14903],{"class":3024,"line":6378},[3022,14904,6130],{"class":3028},[3022,14906,14907,14909],{"class":3024,"line":6384},[3022,14908,5192],{"class":3138},[3022,14910,4315],{"class":3028},[3022,14912,14913],{"class":3024,"line":6390},[3022,14914,5199],{"class":3028},[3022,14916,14917],{"class":3024,"line":6395},[3022,14918,4042],{"emptyLinePlaceholder":4041},[3022,14920,14921],{"class":3024,"line":6400},[3022,14922,14923],{"class":3093},"        // Зберігаємо tenant у HttpContext\n",[3022,14925,14926,14928,14930,14932,14934,14936,14938,14941,14943,14945],{"class":3024,"line":6428},[3022,14927,5488],{"class":3077},[3022,14929,3103],{"class":3028},[3022,14931,5001],{"class":3077},[3022,14933,3103],{"class":3028},[3022,14935,5497],{"class":3077},[3022,14937,3029],{"class":3028},[3022,14939,14940],{"class":3039},"\"Tenant\"",[3022,14942,5505],{"class":3028},[3022,14944,14206],{"class":3077},[3022,14946,4315],{"class":3028},[3022,14948,14949],{"class":3024,"line":6433},[3022,14950,4151],{"class":3028},[3022,14952,14953,14955,14957,14959],{"class":3024,"line":6439},[3022,14954,4943],{"class":3077},[3022,14956,3103],{"class":3028},[3022,14958,3106],{"class":3068},[3022,14960,4119],{"class":3028},[3022,14962,14963,14966],{"class":3024,"line":6448},[3022,14964,14965],{"class":3039},"            \"Tenant {TenantId} ({TenantName}) authenticated for {Path}\"",[3022,14967,3527],{"class":3028},[3022,14969,14970,14973,14975,14977],{"class":3024,"line":6453},[3022,14971,14972],{"class":3077},"            tenant",[3022,14974,3103],{"class":3028},[3022,14976,14797],{"class":3077},[3022,14978,3527],{"class":3028},[3022,14980,14981,14983,14985,14987],{"class":3024,"line":10205},[3022,14982,14972],{"class":3077},[3022,14984,3103],{"class":3028},[3022,14986,10167],{"class":3077},[3022,14988,3527],{"class":3028},[3022,14990,14991,14993],{"class":3024,"line":10210},[3022,14992,7858],{"class":3077},[3022,14994,3120],{"class":3028},[3022,14996,14997],{"class":3024,"line":10229},[3022,14998,4042],{"emptyLinePlaceholder":4041},[3022,15000,15001,15003,15005,15007,15009],{"class":3024,"line":10243},[3022,15002,5524],{"class":3049},[3022,15004,3056],{"class":3077},[3022,15006,3103],{"class":3028},[3022,15008,5531],{"class":3077},[3022,15010,4315],{"class":3028},[3022,15012,15013],{"class":3024,"line":10248},[3022,15014,4036],{"class":3028},[3022,15016,15017],{"class":3024,"line":10289},[3022,15018,3381],{"class":3028},[2964,15020,15021],{},[2974,15022,15023],{},"4. Tenant-Aware Rate Limiting Filter:",[3012,15025,15027],{"className":3014,"code":15026,"language":3016,"meta":3017,"style":3017},"public class TenantRateLimitingFilter : IAsyncActionFilter\n{\n    private static readonly Dictionary\u003Cstring, Queue\u003CDateTime>> _requestHistory = new();\n    private static readonly object _lock = new();\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var tenant = context.HttpContext.Items[\"Tenant\"] as TenantInfo;\n        \n        if (tenant is null)\n        {\n            await next();\n            return;\n        }\n\n        lock (_lock)\n        {\n            if (!_requestHistory.ContainsKey(tenant.TenantId))\n            {\n                _requestHistory[tenant.TenantId] = new Queue\u003CDateTime>();\n            }\n\n            var history = _requestHistory[tenant.TenantId];\n            var now = DateTime.UtcNow;\n            var oneMinuteAgo = now.AddMinutes(-1);\n\n            // Видаляємо старі записи\n            while (history.Count > 0 && history.Peek() \u003C oneMinuteAgo)\n            {\n                history.Dequeue();\n            }\n\n            if (history.Count >= tenant.RateLimitPerMinute)\n            {\n                var retryAfter = (int)(history.Peek().AddMinutes(1) - now).TotalSeconds;\n                \n                context.HttpContext.Response.Headers.Append(\"Retry-After\", retryAfter.ToString());\n                context.HttpContext.Response.Headers.Append(\"X-RateLimit-Limit\", tenant.RateLimitPerMinute.ToString());\n                context.HttpContext.Response.Headers.Append(\"X-RateLimit-Remaining\", \"0\");\n                \n                context.Result = new ObjectResult(new ProblemDetails\n                {\n                    Status = StatusCodes.Status429TooManyRequests,\n                    Title = \"Rate Limit Exceeded\",\n                    Detail = $\"Maximum {tenant.RateLimitPerMinute} requests per minute allowed for your subscription\"\n                })\n                {\n                    StatusCode = StatusCodes.Status429TooManyRequests\n                };\n                return;\n            }\n\n            history.Enqueue(now);\n            \n            // Додаємо rate limit headers\n            context.HttpContext.Response.Headers.Append(\"X-RateLimit-Limit\", tenant.RateLimitPerMinute.ToString());\n            context.HttpContext.Response.Headers.Append(\"X-RateLimit-Remaining\", (tenant.RateLimitPerMinute - history.Count).ToString());\n        }\n\n        await next();\n    }\n}\n",[3019,15028,15029,15042,15046,15078,15096,15100,15112,15120,15128,15132,15163,15167,15181,15185,15193,15199,15203,15207,15217,15221,15243,15247,15271,15275,15279,15299,15315,15335,15339,15343,15373,15377,15387,15391,15395,15418,15422,15458,15462,15496,15535,15567,15571,15591,15595,15609,15619,15640,15644,15648,15660,15664,15670,15674,15678,15692,15696,15701,15739,15787,15791,15795,15803,15807],{"__ignoreMap":3017},[3022,15030,15031,15033,15035,15038,15040],{"class":3024,"line":3025},[3022,15032,3050],{"class":3049},[3022,15034,3987],{"class":3049},[3022,15036,15037],{"class":3032}," TenantRateLimitingFilter",[3022,15039,3993],{"class":3028},[3022,15041,4101],{"class":3032},[3022,15043,15044],{"class":3024,"line":3046},[3022,15045,3087],{"class":3028},[3022,15047,15048,15050,15052,15054,15056,15058,15060,15062,15064,15066,15068,15070,15072,15074,15076],{"class":3024,"line":3084},[3022,15049,4837],{"class":3049},[3022,15051,12049],{"class":3049},[3022,15053,4859],{"class":3049},[3022,15055,12054],{"class":3032},[3022,15057,3059],{"class":3028},[3022,15059,5244],{"class":3049},[3022,15061,3114],{"class":3028},[3022,15063,12063],{"class":3032},[3022,15065,3059],{"class":3028},[3022,15067,6764],{"class":3032},[3022,15069,9915],{"class":3028},[3022,15071,12072],{"class":3077},[3022,15073,3223],{"class":3028},[3022,15075,3351],{"class":3049},[3022,15077,3200],{"class":3028},[3022,15079,15080,15082,15084,15086,15088,15090,15092,15094],{"class":3024,"line":3090},[3022,15081,4837],{"class":3049},[3022,15083,12049],{"class":3049},[3022,15085,4859],{"class":3049},[3022,15087,11226],{"class":3049},[3022,15089,12091],{"class":3077},[3022,15091,3223],{"class":3028},[3022,15093,3351],{"class":3049},[3022,15095,3200],{"class":3028},[3022,15097,15098],{"class":3024,"line":3097},[3022,15099,4042],{"emptyLinePlaceholder":4041},[3022,15101,15102,15104,15106,15108,15110],{"class":3024,"line":3123},[3022,15103,4005],{"class":3049},[3022,15105,3053],{"class":3049},[3022,15107,3056],{"class":3032},[3022,15109,4116],{"class":3068},[3022,15111,4119],{"class":3028},[3022,15113,15114,15116,15118],{"class":3024,"line":3129},[3022,15115,4124],{"class":3032},[3022,15117,4019],{"class":3077},[3022,15119,3527],{"class":3028},[3022,15121,15122,15124,15126],{"class":3024,"line":3135},[3022,15123,4133],{"class":3032},[3022,15125,4136],{"class":3077},[3022,15127,3081],{"class":3028},[3022,15129,15130],{"class":3024,"line":3170},[3022,15131,4026],{"class":3028},[3022,15133,15134,15136,15138,15140,15142,15144,15146,15148,15150,15152,15154,15156,15159,15161],{"class":3024,"line":3175},[3022,15135,4156],{"class":3049},[3022,15137,14201],{"class":3077},[3022,15139,3223],{"class":3028},[3022,15141,4996],{"class":3077},[3022,15143,3103],{"class":3028},[3022,15145,5001],{"class":3077},[3022,15147,3103],{"class":3028},[3022,15149,5497],{"class":3077},[3022,15151,3029],{"class":3028},[3022,15153,14940],{"class":3039},[3022,15155,11792],{"class":3028},[3022,15157,15158],{"class":3049},"as",[3022,15160,14161],{"class":3032},[3022,15162,4315],{"class":3028},[3022,15164,15165],{"class":3024,"line":3181},[3022,15166,4151],{"class":3028},[3022,15168,15169,15171,15173,15175,15177,15179],{"class":3024,"line":3203},[3022,15170,4991],{"class":3138},[3022,15172,3142],{"class":3028},[3022,15174,14206],{"class":3077},[3022,15176,6225],{"class":3049},[3022,15178,9973],{"class":3049},[3022,15180,3081],{"class":3028},[3022,15182,15183],{"class":3024,"line":3208},[3022,15184,5040],{"class":3028},[3022,15186,15187,15189,15191],{"class":3024,"line":3214},[3022,15188,12213],{"class":3049},[3022,15190,4136],{"class":3068},[3022,15192,3200],{"class":3028},[3022,15194,15195,15197],{"class":3024,"line":3236},[3022,15196,5192],{"class":3138},[3022,15198,4315],{"class":3028},[3022,15200,15201],{"class":3024,"line":3241},[3022,15202,5199],{"class":3028},[3022,15204,15205],{"class":3024,"line":3247},[3022,15206,4042],{"emptyLinePlaceholder":4041},[3022,15208,15209,15211,15213,15215],{"class":3024,"line":3279},[3022,15210,12236],{"class":3138},[3022,15212,3142],{"class":3028},[3022,15214,12241],{"class":3077},[3022,15216,3081],{"class":3028},[3022,15218,15219],{"class":3024,"line":3284},[3022,15220,5040],{"class":3028},[3022,15222,15223,15225,15227,15229,15231,15233,15235,15237,15239,15241],{"class":3024,"line":3290},[3022,15224,6217],{"class":3138},[3022,15226,3186],{"class":3028},[3022,15228,12072],{"class":3077},[3022,15230,3103],{"class":3028},[3022,15232,12260],{"class":3068},[3022,15234,3036],{"class":3028},[3022,15236,14206],{"class":3077},[3022,15238,3103],{"class":3028},[3022,15240,14797],{"class":3077},[3022,15242,5035],{"class":3028},[3022,15244,15245],{"class":3024,"line":3303},[3022,15246,5107],{"class":3028},[3022,15248,15249,15251,15253,15255,15257,15259,15261,15263,15265,15267,15269],{"class":3024,"line":3329},[3022,15250,12275],{"class":3077},[3022,15252,3029],{"class":3028},[3022,15254,14206],{"class":3077},[3022,15256,3103],{"class":3028},[3022,15258,14797],{"class":3077},[3022,15260,5505],{"class":3028},[3022,15262,3351],{"class":3049},[3022,15264,12286],{"class":3032},[3022,15266,3059],{"class":3028},[3022,15268,6764],{"class":3032},[3022,15270,5262],{"class":3028},[3022,15272,15273],{"class":3024,"line":3334},[3022,15274,6387],{"class":3028},[3022,15276,15277],{"class":3024,"line":3340},[3022,15278,4042],{"emptyLinePlaceholder":4041},[3022,15280,15281,15283,15285,15287,15289,15291,15293,15295,15297],{"class":3024,"line":3378},[3022,15282,5927],{"class":3049},[3022,15284,12307],{"class":3077},[3022,15286,3223],{"class":3028},[3022,15288,12072],{"class":3077},[3022,15290,3029],{"class":3028},[3022,15292,14206],{"class":3077},[3022,15294,3103],{"class":3028},[3022,15296,14797],{"class":3077},[3022,15298,12318],{"class":3028},[3022,15300,15301,15303,15305,15307,15309,15311,15313],{"class":3024,"line":3803},[3022,15302,5927],{"class":3049},[3022,15304,12325],{"class":3077},[3022,15306,3223],{"class":3028},[3022,15308,6764],{"class":3077},[3022,15310,3103],{"class":3028},[3022,15312,6769],{"class":3077},[3022,15314,4315],{"class":3028},[3022,15316,15317,15319,15321,15323,15325,15327,15329,15331,15333],{"class":3024,"line":3809},[3022,15318,5927],{"class":3049},[3022,15320,12342],{"class":3077},[3022,15322,3223],{"class":3028},[3022,15324,12347],{"class":3077},[3022,15326,3103],{"class":3028},[3022,15328,12352],{"class":3068},[3022,15330,12355],{"class":3028},[3022,15332,3543],{"class":3150},[3022,15334,3120],{"class":3028},[3022,15336,15337],{"class":3024,"line":4632},[3022,15338,4042],{"emptyLinePlaceholder":4041},[3022,15340,15341],{"class":3024,"line":4658},[3022,15342,12368],{"class":3093},[3022,15344,15345,15347,15349,15351,15353,15355,15357,15359,15361,15363,15365,15367,15369,15371],{"class":3024,"line":4677},[3022,15346,12373],{"class":3138},[3022,15348,3142],{"class":3028},[3022,15350,12378],{"class":3077},[3022,15352,3103],{"class":3028},[3022,15354,5971],{"class":3077},[3022,15356,5974],{"class":3028},[3022,15358,3151],{"class":3150},[3022,15360,12389],{"class":3028},[3022,15362,12378],{"class":3077},[3022,15364,3103],{"class":3028},[3022,15366,12396],{"class":3068},[3022,15368,12399],{"class":3028},[3022,15370,12402],{"class":3077},[3022,15372,3081],{"class":3028},[3022,15374,15375],{"class":3024,"line":4682},[3022,15376,5107],{"class":3028},[3022,15378,15379,15381,15383,15385],{"class":3024,"line":4712},[3022,15380,12413],{"class":3077},[3022,15382,3103],{"class":3028},[3022,15384,12418],{"class":3068},[3022,15386,3200],{"class":3028},[3022,15388,15389],{"class":3024,"line":4731},[3022,15390,6387],{"class":3028},[3022,15392,15393],{"class":3024,"line":5202},[3022,15394,4042],{"emptyLinePlaceholder":4041},[3022,15396,15397,15399,15401,15403,15405,15407,15409,15411,15413,15416],{"class":3024,"line":5207},[3022,15398,6217],{"class":3138},[3022,15400,3142],{"class":3028},[3022,15402,12378],{"class":3077},[3022,15404,3103],{"class":3028},[3022,15406,5971],{"class":3077},[3022,15408,6661],{"class":3028},[3022,15410,14206],{"class":3077},[3022,15412,3103],{"class":3028},[3022,15414,15415],{"class":3077},"RateLimitPerMinute",[3022,15417,3081],{"class":3028},[3022,15419,15420],{"class":3024,"line":5213},[3022,15421,5107],{"class":3028},[3022,15423,15424,15426,15428,15430,15432,15434,15436,15438,15440,15442,15444,15446,15448,15450,15452,15454,15456],{"class":3024,"line":5265},[3022,15425,12456],{"class":3049},[3022,15427,12459],{"class":3077},[3022,15429,12462],{"class":3028},[3022,15431,3074],{"class":3049},[3022,15433,12467],{"class":3028},[3022,15435,12378],{"class":3077},[3022,15437,3103],{"class":3028},[3022,15439,12396],{"class":3068},[3022,15441,7368],{"class":3028},[3022,15443,12352],{"class":3068},[3022,15445,3036],{"class":3028},[3022,15447,3543],{"class":3150},[3022,15449,12484],{"class":3028},[3022,15451,12347],{"class":3077},[3022,15453,5236],{"class":3028},[3022,15455,12491],{"class":3077},[3022,15457,4315],{"class":3028},[3022,15459,15460],{"class":3024,"line":5270},[3022,15461,12498],{"class":3028},[3022,15463,15464,15466,15468,15470,15472,15474,15476,15478,15480,15482,15484,15486,15488,15490,15492,15494],{"class":3024,"line":5276},[3022,15465,5830],{"class":3077},[3022,15467,3103],{"class":3028},[3022,15469,5001],{"class":3077},[3022,15471,3103],{"class":3028},[3022,15473,7394],{"class":3077},[3022,15475,3103],{"class":3028},[3022,15477,5011],{"class":3077},[3022,15479,3103],{"class":3028},[3022,15481,7403],{"class":3068},[3022,15483,3036],{"class":3028},[3022,15485,12523],{"class":3039},[3022,15487,3114],{"class":3028},[3022,15489,12528],{"class":3077},[3022,15491,3103],{"class":3028},[3022,15493,5298],{"class":3068},[3022,15495,7968],{"class":3028},[3022,15497,15498,15500,15502,15504,15506,15508,15510,15512,15514,15516,15518,15521,15523,15525,15527,15529,15531,15533],{"class":3024,"line":5304},[3022,15499,5830],{"class":3077},[3022,15501,3103],{"class":3028},[3022,15503,5001],{"class":3077},[3022,15505,3103],{"class":3028},[3022,15507,7394],{"class":3077},[3022,15509,3103],{"class":3028},[3022,15511,5011],{"class":3077},[3022,15513,3103],{"class":3028},[3022,15515,7403],{"class":3068},[3022,15517,3036],{"class":3028},[3022,15519,15520],{"class":3039},"\"X-RateLimit-Limit\"",[3022,15522,3114],{"class":3028},[3022,15524,14206],{"class":3077},[3022,15526,3103],{"class":3028},[3022,15528,15415],{"class":3077},[3022,15530,3103],{"class":3028},[3022,15532,5298],{"class":3068},[3022,15534,7968],{"class":3028},[3022,15536,15537,15539,15541,15543,15545,15547,15549,15551,15553,15555,15557,15560,15562,15565],{"class":3024,"line":5309},[3022,15538,5830],{"class":3077},[3022,15540,3103],{"class":3028},[3022,15542,5001],{"class":3077},[3022,15544,3103],{"class":3028},[3022,15546,7394],{"class":3077},[3022,15548,3103],{"class":3028},[3022,15550,5011],{"class":3077},[3022,15552,3103],{"class":3028},[3022,15554,7403],{"class":3068},[3022,15556,3036],{"class":3028},[3022,15558,15559],{"class":3039},"\"X-RateLimit-Remaining\"",[3022,15561,3114],{"class":3028},[3022,15563,15564],{"class":3039},"\"0\"",[3022,15566,3120],{"class":3028},[3022,15568,15569],{"class":3024,"line":5329},[3022,15570,12498],{"class":3028},[3022,15572,15573,15575,15577,15579,15581,15583,15585,15587,15589],{"class":3024,"line":5334},[3022,15574,5830],{"class":3077},[3022,15576,3103],{"class":3028},[3022,15578,3921],{"class":3077},[3022,15580,3223],{"class":3028},[3022,15582,3351],{"class":3049},[3022,15584,6642],{"class":3032},[3022,15586,3036],{"class":3028},[3022,15588,3351],{"class":3049},[3022,15590,5102],{"class":3032},[3022,15592,15593],{"class":3024,"line":5355},[3022,15594,6275],{"class":3028},[3022,15596,15597,15599,15601,15603,15605,15607],{"class":3024,"line":5360},[3022,15598,12567],{"class":3077},[3022,15600,3223],{"class":3028},[3022,15602,5117],{"class":3077},[3022,15604,3103],{"class":3028},[3022,15606,12576],{"class":3077},[3022,15608,3527],{"class":3028},[3022,15610,15611,15613,15615,15617],{"class":3024,"line":5375},[3022,15612,12583],{"class":3077},[3022,15614,3223],{"class":3028},[3022,15616,12588],{"class":3039},[3022,15618,3527],{"class":3028},[3022,15620,15621,15623,15625,15627,15629,15631,15633,15635,15637],{"class":3024,"line":5387},[3022,15622,12595],{"class":3077},[3022,15624,3223],{"class":3028},[3022,15626,12600],{"class":3039},[3022,15628,5150],{"class":5149},[3022,15630,14206],{"class":3077},[3022,15632,3103],{"class":5149},[3022,15634,15415],{"class":3077},[3022,15636,5155],{"class":5149},[3022,15638,15639],{"class":3039}," requests per minute allowed for your subscription\"\n",[3022,15641,15642],{"class":3024,"line":5399},[3022,15643,12614],{"class":3028},[3022,15645,15646],{"class":3024,"line":5420},[3022,15647,6275],{"class":3028},[3022,15649,15650,15652,15654,15656,15658],{"class":3024,"line":5425},[3022,15651,12623],{"class":3077},[3022,15653,3223],{"class":3028},[3022,15655,5117],{"class":3077},[3022,15657,3103],{"class":3028},[3022,15659,12632],{"class":3077},[3022,15661,15662],{"class":3024,"line":5432},[3022,15663,12637],{"class":3028},[3022,15665,15666,15668],{"class":3024,"line":5437},[3022,15667,12642],{"class":3138},[3022,15669,4315],{"class":3028},[3022,15671,15672],{"class":3024,"line":5442},[3022,15673,6387],{"class":3028},[3022,15675,15676],{"class":3024,"line":5474},[3022,15677,4042],{"emptyLinePlaceholder":4041},[3022,15679,15680,15682,15684,15686,15688,15690],{"class":3024,"line":5479},[3022,15681,12657],{"class":3077},[3022,15683,3103],{"class":3028},[3022,15685,12662],{"class":3068},[3022,15687,3036],{"class":3028},[3022,15689,12347],{"class":3077},[3022,15691,3120],{"class":3028},[3022,15693,15694],{"class":3024,"line":5485},[3022,15695,5079],{"class":3028},[3022,15697,15698],{"class":3024,"line":5516},[3022,15699,15700],{"class":3093},"            // Додаємо rate limit headers\n",[3022,15702,15703,15705,15707,15709,15711,15713,15715,15717,15719,15721,15723,15725,15727,15729,15731,15733,15735,15737],{"class":3024,"line":5521},[3022,15704,5084],{"class":3077},[3022,15706,3103],{"class":3028},[3022,15708,5001],{"class":3077},[3022,15710,3103],{"class":3028},[3022,15712,7394],{"class":3077},[3022,15714,3103],{"class":3028},[3022,15716,5011],{"class":3077},[3022,15718,3103],{"class":3028},[3022,15720,7403],{"class":3068},[3022,15722,3036],{"class":3028},[3022,15724,15520],{"class":3039},[3022,15726,3114],{"class":3028},[3022,15728,14206],{"class":3077},[3022,15730,3103],{"class":3028},[3022,15732,15415],{"class":3077},[3022,15734,3103],{"class":3028},[3022,15736,5298],{"class":3068},[3022,15738,7968],{"class":3028},[3022,15740,15741,15743,15745,15747,15749,15751,15753,15755,15757,15759,15761,15763,15766,15768,15770,15772,15775,15777,15779,15781,15783,15785],{"class":3024,"line":5536},[3022,15742,5084],{"class":3077},[3022,15744,3103],{"class":3028},[3022,15746,5001],{"class":3077},[3022,15748,3103],{"class":3028},[3022,15750,7394],{"class":3077},[3022,15752,3103],{"class":3028},[3022,15754,5011],{"class":3077},[3022,15756,3103],{"class":3028},[3022,15758,7403],{"class":3068},[3022,15760,3036],{"class":3028},[3022,15762,15559],{"class":3039},[3022,15764,15765],{"class":3028},", (",[3022,15767,14206],{"class":3077},[3022,15769,3103],{"class":3028},[3022,15771,15415],{"class":3077},[3022,15773,15774],{"class":3028}," - ",[3022,15776,12378],{"class":3077},[3022,15778,3103],{"class":3028},[3022,15780,5971],{"class":3077},[3022,15782,5236],{"class":3028},[3022,15784,5298],{"class":3068},[3022,15786,7968],{"class":3028},[3022,15788,15789],{"class":3024,"line":5541},[3022,15790,5199],{"class":3028},[3022,15792,15793],{"class":3024,"line":6364},[3022,15794,4042],{"emptyLinePlaceholder":4041},[3022,15796,15797,15799,15801],{"class":3024,"line":6370},[3022,15798,5524],{"class":3049},[3022,15800,4136],{"class":3068},[3022,15802,3200],{"class":3028},[3022,15804,15805],{"class":3024,"line":6378},[3022,15806,4036],{"class":3028},[3022,15808,15809],{"class":3024,"line":6384},[3022,15810,3381],{"class":3028},[2964,15812,15813],{},[2974,15814,15815],{},"5. Реєстрація:",[3012,15817,15819],{"className":3014,"code":15818,"language":3016,"meta":3017,"style":3017},"// Program.cs\nbuilder.Services.AddSingleton\u003CITenantService, TenantService>();\nbuilder.Services.AddScoped\u003CMultiTenantAuthFilter>();\nbuilder.Services.AddScoped\u003CTenantRateLimitingFilter>();\n\nbuilder.Services.AddControllers(options =>\n{\n    options.Filters.Add\u003CMultiTenantAuthFilter>();\n    options.Filters.Add\u003CTenantRateLimitingFilter>();\n});\n",[3019,15820,15821,15825,15849,15867,15886,15890,15908,15912,15930,15948],{"__ignoreMap":3017},[3022,15822,15823],{"class":3024,"line":3025},[3022,15824,8216],{"class":3093},[3022,15826,15827,15829,15831,15833,15835,15838,15840,15842,15844,15847],{"class":3024,"line":3046},[3022,15828,8221],{"class":3077},[3022,15830,3103],{"class":3028},[3022,15832,8226],{"class":3077},[3022,15834,3103],{"class":3028},[3022,15836,15837],{"class":3068},"AddSingleton",[3022,15839,3059],{"class":3028},[3022,15841,14311],{"class":3032},[3022,15843,3114],{"class":3028},[3022,15845,15846],{"class":3032},"TenantService",[3022,15848,5262],{"class":3028},[3022,15850,15851,15853,15855,15857,15859,15861,15863,15865],{"class":3024,"line":3084},[3022,15852,8221],{"class":3077},[3022,15854,3103],{"class":3028},[3022,15856,8226],{"class":3077},[3022,15858,3103],{"class":3028},[3022,15860,8351],{"class":3068},[3022,15862,3059],{"class":3028},[3022,15864,14290],{"class":3032},[3022,15866,5262],{"class":3028},[3022,15868,15869,15871,15873,15875,15877,15879,15881,15884],{"class":3024,"line":3090},[3022,15870,8221],{"class":3077},[3022,15872,3103],{"class":3028},[3022,15874,8226],{"class":3077},[3022,15876,3103],{"class":3028},[3022,15878,8351],{"class":3068},[3022,15880,3059],{"class":3028},[3022,15882,15883],{"class":3032},"TenantRateLimitingFilter",[3022,15885,5262],{"class":3028},[3022,15887,15888],{"class":3024,"line":3097},[3022,15889,4042],{"emptyLinePlaceholder":4041},[3022,15891,15892,15894,15896,15898,15900,15902,15904,15906],{"class":3024,"line":3123},[3022,15893,8221],{"class":3077},[3022,15895,3103],{"class":3028},[3022,15897,8226],{"class":3077},[3022,15899,3103],{"class":3028},[3022,15901,8231],{"class":3068},[3022,15903,3036],{"class":3028},[3022,15905,8236],{"class":3077},[3022,15907,8239],{"class":3028},[3022,15909,15910],{"class":3024,"line":3129},[3022,15911,3087],{"class":3028},[3022,15913,15914,15916,15918,15920,15922,15924,15926,15928],{"class":3024,"line":3135},[3022,15915,8253],{"class":3077},[3022,15917,3103],{"class":3028},[3022,15919,4792],{"class":3077},[3022,15921,3103],{"class":3028},[3022,15923,8262],{"class":3068},[3022,15925,3059],{"class":3028},[3022,15927,14290],{"class":3032},[3022,15929,5262],{"class":3028},[3022,15931,15932,15934,15936,15938,15940,15942,15944,15946],{"class":3024,"line":3170},[3022,15933,8253],{"class":3077},[3022,15935,3103],{"class":3028},[3022,15937,4792],{"class":3077},[3022,15939,3103],{"class":3028},[3022,15941,8262],{"class":3068},[3022,15943,3059],{"class":3028},[3022,15945,15883],{"class":3032},[3022,15947,5262],{"class":3028},[3022,15949,15950],{"class":3024,"line":3175},[3022,15951,8329],{"class":3028},[2964,15953,15954],{},[2974,15955,15956],{},"6. Використання у контролері:",[3012,15958,15960],{"className":3014,"code":15959,"language":3016,"meta":3017,"style":3017},"[ApiController]\n[Route(\"api/[controller]\")]\npublic class ProductsController : ControllerBase\n{\n    [HttpGet]\n    public IActionResult GetAll()\n    {\n        var tenant = HttpContext.Items[\"Tenant\"] as TenantInfo;\n        \n        // Фільтруємо продукти за tenant\n        var products = GetProductsForTenant(tenant!.TenantId);\n        \n        return Ok(products);\n    }\n}\n",[3019,15961,15962,15970,15982,15994,15998,16006,16018,16022,16048,16052,16057,16078,16082,16094,16098],{"__ignoreMap":3017},[3022,15963,15964,15966,15968],{"class":3024,"line":3025},[3022,15965,3029],{"class":3028},[3022,15967,8445],{"class":3032},[3022,15969,4390],{"class":3028},[3022,15971,15972,15974,15976,15978,15980],{"class":3024,"line":3046},[3022,15973,3029],{"class":3028},[3022,15975,8454],{"class":3032},[3022,15977,3036],{"class":3028},[3022,15979,8459],{"class":3039},[3022,15981,3043],{"class":3028},[3022,15983,15984,15986,15988,15990,15992],{"class":3024,"line":3084},[3022,15985,3050],{"class":3049},[3022,15987,3987],{"class":3049},[3022,15989,8492],{"class":3032},[3022,15991,3993],{"class":3028},[3022,15993,8497],{"class":3032},[3022,15995,15996],{"class":3024,"line":3090},[3022,15997,3087],{"class":3028},[3022,15999,16000,16002,16004],{"class":3024,"line":3097},[3022,16001,4384],{"class":3028},[3022,16003,3033],{"class":3032},[3022,16005,4390],{"class":3028},[3022,16007,16008,16010,16013,16016],{"class":3024,"line":3123},[3022,16009,4005],{"class":3049},[3022,16011,16012],{"class":3032}," IActionResult",[3022,16014,16015],{"class":3068}," GetAll",[3022,16017,6045],{"class":3028},[3022,16019,16020],{"class":3024,"line":3129},[3022,16021,4026],{"class":3028},[3022,16023,16024,16026,16028,16030,16032,16034,16036,16038,16040,16042,16044,16046],{"class":3024,"line":3135},[3022,16025,4156],{"class":3049},[3022,16027,14201],{"class":3077},[3022,16029,3223],{"class":3028},[3022,16031,5001],{"class":3077},[3022,16033,3103],{"class":3028},[3022,16035,5497],{"class":3077},[3022,16037,3029],{"class":3028},[3022,16039,14940],{"class":3039},[3022,16041,11792],{"class":3028},[3022,16043,15158],{"class":3049},[3022,16045,14161],{"class":3032},[3022,16047,4315],{"class":3028},[3022,16049,16050],{"class":3024,"line":3170},[3022,16051,4151],{"class":3028},[3022,16053,16054],{"class":3024,"line":3175},[3022,16055,16056],{"class":3093},"        // Фільтруємо продукти за tenant\n",[3022,16058,16059,16061,16063,16065,16068,16070,16072,16074,16076],{"class":3024,"line":3181},[3022,16060,4156],{"class":3049},[3022,16062,9745],{"class":3077},[3022,16064,3223],{"class":3028},[3022,16066,16067],{"class":3068},"GetProductsForTenant",[3022,16069,3036],{"class":3028},[3022,16071,14206],{"class":3077},[3022,16073,6019],{"class":3028},[3022,16075,14797],{"class":3077},[3022,16077,3120],{"class":3028},[3022,16079,16080],{"class":3024,"line":3203},[3022,16081,4151],{"class":3028},[3022,16083,16084,16086,16088,16090,16092],{"class":3024,"line":3208},[3022,16085,9795],{"class":3138},[3022,16087,3346],{"class":3068},[3022,16089,3036],{"class":3028},[3022,16091,9802],{"class":3077},[3022,16093,3120],{"class":3028},[3022,16095,16096],{"class":3024,"line":3214},[3022,16097,4036],{"class":3028},[3022,16099,16100],{"class":3024,"line":3236},[3022,16101,3381],{"class":3028},[2964,16103,16104],{},[2974,16105,7067],{},[3012,16107,16109],{"className":3446,"code":16108,"language":3448,"meta":3017,"style":3017},"GET /api/products\nX-Api-Key: sk_tenant_a_abc123\n\nHTTP/1.1 200 OK\nX-RateLimit-Limit: 50\nX-RateLimit-Remaining: 49\n",[3019,16110,16111,16115,16120,16124,16129,16134],{"__ignoreMap":3017},[3022,16112,16113],{"class":3024,"line":3025},[3022,16114,3455],{},[3022,16116,16117],{"class":3024,"line":3046},[3022,16118,16119],{},"X-Api-Key: sk_tenant_a_abc123\n",[3022,16121,16122],{"class":3024,"line":3084},[3022,16123,4042],{"emptyLinePlaceholder":4041},[3022,16125,16126],{"class":3024,"line":3090},[3022,16127,16128],{},"HTTP/1.1 200 OK\n",[3022,16130,16131],{"class":3024,"line":3097},[3022,16132,16133],{},"X-RateLimit-Limit: 50\n",[3022,16135,16136],{"class":3024,"line":3123},[3022,16137,16138],{},"X-RateLimit-Remaining: 49\n",[3012,16140,16142],{"className":3446,"code":16141,"language":3448,"meta":3017,"style":3017},"GET /api/analytics\nX-Api-Key: sk_tenant_a_abc123\n\nHTTP/1.1 403 Forbidden\n{\n  \"title\": \"Access Denied\",\n  \"detail\": \"Your subscription does not include access to /api/analytics\"\n}\n",[3019,16143,16144,16149,16153,16157,16162,16166,16171,16176],{"__ignoreMap":3017},[3022,16145,16146],{"class":3024,"line":3025},[3022,16147,16148],{},"GET /api/analytics\n",[3022,16150,16151],{"class":3024,"line":3046},[3022,16152,16119],{},[3022,16154,16155],{"class":3024,"line":3084},[3022,16156,4042],{"emptyLinePlaceholder":4041},[3022,16158,16159],{"class":3024,"line":3090},[3022,16160,16161],{},"HTTP/1.1 403 Forbidden\n",[3022,16163,16164],{"class":3024,"line":3097},[3022,16165,3087],{},[3022,16167,16168],{"class":3024,"line":3123},[3022,16169,16170],{},"  \"title\": \"Access Denied\",\n",[3022,16172,16173],{"class":3024,"line":3129},[3022,16174,16175],{},"  \"detail\": \"Your subscription does not include access to /api/analytics\"\n",[3022,16177,16178],{"class":3024,"line":3135},[3022,16179,3381],{},[3423,16181,16183],{"id":16182},"завдання-32-audit-trail-filter","Завдання 3.2: Audit Trail Filter",[2964,16185,16186],{},"Створіть систему аудиту, що записує всі зміни даних (POST/PUT/DELETE) у базу даних:",[11911,16188,16189,16194,16495,16500,16766,16771,17854,17859,17953,17957],{"title":12020},[2964,16190,16191],{},[2974,16192,16193],{},"1. Модель аудиту:",[3012,16195,16197],{"className":3014,"code":16196,"language":3016,"meta":3017,"style":3017},"public class AuditLog\n{\n    public int Id { get; set; }\n    public required string TenantId { get; set; }\n    public required string UserId { get; set; }\n    public required string Action { get; set; } // CREATE, UPDATE, DELETE\n    public required string Resource { get; set; } // products, orders\n    public string? ResourceId { get; set; }\n    public string? RequestBody { get; set; }\n    public string? ResponseBody { get; set; }\n    public int StatusCode { get; set; }\n    public required string IpAddress { get; set; }\n    public required string UserAgent { get; set; }\n    public required string CorrelationId { get; set; }\n    public DateTime Timestamp { get; set; } = DateTime.UtcNow;\n}\n",[3019,16198,16199,16208,16212,16230,16250,16271,16296,16320,16341,16362,16383,16402,16423,16444,16465,16491],{"__ignoreMap":3017},[3022,16200,16201,16203,16205],{"class":3024,"line":3025},[3022,16202,3050],{"class":3049},[3022,16204,3987],{"class":3049},[3022,16206,16207],{"class":3032}," AuditLog\n",[3022,16209,16210],{"class":3024,"line":3046},[3022,16211,3087],{"class":3028},[3022,16213,16214,16216,16218,16220,16222,16224,16226,16228],{"class":3024,"line":3084},[3022,16215,4005],{"class":3049},[3022,16217,4358],{"class":3049},[3022,16219,4361],{"class":3077},[3022,16221,3354],{"class":3028},[3022,16223,4366],{"class":3049},[3022,16225,4369],{"class":3028},[3022,16227,4372],{"class":3049},[3022,16229,4375],{"class":3028},[3022,16231,16232,16234,16236,16238,16240,16242,16244,16246,16248],{"class":3024,"line":3090},[3022,16233,4005],{"class":3049},[3022,16235,4411],{"class":3049},[3022,16237,4414],{"class":3049},[3022,16239,13820],{"class":3077},[3022,16241,3354],{"class":3028},[3022,16243,4366],{"class":3049},[3022,16245,4369],{"class":3028},[3022,16247,4372],{"class":3049},[3022,16249,4375],{"class":3028},[3022,16251,16252,16254,16256,16258,16261,16263,16265,16267,16269],{"class":3024,"line":3097},[3022,16253,4005],{"class":3049},[3022,16255,4411],{"class":3049},[3022,16257,4414],{"class":3049},[3022,16259,16260],{"class":3077}," UserId",[3022,16262,3354],{"class":3028},[3022,16264,4366],{"class":3049},[3022,16266,4369],{"class":3028},[3022,16268,4372],{"class":3049},[3022,16270,4375],{"class":3028},[3022,16272,16273,16275,16277,16279,16282,16284,16286,16288,16290,16293],{"class":3024,"line":3123},[3022,16274,4005],{"class":3049},[3022,16276,4411],{"class":3049},[3022,16278,4414],{"class":3049},[3022,16280,16281],{"class":3077}," Action",[3022,16283,3354],{"class":3028},[3022,16285,4366],{"class":3049},[3022,16287,4369],{"class":3028},[3022,16289,4372],{"class":3049},[3022,16291,16292],{"class":3028},"; } ",[3022,16294,16295],{"class":3093},"// CREATE, UPDATE, DELETE\n",[3022,16297,16298,16300,16302,16304,16307,16309,16311,16313,16315,16317],{"class":3024,"line":3129},[3022,16299,4005],{"class":3049},[3022,16301,4411],{"class":3049},[3022,16303,4414],{"class":3049},[3022,16305,16306],{"class":3077}," Resource",[3022,16308,3354],{"class":3028},[3022,16310,4366],{"class":3049},[3022,16312,4369],{"class":3028},[3022,16314,4372],{"class":3049},[3022,16316,16292],{"class":3028},[3022,16318,16319],{"class":3093},"// products, orders\n",[3022,16321,16322,16324,16326,16328,16331,16333,16335,16337,16339],{"class":3024,"line":3135},[3022,16323,4005],{"class":3049},[3022,16325,4414],{"class":3049},[3022,16327,6940],{"class":3028},[3022,16329,16330],{"class":3077},"ResourceId",[3022,16332,3354],{"class":3028},[3022,16334,4366],{"class":3049},[3022,16336,4369],{"class":3028},[3022,16338,4372],{"class":3049},[3022,16340,4375],{"class":3028},[3022,16342,16343,16345,16347,16349,16352,16354,16356,16358,16360],{"class":3024,"line":3170},[3022,16344,4005],{"class":3049},[3022,16346,4414],{"class":3049},[3022,16348,6940],{"class":3028},[3022,16350,16351],{"class":3077},"RequestBody",[3022,16353,3354],{"class":3028},[3022,16355,4366],{"class":3049},[3022,16357,4369],{"class":3028},[3022,16359,4372],{"class":3049},[3022,16361,4375],{"class":3028},[3022,16363,16364,16366,16368,16370,16373,16375,16377,16379,16381],{"class":3024,"line":3175},[3022,16365,4005],{"class":3049},[3022,16367,4414],{"class":3049},[3022,16369,6940],{"class":3028},[3022,16371,16372],{"class":3077},"ResponseBody",[3022,16374,3354],{"class":3028},[3022,16376,4366],{"class":3049},[3022,16378,4369],{"class":3028},[3022,16380,4372],{"class":3049},[3022,16382,4375],{"class":3028},[3022,16384,16385,16387,16389,16392,16394,16396,16398,16400],{"class":3024,"line":3181},[3022,16386,4005],{"class":3049},[3022,16388,4358],{"class":3049},[3022,16390,16391],{"class":3077}," StatusCode",[3022,16393,3354],{"class":3028},[3022,16395,4366],{"class":3049},[3022,16397,4369],{"class":3028},[3022,16399,4372],{"class":3049},[3022,16401,4375],{"class":3028},[3022,16403,16404,16406,16408,16410,16413,16415,16417,16419,16421],{"class":3024,"line":3203},[3022,16405,4005],{"class":3049},[3022,16407,4411],{"class":3049},[3022,16409,4414],{"class":3049},[3022,16411,16412],{"class":3077}," IpAddress",[3022,16414,3354],{"class":3028},[3022,16416,4366],{"class":3049},[3022,16418,4369],{"class":3028},[3022,16420,4372],{"class":3049},[3022,16422,4375],{"class":3028},[3022,16424,16425,16427,16429,16431,16434,16436,16438,16440,16442],{"class":3024,"line":3208},[3022,16426,4005],{"class":3049},[3022,16428,4411],{"class":3049},[3022,16430,4414],{"class":3049},[3022,16432,16433],{"class":3077}," UserAgent",[3022,16435,3354],{"class":3028},[3022,16437,4366],{"class":3049},[3022,16439,4369],{"class":3028},[3022,16441,4372],{"class":3049},[3022,16443,4375],{"class":3028},[3022,16445,16446,16448,16450,16452,16455,16457,16459,16461,16463],{"class":3024,"line":3214},[3022,16447,4005],{"class":3049},[3022,16449,4411],{"class":3049},[3022,16451,4414],{"class":3049},[3022,16453,16454],{"class":3077}," CorrelationId",[3022,16456,3354],{"class":3028},[3022,16458,4366],{"class":3049},[3022,16460,4369],{"class":3028},[3022,16462,4372],{"class":3049},[3022,16464,4375],{"class":3028},[3022,16466,16467,16469,16471,16473,16475,16477,16479,16481,16483,16485,16487,16489],{"class":3024,"line":3236},[3022,16468,4005],{"class":3049},[3022,16470,7002],{"class":3032},[3022,16472,7005],{"class":3077},[3022,16474,3354],{"class":3028},[3022,16476,4366],{"class":3049},[3022,16478,4369],{"class":3028},[3022,16480,4372],{"class":3049},[3022,16482,4539],{"class":3028},[3022,16484,6764],{"class":3077},[3022,16486,3103],{"class":3028},[3022,16488,6769],{"class":3077},[3022,16490,4315],{"class":3028},[3022,16492,16493],{"class":3024,"line":3241},[3022,16494,3381],{"class":3028},[2964,16496,16497],{},[2974,16498,16499],{},"2. Audit Service:",[3012,16501,16503],{"className":3014,"code":16502,"language":3016,"meta":3017,"style":3017},"public interface IAuditService\n{\n    Task LogAsync(AuditLog log);\n}\n\npublic class AuditService : IAuditService\n{\n    private readonly ILogger\u003CAuditService> _logger;\n    // У production: inject DbContext\n\n    public AuditService(ILogger\u003CAuditService> logger)\n    {\n        _logger = logger;\n    }\n\n    public async Task LogAsync(AuditLog log)\n    {\n        // У production: зберігаємо у БД\n        _logger.LogInformation(\n            \"AUDIT: {Action} {Resource}/{ResourceId} by {UserId} ({TenantId}) - {StatusCode}\",\n            log.Action,\n            log.Resource,\n            log.ResourceId ?? \"N/A\",\n            log.UserId,\n            log.TenantId,\n            log.StatusCode);\n\n        await Task.CompletedTask;\n    }\n}\n",[3019,16504,16505,16514,16518,16536,16540,16544,16558,16562,16581,16586,16590,16610,16614,16624,16628,16632,16650,16654,16659,16669,16676,16687,16697,16711,16722,16732,16742,16746,16758,16762],{"__ignoreMap":3017},[3022,16506,16507,16509,16511],{"class":3024,"line":3025},[3022,16508,3050],{"class":3049},[3022,16510,13918],{"class":3049},[3022,16512,16513],{"class":3032}," IAuditService\n",[3022,16515,16516],{"class":3024,"line":3046},[3022,16517,3087],{"class":3028},[3022,16519,16520,16523,16526,16528,16531,16534],{"class":3024,"line":3084},[3022,16521,16522],{"class":3032},"    Task",[3022,16524,16525],{"class":3068}," LogAsync",[3022,16527,3036],{"class":3028},[3022,16529,16530],{"class":3032},"AuditLog",[3022,16532,16533],{"class":3077}," log",[3022,16535,3120],{"class":3028},[3022,16537,16538],{"class":3024,"line":3090},[3022,16539,3381],{"class":3028},[3022,16541,16542],{"class":3024,"line":3097},[3022,16543,4042],{"emptyLinePlaceholder":4041},[3022,16545,16546,16548,16550,16553,16555],{"class":3024,"line":3123},[3022,16547,3050],{"class":3049},[3022,16549,3987],{"class":3049},[3022,16551,16552],{"class":3032}," AuditService",[3022,16554,3993],{"class":3028},[3022,16556,16557],{"class":3032},"IAuditService\n",[3022,16559,16560],{"class":3024,"line":3129},[3022,16561,3087],{"class":3028},[3022,16563,16564,16566,16568,16570,16572,16575,16577,16579],{"class":3024,"line":3135},[3022,16565,4837],{"class":3049},[3022,16567,4859],{"class":3049},[3022,16569,4876],{"class":3032},[3022,16571,3059],{"class":3028},[3022,16573,16574],{"class":3032},"AuditService",[3022,16576,3065],{"class":3028},[3022,16578,4886],{"class":3077},[3022,16580,4315],{"class":3028},[3022,16582,16583],{"class":3024,"line":3170},[3022,16584,16585],{"class":3093},"    // У production: inject DbContext\n",[3022,16587,16588],{"class":3024,"line":3175},[3022,16589,4042],{"emptyLinePlaceholder":4041},[3022,16591,16592,16594,16596,16598,16600,16602,16604,16606,16608],{"class":3024,"line":3181},[3022,16593,4005],{"class":3049},[3022,16595,16552],{"class":3068},[3022,16597,3036],{"class":3028},[3022,16599,4911],{"class":3032},[3022,16601,3059],{"class":3028},[3022,16603,16574],{"class":3032},[3022,16605,3065],{"class":3028},[3022,16607,4920],{"class":3077},[3022,16609,3081],{"class":3028},[3022,16611,16612],{"class":3024,"line":3203},[3022,16613,4026],{"class":3028},[3022,16615,16616,16618,16620,16622],{"class":3024,"line":3208},[3022,16617,4943],{"class":3077},[3022,16619,3223],{"class":3028},[3022,16621,4920],{"class":3077},[3022,16623,4315],{"class":3028},[3022,16625,16626],{"class":3024,"line":3214},[3022,16627,4036],{"class":3028},[3022,16629,16630],{"class":3024,"line":3236},[3022,16631,4042],{"emptyLinePlaceholder":4041},[3022,16633,16634,16636,16638,16640,16642,16644,16646,16648],{"class":3024,"line":3241},[3022,16635,4005],{"class":3049},[3022,16637,3053],{"class":3049},[3022,16639,3056],{"class":3032},[3022,16641,16525],{"class":3068},[3022,16643,3036],{"class":3028},[3022,16645,16530],{"class":3032},[3022,16647,16533],{"class":3077},[3022,16649,3081],{"class":3028},[3022,16651,16652],{"class":3024,"line":3247},[3022,16653,4026],{"class":3028},[3022,16655,16656],{"class":3024,"line":3279},[3022,16657,16658],{"class":3093},"        // У production: зберігаємо у БД\n",[3022,16660,16661,16663,16665,16667],{"class":3024,"line":3284},[3022,16662,4943],{"class":3077},[3022,16664,3103],{"class":3028},[3022,16666,3106],{"class":3068},[3022,16668,4119],{"class":3028},[3022,16670,16671,16674],{"class":3024,"line":3290},[3022,16672,16673],{"class":3039},"            \"AUDIT: {Action} {Resource}/{ResourceId} by {UserId} ({TenantId}) - {StatusCode}\"",[3022,16675,3527],{"class":3028},[3022,16677,16678,16681,16683,16685],{"class":3024,"line":3303},[3022,16679,16680],{"class":3077},"            log",[3022,16682,3103],{"class":3028},[3022,16684,3885],{"class":3077},[3022,16686,3527],{"class":3028},[3022,16688,16689,16691,16693,16695],{"class":3024,"line":3329},[3022,16690,16680],{"class":3077},[3022,16692,3103],{"class":3028},[3022,16694,3867],{"class":3077},[3022,16696,3527],{"class":3028},[3022,16698,16699,16701,16703,16705,16707,16709],{"class":3024,"line":3334},[3022,16700,16680],{"class":3077},[3022,16702,3103],{"class":3028},[3022,16704,16330],{"class":3077},[3022,16706,13122],{"class":3028},[3022,16708,12859],{"class":3039},[3022,16710,3527],{"class":3028},[3022,16712,16713,16715,16717,16720],{"class":3024,"line":3340},[3022,16714,16680],{"class":3077},[3022,16716,3103],{"class":3028},[3022,16718,16719],{"class":3077},"UserId",[3022,16721,3527],{"class":3028},[3022,16723,16724,16726,16728,16730],{"class":3024,"line":3378},[3022,16725,16680],{"class":3077},[3022,16727,3103],{"class":3028},[3022,16729,14797],{"class":3077},[3022,16731,3527],{"class":3028},[3022,16733,16734,16736,16738,16740],{"class":3024,"line":3803},[3022,16735,16680],{"class":3077},[3022,16737,3103],{"class":3028},[3022,16739,6658],{"class":3077},[3022,16741,3120],{"class":3028},[3022,16743,16744],{"class":3024,"line":3809},[3022,16745,4042],{"emptyLinePlaceholder":4041},[3022,16747,16748,16750,16752,16754,16756],{"class":3024,"line":4632},[3022,16749,5524],{"class":3049},[3022,16751,3056],{"class":3077},[3022,16753,3103],{"class":3028},[3022,16755,5531],{"class":3077},[3022,16757,4315],{"class":3028},[3022,16759,16760],{"class":3024,"line":4658},[3022,16761,4036],{"class":3028},[3022,16763,16764],{"class":3024,"line":4677},[3022,16765,3381],{"class":3028},[2964,16767,16768],{},[2974,16769,16770],{},"3. Audit Filter:",[3012,16772,16774],{"className":3014,"code":16773,"language":3016,"meta":3017,"style":3017},"public class AuditTrailFilter : IAsyncActionFilter\n{\n    private readonly IAuditService _auditService;\n\n    public AuditTrailFilter(IAuditService auditService)\n    {\n        _auditService = auditService;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        var method = context.HttpContext.Request.Method;\n\n        // Аудит тільки для модифікуючих операцій\n        if (method != \"POST\" && method != \"PUT\" && method != \"DELETE\")\n        {\n            await next();\n            return;\n        }\n\n        // Збираємо дані запиту\n        var tenant = context.HttpContext.Items[\"Tenant\"] as TenantInfo;\n        var correlationId = context.HttpContext.Items[\"CorrelationId\"]?.ToString() ?? Guid.NewGuid().ToString();\n        var requestBody = await ReadRequestBody(context.HttpContext.Request);\n\n        // Виконуємо action\n        var resultContext = await next();\n\n        // Збираємо дані відповіді\n        var statusCode = context.HttpContext.Response.StatusCode;\n        var responseBody = resultContext.Result is ObjectResult objectResult\n            ? System.Text.Json.JsonSerializer.Serialize(objectResult.Value)\n            : null;\n\n        // Визначаємо action type\n        var actionType = method switch\n        {\n            \"POST\" => \"CREATE\",\n            \"PUT\" => \"UPDATE\",\n            \"DELETE\" => \"DELETE\",\n            _ => \"UNKNOWN\"\n        };\n\n        // Витягуємо resource та ID з маршруту\n        var routeData = context.RouteData;\n        var controller = routeData.Values[\"controller\"]?.ToString()?.ToLower() ?? \"unknown\";\n        var resourceId = routeData.Values[\"id\"]?.ToString();\n\n        // Створюємо audit log\n        var auditLog = new AuditLog\n        {\n            TenantId = tenant?.TenantId ?? \"anonymous\",\n            UserId = context.HttpContext.User.Identity?.Name ?? \"anonymous\",\n            Action = actionType,\n            Resource = controller,\n            ResourceId = resourceId,\n            RequestBody = requestBody,\n            ResponseBody = responseBody,\n            StatusCode = statusCode,\n            IpAddress = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? \"unknown\",\n            UserAgent = context.HttpContext.Request.Headers.UserAgent.ToString(),\n            CorrelationId = correlationId\n        };\n\n        // Зберігаємо асинхронно (fire-and-forget для продуктивності)\n        _ = Task.Run(() => _auditService.LogAsync(auditLog));\n    }\n\n    private async Task\u003Cstring?> ReadRequestBody(HttpRequest request)\n    {\n        if (request.ContentLength == null || request.ContentLength == 0)\n            return null;\n\n        request.EnableBuffering();\n        \n        using var reader = new StreamReader(\n            request.Body,\n            encoding: System.Text.Encoding.UTF8,\n            detectEncodingFromByteOrderMarks: false,\n            leaveOpen: true);\n        \n        var body = await reader.ReadToEndAsync();\n        request.Body.Position = 0;\n        \n        return body;\n    }\n}\n",[3019,16775,16776,16789,16793,16807,16811,16827,16831,16843,16847,16851,16863,16871,16879,16883,16907,16911,16916,16950,16954,16962,16968,16972,16976,16981,17011,17051,17077,17081,17085,17099,17103,17108,17133,17154,17187,17196,17200,17205,17219,17223,17235,17247,17258,17268,17272,17276,17281,17299,17337,17362,17366,17371,17384,17388,17407,17440,17452,17464,17476,17488,17500,17512,17545,17578,17588,17592,17596,17601,17633,17637,17641,17666,17670,17700,17708,17712,17722,17726,17742,17752,17774,17784,17794,17798,17816,17834,17838,17846,17850],{"__ignoreMap":3017},[3022,16777,16778,16780,16782,16785,16787],{"class":3024,"line":3025},[3022,16779,3050],{"class":3049},[3022,16781,3987],{"class":3049},[3022,16783,16784],{"class":3032}," AuditTrailFilter",[3022,16786,3993],{"class":3028},[3022,16788,4101],{"class":3032},[3022,16790,16791],{"class":3024,"line":3046},[3022,16792,3087],{"class":3028},[3022,16794,16795,16797,16799,16802,16805],{"class":3024,"line":3084},[3022,16796,4837],{"class":3049},[3022,16798,4859],{"class":3049},[3022,16800,16801],{"class":3032}," IAuditService",[3022,16803,16804],{"class":3077}," _auditService",[3022,16806,4315],{"class":3028},[3022,16808,16809],{"class":3024,"line":3090},[3022,16810,4042],{"emptyLinePlaceholder":4041},[3022,16812,16813,16815,16817,16819,16822,16825],{"class":3024,"line":3097},[3022,16814,4005],{"class":3049},[3022,16816,16784],{"class":3068},[3022,16818,3036],{"class":3028},[3022,16820,16821],{"class":3032},"IAuditService",[3022,16823,16824],{"class":3077}," auditService",[3022,16826,3081],{"class":3028},[3022,16828,16829],{"class":3024,"line":3123},[3022,16830,4026],{"class":3028},[3022,16832,16833,16836,16838,16841],{"class":3024,"line":3129},[3022,16834,16835],{"class":3077},"        _auditService",[3022,16837,3223],{"class":3028},[3022,16839,16840],{"class":3077},"auditService",[3022,16842,4315],{"class":3028},[3022,16844,16845],{"class":3024,"line":3135},[3022,16846,4036],{"class":3028},[3022,16848,16849],{"class":3024,"line":3170},[3022,16850,4042],{"emptyLinePlaceholder":4041},[3022,16852,16853,16855,16857,16859,16861],{"class":3024,"line":3175},[3022,16854,4005],{"class":3049},[3022,16856,3053],{"class":3049},[3022,16858,3056],{"class":3032},[3022,16860,4116],{"class":3068},[3022,16862,4119],{"class":3028},[3022,16864,16865,16867,16869],{"class":3024,"line":3181},[3022,16866,4124],{"class":3032},[3022,16868,4019],{"class":3077},[3022,16870,3527],{"class":3028},[3022,16872,16873,16875,16877],{"class":3024,"line":3203},[3022,16874,4133],{"class":3032},[3022,16876,4136],{"class":3077},[3022,16878,3081],{"class":3028},[3022,16880,16881],{"class":3024,"line":3208},[3022,16882,4026],{"class":3028},[3022,16884,16885,16887,16889,16891,16893,16895,16897,16899,16901,16903,16905],{"class":3024,"line":3214},[3022,16886,4156],{"class":3049},[3022,16888,7781],{"class":3077},[3022,16890,3223],{"class":3028},[3022,16892,4996],{"class":3077},[3022,16894,3103],{"class":3028},[3022,16896,5001],{"class":3077},[3022,16898,3103],{"class":3028},[3022,16900,5006],{"class":3077},[3022,16902,3103],{"class":3028},[3022,16904,7798],{"class":3077},[3022,16906,4315],{"class":3028},[3022,16908,16909],{"class":3024,"line":3236},[3022,16910,4042],{"emptyLinePlaceholder":4041},[3022,16912,16913],{"class":3024,"line":3241},[3022,16914,16915],{"class":3093},"        // Аудит тільки для модифікуючих операцій\n",[3022,16917,16918,16920,16922,16925,16927,16930,16932,16934,16936,16939,16941,16943,16945,16948],{"class":3024,"line":3247},[3022,16919,4991],{"class":3138},[3022,16921,3142],{"class":3028},[3022,16923,16924],{"class":3077},"method",[3022,16926,6266],{"class":3028},[3022,16928,16929],{"class":3039},"\"POST\"",[3022,16931,12389],{"class":3028},[3022,16933,16924],{"class":3077},[3022,16935,6266],{"class":3028},[3022,16937,16938],{"class":3039},"\"PUT\"",[3022,16940,12389],{"class":3028},[3022,16942,16924],{"class":3077},[3022,16944,6266],{"class":3028},[3022,16946,16947],{"class":3039},"\"DELETE\"",[3022,16949,3081],{"class":3028},[3022,16951,16952],{"class":3024,"line":3279},[3022,16953,5040],{"class":3028},[3022,16955,16956,16958,16960],{"class":3024,"line":3284},[3022,16957,12213],{"class":3049},[3022,16959,4136],{"class":3068},[3022,16961,3200],{"class":3028},[3022,16963,16964,16966],{"class":3024,"line":3290},[3022,16965,5192],{"class":3138},[3022,16967,4315],{"class":3028},[3022,16969,16970],{"class":3024,"line":3303},[3022,16971,5199],{"class":3028},[3022,16973,16974],{"class":3024,"line":3329},[3022,16975,4042],{"emptyLinePlaceholder":4041},[3022,16977,16978],{"class":3024,"line":3334},[3022,16979,16980],{"class":3093},"        // Збираємо дані запиту\n",[3022,16982,16983,16985,16987,16989,16991,16993,16995,16997,16999,17001,17003,17005,17007,17009],{"class":3024,"line":3340},[3022,16984,4156],{"class":3049},[3022,16986,14201],{"class":3077},[3022,16988,3223],{"class":3028},[3022,16990,4996],{"class":3077},[3022,16992,3103],{"class":3028},[3022,16994,5001],{"class":3077},[3022,16996,3103],{"class":3028},[3022,16998,5497],{"class":3077},[3022,17000,3029],{"class":3028},[3022,17002,14940],{"class":3039},[3022,17004,11792],{"class":3028},[3022,17006,15158],{"class":3049},[3022,17008,14161],{"class":3032},[3022,17010,4315],{"class":3028},[3022,17012,17013,17015,17017,17019,17021,17023,17025,17027,17029,17031,17033,17035,17037,17039,17041,17043,17045,17047,17049],{"class":3024,"line":3378},[3022,17014,4156],{"class":3049},[3022,17016,7323],{"class":3077},[3022,17018,3223],{"class":3028},[3022,17020,4996],{"class":3077},[3022,17022,3103],{"class":3028},[3022,17024,5001],{"class":3077},[3022,17026,3103],{"class":3028},[3022,17028,5497],{"class":3077},[3022,17030,3029],{"class":3028},[3022,17032,7475],{"class":3039},[3022,17034,9711],{"class":3028},[3022,17036,5298],{"class":3068},[3022,17038,7357],{"class":3028},[3022,17040,7360],{"class":3077},[3022,17042,3103],{"class":3028},[3022,17044,7365],{"class":3068},[3022,17046,7368],{"class":3028},[3022,17048,5298],{"class":3068},[3022,17050,3200],{"class":3028},[3022,17052,17053,17055,17057,17059,17061,17063,17065,17067,17069,17071,17073,17075],{"class":3024,"line":3803},[3022,17054,4156],{"class":3049},[3022,17056,12925],{"class":3077},[3022,17058,3223],{"class":3028},[3022,17060,3257],{"class":3049},[3022,17062,12932],{"class":3068},[3022,17064,3036],{"class":3028},[3022,17066,4996],{"class":3077},[3022,17068,3103],{"class":3028},[3022,17070,5001],{"class":3077},[3022,17072,3103],{"class":3028},[3022,17074,5006],{"class":3077},[3022,17076,3120],{"class":3028},[3022,17078,17079],{"class":3024,"line":3809},[3022,17080,4042],{"emptyLinePlaceholder":4041},[3022,17082,17083],{"class":3024,"line":4632},[3022,17084,7876],{"class":3093},[3022,17086,17087,17089,17091,17093,17095,17097],{"class":3024,"line":4658},[3022,17088,4156],{"class":3049},[3022,17090,4159],{"class":3077},[3022,17092,3223],{"class":3028},[3022,17094,3257],{"class":3049},[3022,17096,4136],{"class":3068},[3022,17098,3200],{"class":3028},[3022,17100,17101],{"class":3024,"line":4677},[3022,17102,4042],{"emptyLinePlaceholder":4041},[3022,17104,17105],{"class":3024,"line":4682},[3022,17106,17107],{"class":3093},"        // Збираємо дані відповіді\n",[3022,17109,17110,17112,17115,17117,17119,17121,17123,17125,17127,17129,17131],{"class":3024,"line":4712},[3022,17111,4156],{"class":3049},[3022,17113,17114],{"class":3077}," statusCode",[3022,17116,3223],{"class":3028},[3022,17118,4996],{"class":3077},[3022,17120,3103],{"class":3028},[3022,17122,5001],{"class":3077},[3022,17124,3103],{"class":3028},[3022,17126,7394],{"class":3077},[3022,17128,3103],{"class":3028},[3022,17130,6658],{"class":3077},[3022,17132,4315],{"class":3028},[3022,17134,17135,17137,17139,17141,17143,17145,17147,17149,17151],{"class":3024,"line":4731},[3022,17136,4156],{"class":3049},[3022,17138,13051],{"class":3077},[3022,17140,3223],{"class":3028},[3022,17142,11316],{"class":3077},[3022,17144,3103],{"class":3028},[3022,17146,3921],{"class":3077},[3022,17148,6225],{"class":3049},[3022,17150,6642],{"class":3032},[3022,17152,17153],{"class":3077}," objectResult\n",[3022,17155,17156,17159,17161,17163,17165,17167,17169,17171,17173,17175,17177,17179,17181,17183,17185],{"class":3024,"line":5202},[3022,17157,17158],{"class":3028},"            ? ",[3022,17160,13056],{"class":3077},[3022,17162,3103],{"class":3028},[3022,17164,13061],{"class":3077},[3022,17166,3103],{"class":3028},[3022,17168,13066],{"class":3077},[3022,17170,3103],{"class":3028},[3022,17172,13071],{"class":3077},[3022,17174,3103],{"class":3028},[3022,17176,13076],{"class":3068},[3022,17178,3036],{"class":3028},[3022,17180,6732],{"class":3077},[3022,17182,3103],{"class":3028},[3022,17184,5961],{"class":3077},[3022,17186,3081],{"class":3028},[3022,17188,17189,17192,17194],{"class":3024,"line":5207},[3022,17190,17191],{"class":3028},"            : ",[3022,17193,13196],{"class":3049},[3022,17195,4315],{"class":3028},[3022,17197,17198],{"class":3024,"line":5213},[3022,17199,4042],{"emptyLinePlaceholder":4041},[3022,17201,17202],{"class":3024,"line":5265},[3022,17203,17204],{"class":3093},"        // Визначаємо action type\n",[3022,17206,17207,17209,17212,17214,17216],{"class":3024,"line":5270},[3022,17208,4156],{"class":3049},[3022,17210,17211],{"class":3077}," actionType",[3022,17213,3223],{"class":3028},[3022,17215,16924],{"class":3077},[3022,17217,17218],{"class":3138}," switch\n",[3022,17220,17221],{"class":3024,"line":5276},[3022,17222,5040],{"class":3028},[3022,17224,17225,17228,17230,17233],{"class":3024,"line":5304},[3022,17226,17227],{"class":3039},"            \"POST\"",[3022,17229,5886],{"class":3028},[3022,17231,17232],{"class":3039},"\"CREATE\"",[3022,17234,3527],{"class":3028},[3022,17236,17237,17240,17242,17245],{"class":3024,"line":5309},[3022,17238,17239],{"class":3039},"            \"PUT\"",[3022,17241,5886],{"class":3028},[3022,17243,17244],{"class":3039},"\"UPDATE\"",[3022,17246,3527],{"class":3028},[3022,17248,17249,17252,17254,17256],{"class":3024,"line":5329},[3022,17250,17251],{"class":3039},"            \"DELETE\"",[3022,17253,5886],{"class":3028},[3022,17255,16947],{"class":3039},[3022,17257,3527],{"class":3028},[3022,17259,17260,17263,17265],{"class":3024,"line":5334},[3022,17261,17262],{"class":3049},"            _",[3022,17264,5886],{"class":3028},[3022,17266,17267],{"class":3039},"\"UNKNOWN\"\n",[3022,17269,17270],{"class":3024,"line":5355},[3022,17271,8170],{"class":3028},[3022,17273,17274],{"class":3024,"line":5360},[3022,17275,4042],{"emptyLinePlaceholder":4041},[3022,17277,17278],{"class":3024,"line":5375},[3022,17279,17280],{"class":3093},"        // Витягуємо resource та ID з маршруту\n",[3022,17282,17283,17285,17288,17290,17292,17294,17297],{"class":3024,"line":5387},[3022,17284,4156],{"class":3049},[3022,17286,17287],{"class":3077}," routeData",[3022,17289,3223],{"class":3028},[3022,17291,4996],{"class":3077},[3022,17293,3103],{"class":3028},[3022,17295,17296],{"class":3077},"RouteData",[3022,17298,4315],{"class":3028},[3022,17300,17301,17303,17306,17308,17311,17313,17315,17317,17320,17322,17324,17327,17330,17332,17335],{"class":3024,"line":5399},[3022,17302,4156],{"class":3049},[3022,17304,17305],{"class":3077}," controller",[3022,17307,3223],{"class":3028},[3022,17309,17310],{"class":3077},"routeData",[3022,17312,3103],{"class":3028},[3022,17314,6206],{"class":3077},[3022,17316,3029],{"class":3028},[3022,17318,17319],{"class":3039},"\"controller\"",[3022,17321,9711],{"class":3028},[3022,17323,5298],{"class":3068},[3022,17325,17326],{"class":3028},"()?.",[3022,17328,17329],{"class":3068},"ToLower",[3022,17331,7357],{"class":3028},[3022,17333,17334],{"class":3039},"\"unknown\"",[3022,17336,4315],{"class":3028},[3022,17338,17339,17341,17344,17346,17348,17350,17352,17354,17356,17358,17360],{"class":3024,"line":5420},[3022,17340,4156],{"class":3049},[3022,17342,17343],{"class":3077}," resourceId",[3022,17345,3223],{"class":3028},[3022,17347,17310],{"class":3077},[3022,17349,3103],{"class":3028},[3022,17351,6206],{"class":3077},[3022,17353,3029],{"class":3028},[3022,17355,3538],{"class":3039},[3022,17357,9711],{"class":3028},[3022,17359,5298],{"class":3068},[3022,17361,3200],{"class":3028},[3022,17363,17364],{"class":3024,"line":5425},[3022,17365,4042],{"emptyLinePlaceholder":4041},[3022,17367,17368],{"class":3024,"line":5432},[3022,17369,17370],{"class":3093},"        // Створюємо audit log\n",[3022,17372,17373,17375,17378,17380,17382],{"class":3024,"line":5437},[3022,17374,4156],{"class":3049},[3022,17376,17377],{"class":3077}," auditLog",[3022,17379,3223],{"class":3028},[3022,17381,3351],{"class":3049},[3022,17383,16207],{"class":3032},[3022,17385,17386],{"class":3024,"line":5442},[3022,17387,5040],{"class":3028},[3022,17389,17390,17392,17394,17396,17398,17400,17402,17405],{"class":3024,"line":5474},[3022,17391,14024],{"class":3077},[3022,17393,3223],{"class":3028},[3022,17395,14206],{"class":3077},[3022,17397,5964],{"class":3028},[3022,17399,14797],{"class":3077},[3022,17401,13122],{"class":3028},[3022,17403,17404],{"class":3039},"\"anonymous\"",[3022,17406,3527],{"class":3028},[3022,17408,17409,17412,17414,17416,17418,17420,17422,17425,17427,17430,17432,17434,17436,17438],{"class":3024,"line":5479},[3022,17410,17411],{"class":3077},"            UserId",[3022,17413,3223],{"class":3028},[3022,17415,4996],{"class":3077},[3022,17417,3103],{"class":3028},[3022,17419,5001],{"class":3077},[3022,17421,3103],{"class":3028},[3022,17423,17424],{"class":3077},"User",[3022,17426,3103],{"class":3028},[3022,17428,17429],{"class":3077},"Identity",[3022,17431,5964],{"class":3028},[3022,17433,10167],{"class":3077},[3022,17435,13122],{"class":3028},[3022,17437,17404],{"class":3039},[3022,17439,3527],{"class":3028},[3022,17441,17442,17445,17447,17450],{"class":3024,"line":5485},[3022,17443,17444],{"class":3077},"            Action",[3022,17446,3223],{"class":3028},[3022,17448,17449],{"class":3077},"actionType",[3022,17451,3527],{"class":3028},[3022,17453,17454,17457,17459,17462],{"class":3024,"line":5516},[3022,17455,17456],{"class":3077},"            Resource",[3022,17458,3223],{"class":3028},[3022,17460,17461],{"class":3077},"controller",[3022,17463,3527],{"class":3028},[3022,17465,17466,17469,17471,17474],{"class":3024,"line":5521},[3022,17467,17468],{"class":3077},"            ResourceId",[3022,17470,3223],{"class":3028},[3022,17472,17473],{"class":3077},"resourceId",[3022,17475,3527],{"class":3028},[3022,17477,17478,17481,17483,17486],{"class":3024,"line":5536},[3022,17479,17480],{"class":3077},"            RequestBody",[3022,17482,3223],{"class":3028},[3022,17484,17485],{"class":3077},"requestBody",[3022,17487,3527],{"class":3028},[3022,17489,17490,17493,17495,17498],{"class":3024,"line":5541},[3022,17491,17492],{"class":3077},"            ResponseBody",[3022,17494,3223],{"class":3028},[3022,17496,17497],{"class":3077},"responseBody",[3022,17499,3527],{"class":3028},[3022,17501,17502,17505,17507,17510],{"class":3024,"line":6364},[3022,17503,17504],{"class":3077},"            StatusCode",[3022,17506,3223],{"class":3028},[3022,17508,17509],{"class":3077},"statusCode",[3022,17511,3527],{"class":3028},[3022,17513,17514,17517,17519,17521,17523,17525,17527,17530,17532,17535,17537,17539,17541,17543],{"class":3024,"line":6370},[3022,17515,17516],{"class":3077},"            IpAddress",[3022,17518,3223],{"class":3028},[3022,17520,4996],{"class":3077},[3022,17522,3103],{"class":3028},[3022,17524,5001],{"class":3077},[3022,17526,3103],{"class":3028},[3022,17528,17529],{"class":3077},"Connection",[3022,17531,3103],{"class":3028},[3022,17533,17534],{"class":3077},"RemoteIpAddress",[3022,17536,5964],{"class":3028},[3022,17538,5298],{"class":3068},[3022,17540,7357],{"class":3028},[3022,17542,17334],{"class":3039},[3022,17544,3527],{"class":3028},[3022,17546,17547,17550,17552,17554,17556,17558,17560,17562,17564,17566,17568,17571,17573,17575],{"class":3024,"line":6378},[3022,17548,17549],{"class":3077},"            UserAgent",[3022,17551,3223],{"class":3028},[3022,17553,4996],{"class":3077},[3022,17555,3103],{"class":3028},[3022,17557,5001],{"class":3077},[3022,17559,3103],{"class":3028},[3022,17561,5006],{"class":3077},[3022,17563,3103],{"class":3028},[3022,17565,5011],{"class":3077},[3022,17567,3103],{"class":3028},[3022,17569,17570],{"class":3077},"UserAgent",[3022,17572,3103],{"class":3028},[3022,17574,5298],{"class":3068},[3022,17576,17577],{"class":3028},"(),\n",[3022,17579,17580,17583,17585],{"class":3024,"line":6384},[3022,17581,17582],{"class":3077},"            CorrelationId",[3022,17584,3223],{"class":3028},[3022,17586,17587],{"class":3077},"correlationId\n",[3022,17589,17590],{"class":3024,"line":6390},[3022,17591,8170],{"class":3028},[3022,17593,17594],{"class":3024,"line":6395},[3022,17595,4042],{"emptyLinePlaceholder":4041},[3022,17597,17598],{"class":3024,"line":6400},[3022,17599,17600],{"class":3093},"        // Зберігаємо асинхронно (fire-and-forget для продуктивності)\n",[3022,17602,17603,17606,17608,17611,17613,17615,17618,17621,17623,17626,17628,17631],{"class":3024,"line":6428},[3022,17604,17605],{"class":3077},"        _",[3022,17607,3223],{"class":3028},[3022,17609,17610],{"class":3077},"Task",[3022,17612,3103],{"class":3028},[3022,17614,9215],{"class":3068},[3022,17616,17617],{"class":3028},"(() => ",[3022,17619,17620],{"class":3077},"_auditService",[3022,17622,3103],{"class":3028},[3022,17624,17625],{"class":3068},"LogAsync",[3022,17627,3036],{"class":3028},[3022,17629,17630],{"class":3077},"auditLog",[3022,17632,8736],{"class":3028},[3022,17634,17635],{"class":3024,"line":6433},[3022,17636,4036],{"class":3028},[3022,17638,17639],{"class":3024,"line":6439},[3022,17640,4042],{"emptyLinePlaceholder":4041},[3022,17642,17643,17645,17647,17649,17651,17653,17656,17658,17660,17662,17664],{"class":3024,"line":6448},[3022,17644,4837],{"class":3049},[3022,17646,3053],{"class":3049},[3022,17648,3056],{"class":3032},[3022,17650,3059],{"class":3028},[3022,17652,5244],{"class":3049},[3022,17654,17655],{"class":3028},"?> ",[3022,17657,13162],{"class":3068},[3022,17659,3036],{"class":3028},[3022,17661,13167],{"class":3032},[3022,17663,13170],{"class":3077},[3022,17665,3081],{"class":3028},[3022,17667,17668],{"class":3024,"line":6453},[3022,17669,4026],{"class":3028},[3022,17671,17672,17674,17676,17678,17680,17682,17684,17686,17688,17690,17692,17694,17696,17698],{"class":3024,"line":10205},[3022,17673,4991],{"class":3138},[3022,17675,3142],{"class":3028},[3022,17677,13185],{"class":3077},[3022,17679,3103],{"class":3028},[3022,17681,13190],{"class":3077},[3022,17683,13193],{"class":3028},[3022,17685,13196],{"class":3049},[3022,17687,13199],{"class":3028},[3022,17689,13185],{"class":3077},[3022,17691,3103],{"class":3028},[3022,17693,13190],{"class":3077},[3022,17695,13193],{"class":3028},[3022,17697,3151],{"class":3150},[3022,17699,3081],{"class":3028},[3022,17701,17702,17704,17706],{"class":3024,"line":10210},[3022,17703,5192],{"class":3138},[3022,17705,9973],{"class":3049},[3022,17707,4315],{"class":3028},[3022,17709,17710],{"class":3024,"line":10229},[3022,17711,4042],{"emptyLinePlaceholder":4041},[3022,17713,17714,17716,17718,17720],{"class":3024,"line":10243},[3022,17715,13229],{"class":3077},[3022,17717,3103],{"class":3028},[3022,17719,13234],{"class":3068},[3022,17721,3200],{"class":3028},[3022,17723,17724],{"class":3024,"line":10248},[3022,17725,4151],{"class":3028},[3022,17727,17728,17730,17732,17734,17736,17738,17740],{"class":3024,"line":10289},[3022,17729,13248],{"class":3138},[3022,17731,5029],{"class":3049},[3022,17733,13253],{"class":3077},[3022,17735,3223],{"class":3028},[3022,17737,3351],{"class":3049},[3022,17739,13260],{"class":3032},[3022,17741,4119],{"class":3028},[3022,17743,17744,17746,17748,17750],{"class":3024,"line":10294},[3022,17745,13267],{"class":3077},[3022,17747,3103],{"class":3028},[3022,17749,13272],{"class":3077},[3022,17751,3527],{"class":3028},[3022,17753,17754,17756,17758,17760,17762,17764,17766,17768,17770,17772],{"class":3024,"line":10299},[3022,17755,13279],{"class":3077},[3022,17757,3522],{"class":3028},[3022,17759,13056],{"class":3077},[3022,17761,3103],{"class":3028},[3022,17763,13061],{"class":3077},[3022,17765,3103],{"class":3028},[3022,17767,13292],{"class":3077},[3022,17769,3103],{"class":3028},[3022,17771,13297],{"class":3077},[3022,17773,3527],{"class":3028},[3022,17775,17776,17778,17780,17782],{"class":3024,"line":10310},[3022,17777,13304],{"class":3077},[3022,17779,3522],{"class":3028},[3022,17781,11719],{"class":3049},[3022,17783,3527],{"class":3028},[3022,17785,17786,17788,17790,17792],{"class":3024,"line":10316},[3022,17787,13315],{"class":3077},[3022,17789,3522],{"class":3028},[3022,17791,3362],{"class":3049},[3022,17793,3120],{"class":3028},[3022,17795,17796],{"class":3024,"line":10327},[3022,17797,4151],{"class":3028},[3022,17799,17800,17802,17804,17806,17808,17810,17812,17814],{"class":3024,"line":10341},[3022,17801,4156],{"class":3049},[3022,17803,13332],{"class":3077},[3022,17805,3223],{"class":3028},[3022,17807,3257],{"class":3049},[3022,17809,13253],{"class":3077},[3022,17811,3103],{"class":3028},[3022,17813,13343],{"class":3068},[3022,17815,3200],{"class":3028},[3022,17817,17818,17820,17822,17824,17826,17828,17830,17832],{"class":3024,"line":10359},[3022,17819,13229],{"class":3077},[3022,17821,3103],{"class":3028},[3022,17823,13272],{"class":3077},[3022,17825,3103],{"class":3028},[3022,17827,13358],{"class":3077},[3022,17829,3223],{"class":3028},[3022,17831,3151],{"class":3150},[3022,17833,4315],{"class":3028},[3022,17835,17836],{"class":3024,"line":10376},[3022,17837,4151],{"class":3028},[3022,17839,17840,17842,17844],{"class":3024,"line":10402},[3022,17841,9795],{"class":3138},[3022,17843,13332],{"class":3077},[3022,17845,4315],{"class":3028},[3022,17847,17848],{"class":3024,"line":10407},[3022,17849,4036],{"class":3028},[3022,17851,17852],{"class":3024,"line":10434},[3022,17853,3381],{"class":3028},[2964,17855,17856],{},[2974,17857,17858],{},"4. Реєстрація:",[3012,17860,17862],{"className":3014,"code":17861,"language":3016,"meta":3017,"style":3017},"builder.Services.AddScoped\u003CIAuditService, AuditService>();\nbuilder.Services.AddScoped\u003CAuditTrailFilter>();\n\nbuilder.Services.AddControllers(options =>\n{\n    options.Filters.Add\u003CAuditTrailFilter>();\n});\n",[3019,17863,17864,17886,17905,17909,17927,17931,17949],{"__ignoreMap":3017},[3022,17865,17866,17868,17870,17872,17874,17876,17878,17880,17882,17884],{"class":3024,"line":3025},[3022,17867,8221],{"class":3077},[3022,17869,3103],{"class":3028},[3022,17871,8226],{"class":3077},[3022,17873,3103],{"class":3028},[3022,17875,8351],{"class":3068},[3022,17877,3059],{"class":3028},[3022,17879,16821],{"class":3032},[3022,17881,3114],{"class":3028},[3022,17883,16574],{"class":3032},[3022,17885,5262],{"class":3028},[3022,17887,17888,17890,17892,17894,17896,17898,17900,17903],{"class":3024,"line":3046},[3022,17889,8221],{"class":3077},[3022,17891,3103],{"class":3028},[3022,17893,8226],{"class":3077},[3022,17895,3103],{"class":3028},[3022,17897,8351],{"class":3068},[3022,17899,3059],{"class":3028},[3022,17901,17902],{"class":3032},"AuditTrailFilter",[3022,17904,5262],{"class":3028},[3022,17906,17907],{"class":3024,"line":3084},[3022,17908,4042],{"emptyLinePlaceholder":4041},[3022,17910,17911,17913,17915,17917,17919,17921,17923,17925],{"class":3024,"line":3090},[3022,17912,8221],{"class":3077},[3022,17914,3103],{"class":3028},[3022,17916,8226],{"class":3077},[3022,17918,3103],{"class":3028},[3022,17920,8231],{"class":3068},[3022,17922,3036],{"class":3028},[3022,17924,8236],{"class":3077},[3022,17926,8239],{"class":3028},[3022,17928,17929],{"class":3024,"line":3097},[3022,17930,3087],{"class":3028},[3022,17932,17933,17935,17937,17939,17941,17943,17945,17947],{"class":3024,"line":3123},[3022,17934,8253],{"class":3077},[3022,17936,3103],{"class":3028},[3022,17938,4792],{"class":3077},[3022,17940,3103],{"class":3028},[3022,17942,8262],{"class":3068},[3022,17944,3059],{"class":3028},[3022,17946,17902],{"class":3032},[3022,17948,5262],{"class":3028},[3022,17950,17951],{"class":3024,"line":3129},[3022,17952,8329],{"class":3028},[2964,17954,17955],{},[2974,17956,8183],{},[3012,17958,17961],{"className":17959,"code":17960,"language":3628},[3626],"[INFO] AUDIT: CREATE products/N/A by john@company-a.com (tenant-a) - 201\n[INFO] AUDIT: UPDATE products/5 by jane@company-b.com (tenant-b) - 200\n[INFO] AUDIT: DELETE products/3 by admin@company-a.com (tenant-a) - 204\n",[3019,17962,17960],{"__ignoreMap":3017},[3423,17964,17966],{"id":17965},"завдання-33-feature-flag-filter","Завдання 3.3: Feature Flag Filter",[2964,17968,17969],{},"Створіть фільтр для керування доступом до експериментальних features через feature flags:",[11911,17971,17972,17977,18401,18406,18509,18514,19075,19080,19181,19186,19361,19366,19460,19464,19491],{"title":12020},[2964,17973,17974],{},[2974,17975,17976],{},"1. Feature Flag Service:",[3012,17978,17980],{"className":3014,"code":17979,"language":3016,"meta":3017,"style":3017},"public interface IFeatureFlagService\n{\n    bool IsEnabled(string featureName, string? tenantId = null);\n}\n\npublic class FeatureFlagService : IFeatureFlagService\n{\n    private readonly IConfiguration _configuration;\n\n    public FeatureFlagService(IConfiguration configuration)\n    {\n        _configuration = configuration;\n    }\n\n    public bool IsEnabled(string featureName, string? tenantId = null)\n    {\n        // Глобальні feature flags\n        var globalFlags = _configuration.GetSection(\"FeatureFlags\").Get\u003CDictionary\u003Cstring, bool>>() \n            ?? new Dictionary\u003Cstring, bool>();\n\n        if (globalFlags.TryGetValue(featureName, out var isEnabled))\n        {\n            return isEnabled;\n        }\n\n        // Tenant-specific feature flags\n        if (tenantId != null)\n        {\n            var tenantFlags = _configuration.GetSection($\"FeatureFlags:Tenants:{tenantId}\").Get\u003CDictionary\u003Cstring, bool>>();\n            if (tenantFlags?.TryGetValue(featureName, out var tenantEnabled) == true)\n            {\n                return tenantEnabled;\n            }\n        }\n\n        return false; // За замовчуванням вимкнено\n    }\n}\n",[3019,17981,17982,17991,17995,18025,18029,18033,18047,18051,18063,18067,18081,18085,18095,18099,18103,18131,18135,18140,18181,18200,18204,18233,18237,18245,18249,18253,18258,18272,18276,18324,18357,18361,18369,18373,18377,18381,18393,18397],{"__ignoreMap":3017},[3022,17983,17984,17986,17988],{"class":3024,"line":3025},[3022,17985,3050],{"class":3049},[3022,17987,13918],{"class":3049},[3022,17989,17990],{"class":3032}," IFeatureFlagService\n",[3022,17992,17993],{"class":3024,"line":3046},[3022,17994,3087],{"class":3028},[3022,17996,17997,18000,18003,18005,18007,18010,18012,18014,18016,18019,18021,18023],{"class":3024,"line":3084},[3022,17998,17999],{"class":3049},"    bool",[3022,18001,18002],{"class":3068}," IsEnabled",[3022,18004,3036],{"class":3028},[3022,18006,5244],{"class":3049},[3022,18008,18009],{"class":3077}," featureName",[3022,18011,3114],{"class":3028},[3022,18013,5244],{"class":3049},[3022,18015,6940],{"class":3028},[3022,18017,18018],{"class":3077},"tenantId",[3022,18020,3223],{"class":3028},[3022,18022,13196],{"class":3049},[3022,18024,3120],{"class":3028},[3022,18026,18027],{"class":3024,"line":3090},[3022,18028,3381],{"class":3028},[3022,18030,18031],{"class":3024,"line":3097},[3022,18032,4042],{"emptyLinePlaceholder":4041},[3022,18034,18035,18037,18039,18042,18044],{"class":3024,"line":3123},[3022,18036,3050],{"class":3049},[3022,18038,3987],{"class":3049},[3022,18040,18041],{"class":3032}," FeatureFlagService",[3022,18043,3993],{"class":3028},[3022,18045,18046],{"class":3032},"IFeatureFlagService\n",[3022,18048,18049],{"class":3024,"line":3129},[3022,18050,3087],{"class":3028},[3022,18052,18053,18055,18057,18059,18061],{"class":3024,"line":3135},[3022,18054,4837],{"class":3049},[3022,18056,4859],{"class":3049},[3022,18058,4862],{"class":3032},[3022,18060,4865],{"class":3077},[3022,18062,4315],{"class":3028},[3022,18064,18065],{"class":3024,"line":3170},[3022,18066,4042],{"emptyLinePlaceholder":4041},[3022,18068,18069,18071,18073,18075,18077,18079],{"class":3024,"line":3175},[3022,18070,4005],{"class":3049},[3022,18072,18041],{"class":3068},[3022,18074,3036],{"class":3028},[3022,18076,4903],{"class":3032},[3022,18078,4906],{"class":3077},[3022,18080,3081],{"class":3028},[3022,18082,18083],{"class":3024,"line":3181},[3022,18084,4026],{"class":3028},[3022,18086,18087,18089,18091,18093],{"class":3024,"line":3203},[3022,18088,4931],{"class":3077},[3022,18090,3223],{"class":3028},[3022,18092,4936],{"class":3077},[3022,18094,4315],{"class":3028},[3022,18096,18097],{"class":3024,"line":3208},[3022,18098,4036],{"class":3028},[3022,18100,18101],{"class":3024,"line":3214},[3022,18102,4042],{"emptyLinePlaceholder":4041},[3022,18104,18105,18107,18109,18111,18113,18115,18117,18119,18121,18123,18125,18127,18129],{"class":3024,"line":3236},[3022,18106,4005],{"class":3049},[3022,18108,4525],{"class":3049},[3022,18110,18002],{"class":3068},[3022,18112,3036],{"class":3028},[3022,18114,5244],{"class":3049},[3022,18116,18009],{"class":3077},[3022,18118,3114],{"class":3028},[3022,18120,5244],{"class":3049},[3022,18122,6940],{"class":3028},[3022,18124,18018],{"class":3077},[3022,18126,3223],{"class":3028},[3022,18128,13196],{"class":3049},[3022,18130,3081],{"class":3028},[3022,18132,18133],{"class":3024,"line":3241},[3022,18134,4026],{"class":3028},[3022,18136,18137],{"class":3024,"line":3247},[3022,18138,18139],{"class":3093},"        // Глобальні feature flags\n",[3022,18141,18142,18144,18147,18149,18151,18153,18155,18157,18160,18162,18164,18166,18169,18171,18173,18175,18178],{"class":3024,"line":3279},[3022,18143,4156],{"class":3049},[3022,18145,18146],{"class":3077}," globalFlags",[3022,18148,3223],{"class":3028},[3022,18150,5223],{"class":3077},[3022,18152,3103],{"class":3028},[3022,18154,5228],{"class":3068},[3022,18156,3036],{"class":3028},[3022,18158,18159],{"class":3039},"\"FeatureFlags\"",[3022,18161,5236],{"class":3028},[3022,18163,5239],{"class":3068},[3022,18165,3059],{"class":3028},[3022,18167,18168],{"class":3032},"Dictionary",[3022,18170,3059],{"class":3028},[3022,18172,5244],{"class":3049},[3022,18174,3114],{"class":3028},[3022,18176,18177],{"class":3049},"bool",[3022,18179,18180],{"class":3028},">>() \n",[3022,18182,18183,18186,18188,18190,18192,18194,18196,18198],{"class":3024,"line":3284},[3022,18184,18185],{"class":3028},"            ?? ",[3022,18187,3351],{"class":3049},[3022,18189,12054],{"class":3032},[3022,18191,3059],{"class":3028},[3022,18193,5244],{"class":3049},[3022,18195,3114],{"class":3028},[3022,18197,18177],{"class":3049},[3022,18199,5262],{"class":3028},[3022,18201,18202],{"class":3024,"line":3290},[3022,18203,4042],{"emptyLinePlaceholder":4041},[3022,18205,18206,18208,18210,18213,18215,18217,18219,18222,18224,18226,18228,18231],{"class":3024,"line":3303},[3022,18207,4991],{"class":3138},[3022,18209,3142],{"class":3028},[3022,18211,18212],{"class":3077},"globalFlags",[3022,18214,3103],{"class":3028},[3022,18216,5016],{"class":3068},[3022,18218,3036],{"class":3028},[3022,18220,18221],{"class":3077},"featureName",[3022,18223,3114],{"class":3028},[3022,18225,5026],{"class":3049},[3022,18227,5029],{"class":3049},[3022,18229,18230],{"class":3077}," isEnabled",[3022,18232,5035],{"class":3028},[3022,18234,18235],{"class":3024,"line":3329},[3022,18236,5040],{"class":3028},[3022,18238,18239,18241,18243],{"class":3024,"line":3334},[3022,18240,5192],{"class":3138},[3022,18242,18230],{"class":3077},[3022,18244,4315],{"class":3028},[3022,18246,18247],{"class":3024,"line":3340},[3022,18248,5199],{"class":3028},[3022,18250,18251],{"class":3024,"line":3378},[3022,18252,4042],{"emptyLinePlaceholder":4041},[3022,18254,18255],{"class":3024,"line":3803},[3022,18256,18257],{"class":3093},"        // Tenant-specific feature flags\n",[3022,18259,18260,18262,18264,18266,18268,18270],{"class":3024,"line":3809},[3022,18261,4991],{"class":3138},[3022,18263,3142],{"class":3028},[3022,18265,18018],{"class":3077},[3022,18267,6266],{"class":3028},[3022,18269,13196],{"class":3049},[3022,18271,3081],{"class":3028},[3022,18273,18274],{"class":3024,"line":4632},[3022,18275,5040],{"class":3028},[3022,18277,18278,18280,18283,18285,18287,18289,18291,18293,18296,18298,18300,18302,18305,18307,18309,18311,18313,18315,18317,18319,18321],{"class":3024,"line":4658},[3022,18279,5927],{"class":3049},[3022,18281,18282],{"class":3077}," tenantFlags",[3022,18284,3223],{"class":3028},[3022,18286,5223],{"class":3077},[3022,18288,3103],{"class":3028},[3022,18290,5228],{"class":3068},[3022,18292,3036],{"class":3028},[3022,18294,18295],{"class":3039},"$\"FeatureFlags:Tenants:",[3022,18297,5150],{"class":5149},[3022,18299,18018],{"class":3077},[3022,18301,5155],{"class":5149},[3022,18303,18304],{"class":3039},"\"",[3022,18306,5236],{"class":3028},[3022,18308,5239],{"class":3068},[3022,18310,3059],{"class":3028},[3022,18312,18168],{"class":3032},[3022,18314,3059],{"class":3028},[3022,18316,5244],{"class":3049},[3022,18318,3114],{"class":3028},[3022,18320,18177],{"class":3049},[3022,18322,18323],{"class":3028},">>();\n",[3022,18325,18326,18328,18330,18333,18335,18337,18339,18341,18343,18345,18347,18350,18353,18355],{"class":3024,"line":4677},[3022,18327,6217],{"class":3138},[3022,18329,3142],{"class":3028},[3022,18331,18332],{"class":3077},"tenantFlags",[3022,18334,5964],{"class":3028},[3022,18336,5016],{"class":3068},[3022,18338,3036],{"class":3028},[3022,18340,18221],{"class":3077},[3022,18342,3114],{"class":3028},[3022,18344,5026],{"class":3049},[3022,18346,5029],{"class":3049},[3022,18348,18349],{"class":3077}," tenantEnabled",[3022,18351,18352],{"class":3028},") == ",[3022,18354,3362],{"class":3049},[3022,18356,3081],{"class":3028},[3022,18358,18359],{"class":3024,"line":4682},[3022,18360,5107],{"class":3028},[3022,18362,18363,18365,18367],{"class":3024,"line":4712},[3022,18364,12642],{"class":3138},[3022,18366,18349],{"class":3077},[3022,18368,4315],{"class":3028},[3022,18370,18371],{"class":3024,"line":4731},[3022,18372,6387],{"class":3028},[3022,18374,18375],{"class":3024,"line":5202},[3022,18376,5199],{"class":3028},[3022,18378,18379],{"class":3024,"line":5207},[3022,18380,4042],{"emptyLinePlaceholder":4041},[3022,18382,18383,18385,18388,18390],{"class":3024,"line":5213},[3022,18384,9795],{"class":3138},[3022,18386,18387],{"class":3049}," false",[3022,18389,4369],{"class":3028},[3022,18391,18392],{"class":3093},"// За замовчуванням вимкнено\n",[3022,18394,18395],{"class":3024,"line":5265},[3022,18396,4036],{"class":3028},[3022,18398,18399],{"class":3024,"line":5270},[3022,18400,3381],{"class":3028},[2964,18402,18403],{},[2974,18404,18405],{},"2. Feature Flag Attribute:",[3012,18407,18409],{"className":3014,"code":18408,"language":3016,"meta":3017,"style":3017},"[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]\npublic class RequireFeatureAttribute : Attribute\n{\n    public string FeatureName { get; }\n\n    public RequireFeatureAttribute(string featureName)\n    {\n        FeatureName = featureName;\n    }\n}\n",[3019,18410,18411,18435,18449,18453,18468,18472,18486,18490,18501,18505],{"__ignoreMap":3017},[3022,18412,18413,18415,18417,18419,18421,18423,18425,18427,18429,18431,18433],{"class":3024,"line":3025},[3022,18414,3029],{"class":3028},[3022,18416,11661],{"class":3032},[3022,18418,3036],{"class":3028},[3022,18420,11666],{"class":3077},[3022,18422,3103],{"class":3028},[3022,18424,11671],{"class":3077},[3022,18426,11674],{"class":3028},[3022,18428,11666],{"class":3077},[3022,18430,3103],{"class":3028},[3022,18432,7798],{"class":3077},[3022,18434,3043],{"class":3028},[3022,18436,18437,18439,18441,18444,18446],{"class":3024,"line":3046},[3022,18438,3050],{"class":3049},[3022,18440,3987],{"class":3049},[3022,18442,18443],{"class":3032}," RequireFeatureAttribute",[3022,18445,3993],{"class":3028},[3022,18447,18448],{"class":3032},"Attribute\n",[3022,18450,18451],{"class":3024,"line":3084},[3022,18452,3087],{"class":3028},[3022,18454,18455,18457,18459,18462,18464,18466],{"class":3024,"line":3090},[3022,18456,4005],{"class":3049},[3022,18458,4414],{"class":3049},[3022,18460,18461],{"class":3077}," FeatureName",[3022,18463,3354],{"class":3028},[3022,18465,4366],{"class":3049},[3022,18467,4375],{"class":3028},[3022,18469,18470],{"class":3024,"line":3097},[3022,18471,4042],{"emptyLinePlaceholder":4041},[3022,18473,18474,18476,18478,18480,18482,18484],{"class":3024,"line":3123},[3022,18475,4005],{"class":3049},[3022,18477,18443],{"class":3068},[3022,18479,3036],{"class":3028},[3022,18481,5244],{"class":3049},[3022,18483,18009],{"class":3077},[3022,18485,3081],{"class":3028},[3022,18487,18488],{"class":3024,"line":3129},[3022,18489,4026],{"class":3028},[3022,18491,18492,18495,18497,18499],{"class":3024,"line":3135},[3022,18493,18494],{"class":3077},"        FeatureName",[3022,18496,3223],{"class":3028},[3022,18498,18221],{"class":3077},[3022,18500,4315],{"class":3028},[3022,18502,18503],{"class":3024,"line":3170},[3022,18504,4036],{"class":3028},[3022,18506,18507],{"class":3024,"line":3175},[3022,18508,3381],{"class":3028},[2964,18510,18511],{},[2974,18512,18513],{},"3. Feature Flag Filter:",[3012,18515,18517],{"className":3014,"code":18516,"language":3016,"meta":3017,"style":3017},"public class FeatureFlagFilter : IAsyncActionFilter\n{\n    private readonly IFeatureFlagService _featureFlagService;\n    private readonly ILogger\u003CFeatureFlagFilter> _logger;\n\n    public FeatureFlagFilter(IFeatureFlagService featureFlagService, ILogger\u003CFeatureFlagFilter> logger)\n    {\n        _featureFlagService = featureFlagService;\n        _logger = logger;\n    }\n\n    public async Task OnActionExecutionAsync(\n        ActionExecutingContext context,\n        ActionExecutionDelegate next)\n    {\n        // Перевіряємо наявність атрибута\n        var featureAttribute = context.ActionDescriptor.EndpointMetadata\n            .OfType\u003CRequireFeatureAttribute>()\n            .FirstOrDefault();\n\n        if (featureAttribute is null)\n        {\n            await next();\n            return;\n        }\n\n        // Отримуємо tenant\n        var tenant = context.HttpContext.Items[\"Tenant\"] as TenantInfo;\n\n        // Перевіряємо feature flag\n        var isEnabled = _featureFlagService.IsEnabled(featureAttribute.FeatureName, tenant?.TenantId);\n\n        if (!isEnabled)\n        {\n            _logger.LogWarning(\n                \"Feature {FeatureName} is disabled for tenant {TenantId}\",\n                featureAttribute.FeatureName,\n                tenant?.TenantId ?? \"anonymous\");\n\n            context.Result = new ObjectResult(new ProblemDetails\n            {\n                Status = StatusCodes.Status403Forbidden,\n                Title = \"Feature Not Available\",\n                Detail = $\"The feature '{featureAttribute.FeatureName}' is not available for your account\"\n            })\n            {\n                StatusCode = StatusCodes.Status403Forbidden\n            };\n            return;\n        }\n\n        _logger.LogInformation(\n            \"Feature {FeatureName} is enabled for tenant {TenantId}\",\n            featureAttribute.FeatureName,\n            tenant?.TenantId ?? \"anonymous\");\n\n        await next();\n    }\n}\n",[3019,18518,18519,18532,18536,18550,18569,18573,18601,18605,18617,18627,18631,18635,18647,18655,18663,18667,18672,18692,18707,18715,18719,18734,18738,18746,18752,18756,18760,18765,18795,18799,18804,18839,18843,18854,18858,18868,18875,18886,18900,18904,18924,18928,18942,18953,18975,18979,18983,18995,18999,19005,19009,19013,19023,19030,19041,19055,19059,19067,19071],{"__ignoreMap":3017},[3022,18520,18521,18523,18525,18528,18530],{"class":3024,"line":3025},[3022,18522,3050],{"class":3049},[3022,18524,3987],{"class":3049},[3022,18526,18527],{"class":3032}," FeatureFlagFilter",[3022,18529,3993],{"class":3028},[3022,18531,4101],{"class":3032},[3022,18533,18534],{"class":3024,"line":3046},[3022,18535,3087],{"class":3028},[3022,18537,18538,18540,18542,18545,18548],{"class":3024,"line":3084},[3022,18539,4837],{"class":3049},[3022,18541,4859],{"class":3049},[3022,18543,18544],{"class":3032}," IFeatureFlagService",[3022,18546,18547],{"class":3077}," _featureFlagService",[3022,18549,4315],{"class":3028},[3022,18551,18552,18554,18556,18558,18560,18563,18565,18567],{"class":3024,"line":3090},[3022,18553,4837],{"class":3049},[3022,18555,4859],{"class":3049},[3022,18557,4876],{"class":3032},[3022,18559,3059],{"class":3028},[3022,18561,18562],{"class":3032},"FeatureFlagFilter",[3022,18564,3065],{"class":3028},[3022,18566,4886],{"class":3077},[3022,18568,4315],{"class":3028},[3022,18570,18571],{"class":3024,"line":3097},[3022,18572,4042],{"emptyLinePlaceholder":4041},[3022,18574,18575,18577,18579,18581,18584,18587,18589,18591,18593,18595,18597,18599],{"class":3024,"line":3123},[3022,18576,4005],{"class":3049},[3022,18578,18527],{"class":3068},[3022,18580,3036],{"class":3028},[3022,18582,18583],{"class":3032},"IFeatureFlagService",[3022,18585,18586],{"class":3077}," featureFlagService",[3022,18588,3114],{"class":3028},[3022,18590,4911],{"class":3032},[3022,18592,3059],{"class":3028},[3022,18594,18562],{"class":3032},[3022,18596,3065],{"class":3028},[3022,18598,4920],{"class":3077},[3022,18600,3081],{"class":3028},[3022,18602,18603],{"class":3024,"line":3129},[3022,18604,4026],{"class":3028},[3022,18606,18607,18610,18612,18615],{"class":3024,"line":3135},[3022,18608,18609],{"class":3077},"        _featureFlagService",[3022,18611,3223],{"class":3028},[3022,18613,18614],{"class":3077},"featureFlagService",[3022,18616,4315],{"class":3028},[3022,18618,18619,18621,18623,18625],{"class":3024,"line":3170},[3022,18620,4943],{"class":3077},[3022,18622,3223],{"class":3028},[3022,18624,4920],{"class":3077},[3022,18626,4315],{"class":3028},[3022,18628,18629],{"class":3024,"line":3175},[3022,18630,4036],{"class":3028},[3022,18632,18633],{"class":3024,"line":3181},[3022,18634,4042],{"emptyLinePlaceholder":4041},[3022,18636,18637,18639,18641,18643,18645],{"class":3024,"line":3203},[3022,18638,4005],{"class":3049},[3022,18640,3053],{"class":3049},[3022,18642,3056],{"class":3032},[3022,18644,4116],{"class":3068},[3022,18646,4119],{"class":3028},[3022,18648,18649,18651,18653],{"class":3024,"line":3208},[3022,18650,4124],{"class":3032},[3022,18652,4019],{"class":3077},[3022,18654,3527],{"class":3028},[3022,18656,18657,18659,18661],{"class":3024,"line":3214},[3022,18658,4133],{"class":3032},[3022,18660,4136],{"class":3077},[3022,18662,3081],{"class":3028},[3022,18664,18665],{"class":3024,"line":3236},[3022,18666,4026],{"class":3028},[3022,18668,18669],{"class":3024,"line":3241},[3022,18670,18671],{"class":3093},"        // Перевіряємо наявність атрибута\n",[3022,18673,18674,18676,18679,18681,18683,18685,18687,18689],{"class":3024,"line":3247},[3022,18675,4156],{"class":3049},[3022,18677,18678],{"class":3077}," featureAttribute",[3022,18680,3223],{"class":3028},[3022,18682,4996],{"class":3077},[3022,18684,3103],{"class":3028},[3022,18686,5835],{"class":3077},[3022,18688,3103],{"class":3028},[3022,18690,18691],{"class":3077},"EndpointMetadata\n",[3022,18693,18694,18696,18699,18701,18704],{"class":3024,"line":3279},[3022,18695,7351],{"class":3028},[3022,18697,18698],{"class":3068},"OfType",[3022,18700,3059],{"class":3028},[3022,18702,18703],{"class":3032},"RequireFeatureAttribute",[3022,18705,18706],{"class":3028},">()\n",[3022,18708,18709,18711,18713],{"class":3024,"line":3284},[3022,18710,7351],{"class":3028},[3022,18712,7354],{"class":3068},[3022,18714,3200],{"class":3028},[3022,18716,18717],{"class":3024,"line":3290},[3022,18718,4042],{"emptyLinePlaceholder":4041},[3022,18720,18721,18723,18725,18728,18730,18732],{"class":3024,"line":3303},[3022,18722,4991],{"class":3138},[3022,18724,3142],{"class":3028},[3022,18726,18727],{"class":3077},"featureAttribute",[3022,18729,6225],{"class":3049},[3022,18731,9973],{"class":3049},[3022,18733,3081],{"class":3028},[3022,18735,18736],{"class":3024,"line":3329},[3022,18737,5040],{"class":3028},[3022,18739,18740,18742,18744],{"class":3024,"line":3334},[3022,18741,12213],{"class":3049},[3022,18743,4136],{"class":3068},[3022,18745,3200],{"class":3028},[3022,18747,18748,18750],{"class":3024,"line":3340},[3022,18749,5192],{"class":3138},[3022,18751,4315],{"class":3028},[3022,18753,18754],{"class":3024,"line":3378},[3022,18755,5199],{"class":3028},[3022,18757,18758],{"class":3024,"line":3803},[3022,18759,4042],{"emptyLinePlaceholder":4041},[3022,18761,18762],{"class":3024,"line":3809},[3022,18763,18764],{"class":3093},"        // Отримуємо tenant\n",[3022,18766,18767,18769,18771,18773,18775,18777,18779,18781,18783,18785,18787,18789,18791,18793],{"class":3024,"line":4632},[3022,18768,4156],{"class":3049},[3022,18770,14201],{"class":3077},[3022,18772,3223],{"class":3028},[3022,18774,4996],{"class":3077},[3022,18776,3103],{"class":3028},[3022,18778,5001],{"class":3077},[3022,18780,3103],{"class":3028},[3022,18782,5497],{"class":3077},[3022,18784,3029],{"class":3028},[3022,18786,14940],{"class":3039},[3022,18788,11792],{"class":3028},[3022,18790,15158],{"class":3049},[3022,18792,14161],{"class":3032},[3022,18794,4315],{"class":3028},[3022,18796,18797],{"class":3024,"line":4658},[3022,18798,4042],{"emptyLinePlaceholder":4041},[3022,18800,18801],{"class":3024,"line":4677},[3022,18802,18803],{"class":3093},"        // Перевіряємо feature flag\n",[3022,18805,18806,18808,18810,18812,18815,18817,18820,18822,18824,18826,18829,18831,18833,18835,18837],{"class":3024,"line":4682},[3022,18807,4156],{"class":3049},[3022,18809,18230],{"class":3077},[3022,18811,3223],{"class":3028},[3022,18813,18814],{"class":3077},"_featureFlagService",[3022,18816,3103],{"class":3028},[3022,18818,18819],{"class":3068},"IsEnabled",[3022,18821,3036],{"class":3028},[3022,18823,18727],{"class":3077},[3022,18825,3103],{"class":3028},[3022,18827,18828],{"class":3077},"FeatureName",[3022,18830,3114],{"class":3028},[3022,18832,14206],{"class":3077},[3022,18834,5964],{"class":3028},[3022,18836,14797],{"class":3077},[3022,18838,3120],{"class":3028},[3022,18840,18841],{"class":3024,"line":4712},[3022,18842,4042],{"emptyLinePlaceholder":4041},[3022,18844,18845,18847,18849,18852],{"class":3024,"line":4731},[3022,18846,4991],{"class":3138},[3022,18848,3186],{"class":3028},[3022,18850,18851],{"class":3077},"isEnabled",[3022,18853,3081],{"class":3028},[3022,18855,18856],{"class":3024,"line":5202},[3022,18857,5040],{"class":3028},[3022,18859,18860,18862,18864,18866],{"class":3024,"line":5207},[3022,18861,5045],{"class":3077},[3022,18863,3103],{"class":3028},[3022,18865,5050],{"class":3068},[3022,18867,4119],{"class":3028},[3022,18869,18870,18873],{"class":3024,"line":5213},[3022,18871,18872],{"class":3039},"                \"Feature {FeatureName} is disabled for tenant {TenantId}\"",[3022,18874,3527],{"class":3028},[3022,18876,18877,18880,18882,18884],{"class":3024,"line":5265},[3022,18878,18879],{"class":3077},"                featureAttribute",[3022,18881,3103],{"class":3028},[3022,18883,18828],{"class":3077},[3022,18885,3527],{"class":3028},[3022,18887,18888,18890,18892,18894,18896,18898],{"class":3024,"line":5270},[3022,18889,14792],{"class":3077},[3022,18891,5964],{"class":3028},[3022,18893,14797],{"class":3077},[3022,18895,13122],{"class":3028},[3022,18897,17404],{"class":3039},[3022,18899,3120],{"class":3028},[3022,18901,18902],{"class":3024,"line":5276},[3022,18903,4042],{"emptyLinePlaceholder":4041},[3022,18905,18906,18908,18910,18912,18914,18916,18918,18920,18922],{"class":3024,"line":5304},[3022,18907,5084],{"class":3077},[3022,18909,3103],{"class":3028},[3022,18911,3921],{"class":3077},[3022,18913,3223],{"class":3028},[3022,18915,3351],{"class":3049},[3022,18917,6642],{"class":3032},[3022,18919,3036],{"class":3028},[3022,18921,3351],{"class":3049},[3022,18923,5102],{"class":3032},[3022,18925,18926],{"class":3024,"line":5309},[3022,18927,5107],{"class":3028},[3022,18929,18930,18932,18934,18936,18938,18940],{"class":3024,"line":5329},[3022,18931,5112],{"class":3077},[3022,18933,3223],{"class":3028},[3022,18935,5117],{"class":3077},[3022,18937,3103],{"class":3028},[3022,18939,14846],{"class":3077},[3022,18941,3527],{"class":3028},[3022,18943,18944,18946,18948,18951],{"class":3024,"line":5334},[3022,18945,5129],{"class":3077},[3022,18947,3223],{"class":3028},[3022,18949,18950],{"class":3039},"\"Feature Not Available\"",[3022,18952,3527],{"class":3028},[3022,18954,18955,18957,18959,18962,18964,18966,18968,18970,18972],{"class":3024,"line":5355},[3022,18956,5141],{"class":3077},[3022,18958,3223],{"class":3028},[3022,18960,18961],{"class":3039},"$\"The feature '",[3022,18963,5150],{"class":5149},[3022,18965,18727],{"class":3077},[3022,18967,3103],{"class":5149},[3022,18969,18828],{"class":3077},[3022,18971,5155],{"class":5149},[3022,18973,18974],{"class":3039},"' is not available for your account\"\n",[3022,18976,18977],{"class":3024,"line":5360},[3022,18978,14883],{"class":3028},[3022,18980,18981],{"class":3024,"line":5375},[3022,18982,5107],{"class":3028},[3022,18984,18985,18987,18989,18991,18993],{"class":3024,"line":5387},[3022,18986,6846],{"class":3077},[3022,18988,3223],{"class":3028},[3022,18990,5117],{"class":3077},[3022,18992,3103],{"class":3028},[3022,18994,14900],{"class":3077},[3022,18996,18997],{"class":3024,"line":5399},[3022,18998,6130],{"class":3028},[3022,19000,19001,19003],{"class":3024,"line":5420},[3022,19002,5192],{"class":3138},[3022,19004,4315],{"class":3028},[3022,19006,19007],{"class":3024,"line":5425},[3022,19008,5199],{"class":3028},[3022,19010,19011],{"class":3024,"line":5432},[3022,19012,4042],{"emptyLinePlaceholder":4041},[3022,19014,19015,19017,19019,19021],{"class":3024,"line":5437},[3022,19016,4943],{"class":3077},[3022,19018,3103],{"class":3028},[3022,19020,3106],{"class":3068},[3022,19022,4119],{"class":3028},[3022,19024,19025,19028],{"class":3024,"line":5442},[3022,19026,19027],{"class":3039},"            \"Feature {FeatureName} is enabled for tenant {TenantId}\"",[3022,19029,3527],{"class":3028},[3022,19031,19032,19035,19037,19039],{"class":3024,"line":5474},[3022,19033,19034],{"class":3077},"            featureAttribute",[3022,19036,3103],{"class":3028},[3022,19038,18828],{"class":3077},[3022,19040,3527],{"class":3028},[3022,19042,19043,19045,19047,19049,19051,19053],{"class":3024,"line":5479},[3022,19044,14972],{"class":3077},[3022,19046,5964],{"class":3028},[3022,19048,14797],{"class":3077},[3022,19050,13122],{"class":3028},[3022,19052,17404],{"class":3039},[3022,19054,3120],{"class":3028},[3022,19056,19057],{"class":3024,"line":5485},[3022,19058,4042],{"emptyLinePlaceholder":4041},[3022,19060,19061,19063,19065],{"class":3024,"line":5516},[3022,19062,5524],{"class":3049},[3022,19064,4136],{"class":3068},[3022,19066,3200],{"class":3028},[3022,19068,19069],{"class":3024,"line":5521},[3022,19070,4036],{"class":3028},[3022,19072,19073],{"class":3024,"line":5536},[3022,19074,3381],{"class":3028},[2964,19076,19077],{},[2974,19078,19079],{},"4. Конфігурація (appsettings.json):",[3012,19081,19083],{"className":3505,"code":19082,"language":3507,"meta":3017,"style":3017},"{\n  \"FeatureFlags\": {\n    \"AdvancedAnalytics\": false,\n    \"BulkExport\": true,\n    \"AIRecommendations\": false,\n    \"Tenants\": {\n      \"tenant-a\": {\n        \"AdvancedAnalytics\": true,\n        \"AIRecommendations\": true\n      }\n    }\n  }\n}\n",[3019,19084,19085,19089,19096,19107,19118,19129,19136,19143,19154,19164,19169,19173,19177],{"__ignoreMap":3017},[3022,19086,19087],{"class":3024,"line":3025},[3022,19088,3087],{"class":3028},[3022,19090,19091,19094],{"class":3024,"line":3046},[3022,19092,19093],{"class":3518},"  \"FeatureFlags\"",[3022,19095,3564],{"class":3028},[3022,19097,19098,19101,19103,19105],{"class":3024,"line":3084},[3022,19099,19100],{"class":3518},"    \"AdvancedAnalytics\"",[3022,19102,3522],{"class":3028},[3022,19104,11719],{"class":3049},[3022,19106,3527],{"class":3028},[3022,19108,19109,19112,19114,19116],{"class":3024,"line":3090},[3022,19110,19111],{"class":3518},"    \"BulkExport\"",[3022,19113,3522],{"class":3028},[3022,19115,3362],{"class":3049},[3022,19117,3527],{"class":3028},[3022,19119,19120,19123,19125,19127],{"class":3024,"line":3097},[3022,19121,19122],{"class":3518},"    \"AIRecommendations\"",[3022,19124,3522],{"class":3028},[3022,19126,11719],{"class":3049},[3022,19128,3527],{"class":3028},[3022,19130,19131,19134],{"class":3024,"line":3123},[3022,19132,19133],{"class":3518},"    \"Tenants\"",[3022,19135,3564],{"class":3028},[3022,19137,19138,19141],{"class":3024,"line":3129},[3022,19139,19140],{"class":3518},"      \"tenant-a\"",[3022,19142,3564],{"class":3028},[3022,19144,19145,19148,19150,19152],{"class":3024,"line":3135},[3022,19146,19147],{"class":3518},"        \"AdvancedAnalytics\"",[3022,19149,3522],{"class":3028},[3022,19151,3362],{"class":3049},[3022,19153,3527],{"class":3028},[3022,19155,19156,19159,19161],{"class":3024,"line":3170},[3022,19157,19158],{"class":3518},"        \"AIRecommendations\"",[3022,19160,3522],{"class":3028},[3022,19162,19163],{"class":3049},"true\n",[3022,19165,19166],{"class":3024,"line":3175},[3022,19167,19168],{"class":3028},"      }\n",[3022,19170,19171],{"class":3024,"line":3181},[3022,19172,4036],{"class":3028},[3022,19174,19175],{"class":3024,"line":3203},[3022,19176,3591],{"class":3028},[3022,19178,19179],{"class":3024,"line":3208},[3022,19180,3381],{"class":3028},[2964,19182,19183],{},[2974,19184,19185],{},"5. Використання:",[3012,19187,19189],{"className":3014,"code":19188,"language":3016,"meta":3017,"style":3017},"[ApiController]\n[Route(\"api/[controller]\")]\npublic class AnalyticsController : ControllerBase\n{\n    [HttpGet(\"advanced\")]\n    [RequireFeature(\"AdvancedAnalytics\")] // Доступно тільки для tenant-a\n    public IActionResult GetAdvancedAnalytics()\n    {\n        return Ok(new { message = \"Advanced analytics data\" });\n    }\n\n    [HttpGet(\"basic\")]\n    public IActionResult GetBasicAnalytics()\n    {\n        return Ok(new { message = \"Basic analytics data\" });\n    }\n}\n",[3019,19190,19191,19199,19211,19224,19228,19241,19259,19270,19274,19296,19300,19304,19317,19328,19332,19353,19357],{"__ignoreMap":3017},[3022,19192,19193,19195,19197],{"class":3024,"line":3025},[3022,19194,3029],{"class":3028},[3022,19196,8445],{"class":3032},[3022,19198,4390],{"class":3028},[3022,19200,19201,19203,19205,19207,19209],{"class":3024,"line":3046},[3022,19202,3029],{"class":3028},[3022,19204,8454],{"class":3032},[3022,19206,3036],{"class":3028},[3022,19208,8459],{"class":3039},[3022,19210,3043],{"class":3028},[3022,19212,19213,19215,19217,19220,19222],{"class":3024,"line":3084},[3022,19214,3050],{"class":3049},[3022,19216,3987],{"class":3049},[3022,19218,19219],{"class":3032}," AnalyticsController",[3022,19221,3993],{"class":3028},[3022,19223,8497],{"class":3032},[3022,19225,19226],{"class":3024,"line":3090},[3022,19227,3087],{"class":3028},[3022,19229,19230,19232,19234,19236,19239],{"class":3024,"line":3097},[3022,19231,4384],{"class":3028},[3022,19233,3033],{"class":3032},[3022,19235,3036],{"class":3028},[3022,19237,19238],{"class":3039},"\"advanced\"",[3022,19240,3043],{"class":3028},[3022,19242,19243,19245,19248,19250,19253,19256],{"class":3024,"line":3123},[3022,19244,4384],{"class":3028},[3022,19246,19247],{"class":3032},"RequireFeature",[3022,19249,3036],{"class":3028},[3022,19251,19252],{"class":3039},"\"AdvancedAnalytics\"",[3022,19254,19255],{"class":3028},")] ",[3022,19257,19258],{"class":3093},"// Доступно тільки для tenant-a\n",[3022,19260,19261,19263,19265,19268],{"class":3024,"line":3129},[3022,19262,4005],{"class":3049},[3022,19264,16012],{"class":3032},[3022,19266,19267],{"class":3068}," GetAdvancedAnalytics",[3022,19269,6045],{"class":3028},[3022,19271,19272],{"class":3024,"line":3135},[3022,19273,4026],{"class":3028},[3022,19275,19276,19278,19280,19282,19284,19286,19289,19291,19294],{"class":3024,"line":3170},[3022,19277,9795],{"class":3138},[3022,19279,3346],{"class":3068},[3022,19281,3036],{"class":3028},[3022,19283,3351],{"class":3049},[3022,19285,3354],{"class":3028},[3022,19287,19288],{"class":3077},"message",[3022,19290,3223],{"class":3028},[3022,19292,19293],{"class":3039},"\"Advanced analytics data\"",[3022,19295,3375],{"class":3028},[3022,19297,19298],{"class":3024,"line":3175},[3022,19299,4036],{"class":3028},[3022,19301,19302],{"class":3024,"line":3181},[3022,19303,4042],{"emptyLinePlaceholder":4041},[3022,19305,19306,19308,19310,19312,19315],{"class":3024,"line":3203},[3022,19307,4384],{"class":3028},[3022,19309,3033],{"class":3032},[3022,19311,3036],{"class":3028},[3022,19313,19314],{"class":3039},"\"basic\"",[3022,19316,3043],{"class":3028},[3022,19318,19319,19321,19323,19326],{"class":3024,"line":3208},[3022,19320,4005],{"class":3049},[3022,19322,16012],{"class":3032},[3022,19324,19325],{"class":3068}," GetBasicAnalytics",[3022,19327,6045],{"class":3028},[3022,19329,19330],{"class":3024,"line":3214},[3022,19331,4026],{"class":3028},[3022,19333,19334,19336,19338,19340,19342,19344,19346,19348,19351],{"class":3024,"line":3236},[3022,19335,9795],{"class":3138},[3022,19337,3346],{"class":3068},[3022,19339,3036],{"class":3028},[3022,19341,3351],{"class":3049},[3022,19343,3354],{"class":3028},[3022,19345,19288],{"class":3077},[3022,19347,3223],{"class":3028},[3022,19349,19350],{"class":3039},"\"Basic analytics data\"",[3022,19352,3375],{"class":3028},[3022,19354,19355],{"class":3024,"line":3241},[3022,19356,4036],{"class":3028},[3022,19358,19359],{"class":3024,"line":3247},[3022,19360,3381],{"class":3028},[2964,19362,19363],{},[2974,19364,19365],{},"6. Реєстрація:",[3012,19367,19369],{"className":3014,"code":19368,"language":3016,"meta":3017,"style":3017},"builder.Services.AddSingleton\u003CIFeatureFlagService, FeatureFlagService>();\nbuilder.Services.AddScoped\u003CFeatureFlagFilter>();\n\nbuilder.Services.AddControllers(options =>\n{\n    options.Filters.Add\u003CFeatureFlagFilter>();\n});\n",[3019,19370,19371,19394,19412,19416,19434,19438,19456],{"__ignoreMap":3017},[3022,19372,19373,19375,19377,19379,19381,19383,19385,19387,19389,19392],{"class":3024,"line":3025},[3022,19374,8221],{"class":3077},[3022,19376,3103],{"class":3028},[3022,19378,8226],{"class":3077},[3022,19380,3103],{"class":3028},[3022,19382,15837],{"class":3068},[3022,19384,3059],{"class":3028},[3022,19386,18583],{"class":3032},[3022,19388,3114],{"class":3028},[3022,19390,19391],{"class":3032},"FeatureFlagService",[3022,19393,5262],{"class":3028},[3022,19395,19396,19398,19400,19402,19404,19406,19408,19410],{"class":3024,"line":3046},[3022,19397,8221],{"class":3077},[3022,19399,3103],{"class":3028},[3022,19401,8226],{"class":3077},[3022,19403,3103],{"class":3028},[3022,19405,8351],{"class":3068},[3022,19407,3059],{"class":3028},[3022,19409,18562],{"class":3032},[3022,19411,5262],{"class":3028},[3022,19413,19414],{"class":3024,"line":3084},[3022,19415,4042],{"emptyLinePlaceholder":4041},[3022,19417,19418,19420,19422,19424,19426,19428,19430,19432],{"class":3024,"line":3090},[3022,19419,8221],{"class":3077},[3022,19421,3103],{"class":3028},[3022,19423,8226],{"class":3077},[3022,19425,3103],{"class":3028},[3022,19427,8231],{"class":3068},[3022,19429,3036],{"class":3028},[3022,19431,8236],{"class":3077},[3022,19433,8239],{"class":3028},[3022,19435,19436],{"class":3024,"line":3097},[3022,19437,3087],{"class":3028},[3022,19439,19440,19442,19444,19446,19448,19450,19452,19454],{"class":3024,"line":3123},[3022,19441,8253],{"class":3077},[3022,19443,3103],{"class":3028},[3022,19445,4792],{"class":3077},[3022,19447,3103],{"class":3028},[3022,19449,8262],{"class":3068},[3022,19451,3059],{"class":3028},[3022,19453,18562],{"class":3032},[3022,19455,5262],{"class":3028},[3022,19457,19458],{"class":3024,"line":3129},[3022,19459,8329],{"class":3028},[2964,19461,19462],{},[2974,19463,7067],{},[3012,19465,19467],{"className":3446,"code":19466,"language":3448,"meta":3017,"style":3017},"GET /api/analytics/advanced\nX-Api-Key: sk_tenant_a_abc123\n\nHTTP/1.1 200 OK\n{ \"message\": \"Advanced analytics data\" }\n",[3019,19468,19469,19474,19478,19482,19486],{"__ignoreMap":3017},[3022,19470,19471],{"class":3024,"line":3025},[3022,19472,19473],{},"GET /api/analytics/advanced\n",[3022,19475,19476],{"class":3024,"line":3046},[3022,19477,16119],{},[3022,19479,19480],{"class":3024,"line":3084},[3022,19481,4042],{"emptyLinePlaceholder":4041},[3022,19483,19484],{"class":3024,"line":3090},[3022,19485,16128],{},[3022,19487,19488],{"class":3024,"line":3097},[3022,19489,19490],{},"{ \"message\": \"Advanced analytics data\" }\n",[3012,19492,19494],{"className":3446,"code":19493,"language":3448,"meta":3017,"style":3017},"GET /api/analytics/advanced\nX-Api-Key: sk_tenant_b_xyz789\n\nHTTP/1.1 403 Forbidden\n{\n  \"title\": \"Feature Not Available\",\n  \"detail\": \"The feature 'AdvancedAnalytics' is not available for your account\"\n}\n",[3019,19495,19496,19500,19505,19509,19513,19517,19522,19527],{"__ignoreMap":3017},[3022,19497,19498],{"class":3024,"line":3025},[3022,19499,19473],{},[3022,19501,19502],{"class":3024,"line":3046},[3022,19503,19504],{},"X-Api-Key: sk_tenant_b_xyz789\n",[3022,19506,19507],{"class":3024,"line":3084},[3022,19508,4042],{"emptyLinePlaceholder":4041},[3022,19510,19511],{"class":3024,"line":3090},[3022,19512,16161],{},[3022,19514,19515],{"class":3024,"line":3097},[3022,19516,3087],{},[3022,19518,19519],{"class":3024,"line":3123},[3022,19520,19521],{},"  \"title\": \"Feature Not Available\",\n",[3022,19523,19524],{"class":3024,"line":3129},[3022,19525,19526],{},"  \"detail\": \"The feature 'AdvancedAnalytics' is not available for your account\"\n",[3022,19528,19529],{"class":3024,"line":3135},[3022,19530,3381],{},[3652,19532],{},[2959,19534,19536],{"id":19535},"резюме","Резюме",[2964,19538,19539,19540,19543,19544,19546],{},"У цій статті ви навчилися використовувати ",[2974,19541,19542],{},"фільтри"," для реалізації ",[2974,19545,3413],{}," у Web API:",[3423,19548,19550],{"id":19549},"ключові-концепції","Ключові концепції",[2964,19552,19553],{},[2974,19554,19555],{},"1. Типи фільтрів для API:",[2968,19557,19558,19563,19568,19573],{},[2971,19559,19560,19562],{},[2974,19561,3959],{}," — API key validation, custom auth",[2971,19564,19565,19567],{},[2974,19566,3947],{}," — валідація DTO, логування, correlation IDs",[2971,19569,19570,19572],{},[2974,19571,3953],{}," — response wrapping (envelope pattern), headers",[2971,19574,19575,19578],{},[2974,19576,19577],{},"Exception Filters"," — перетворення винятків у ProblemDetails",[2964,19580,19581],{},[2974,19582,19583],{},"2. Filter Pipeline:",[2968,19585,19586,19589,19595],{},[2971,19587,19588],{},"Фільтри виконуються у чіткому порядку навколо action методу",[2971,19590,19591,19594],{},[3019,19592,19593],{},"IOrderedFilter"," дозволяє контролювати порядок виконання",[2971,19596,19597,19598,19600],{},"Short-circuiting через ",[3019,19599,5581],{}," зупиняє pipeline",[2964,19602,19603],{},[2974,19604,19605],{},"3. Реєстрація фільтрів:",[2968,19607,19608,19617,19626],{},[2971,19609,19610,3406,19613,19616],{},[2974,19611,19612],{},"Глобально",[3019,19614,19615],{},"options.Filters.Add\u003CT>()"," для всіх endpoints",[2971,19618,19619,3406,19622,19625],{},[2974,19620,19621],{},"На контролері",[3019,19623,19624],{},"[ServiceFilter(typeof(T))]"," для всіх методів",[2971,19627,19628,3406,19631,19633],{},[2974,19629,19630],{},"На методі",[3019,19632,19624],{}," для конкретного endpoint",[2964,19635,19636],{},[2974,19637,19638],{},"4. Практичні патерни:",[2968,19640,19641,19647,19653,19659,19665,19671],{},[2971,19642,19643,19646],{},[2974,19644,19645],{},"Envelope Pattern"," — обгортання відповідей у стандартну структуру",[2971,19648,19649,19652],{},[2974,19650,19651],{},"Correlation IDs"," — traceability для distributed systems",[2971,19654,19655,19658],{},[2974,19656,19657],{},"Performance Monitoring"," — вимірювання часу виконання",[2971,19660,19661,19664],{},[2974,19662,19663],{},"Multi-Tenancy"," — ізоляція даних між клієнтами",[2971,19666,19667,19670],{},[2974,19668,19669],{},"Audit Trail"," — логування всіх змін даних",[2971,19672,19673,19676],{},[2974,19674,19675],{},"Feature Flags"," — керування доступом до експериментальних features",[3423,19678,19680],{"id":19679},"переваги-фільтрів","Переваги фільтрів",[2964,19682,19683,19684,19687,19688,19691,19692,19695,19696,19691,19698,19701,19702,19691,19704,19707,19708,19691,19710,19713],{},"✅ ",[2974,19685,19686],{},"Централізація логіки"," — без дублювання коду",[19689,19690],"br",{},"\n✅ ",[2974,19693,19694],{},"Чистий код контролерів"," — бізнес-логіка не захаращена технічними деталями",[19689,19697],{},[2974,19699,19700],{},"Тестованість"," — фільтри легко тестувати ізольовано",[19689,19703],{},[2974,19705,19706],{},"Консистентність"," — однакова поведінка для всіх endpoints",[19689,19709],{},[2974,19711,19712],{},"Масштабованість"," — легко додавати нові cross-cutting concerns",[3423,19715,19717],{"id":19716},"коли-використовувати-фільтри","Коли використовувати фільтри",[3818,19719,19720,19729],{},[3821,19721,19722],{},[3824,19723,19724,19727],{},[3827,19725,19726],{},"Сценарій",[3827,19728,3405],{},[3840,19730,19731,19737,19744,19751,19758,19765,19772,19780,19787],{},[3824,19732,19733,19735],{},[3845,19734,11956],{},[3845,19736,11975],{},[3824,19738,19739,19742],{},[3845,19740,19741],{},"Логування запитів/відповідей",[3845,19743,11983],{},[3824,19745,19746,19749],{},[3845,19747,19748],{},"Вимірювання продуктивності",[3845,19750,11983],{},[3824,19752,19753,19756],{},[3845,19754,19755],{},"Обгортання відповідей",[3845,19757,11991],{},[3824,19759,19760,19763],{},[3845,19761,19762],{},"Додавання headers",[3845,19764,11991],{},[3824,19766,19767,19770],{},[3845,19768,19769],{},"Rate limiting",[3845,19771,11983],{},[3824,19773,19774,19777],{},[3845,19775,19776],{},"Multi-tenancy",[3845,19778,19779],{},"Authorization + Action Filters",[3824,19781,19782,19785],{},[3845,19783,19784],{},"Audit trail",[3845,19786,11983],{},[3824,19788,19789,19792],{},[3845,19790,19791],{},"Feature flags",[3845,19793,11983],{},[3934,19795,19796,19799],{},[2974,19797,19798],{},"Best Practice:"," Використовуйте фільтри для технічних аспектів (логування, валідація, авторизація), а бізнес-логіку тримайте у сервісах та контролерах.",[3652,19801],{},[2959,19803,19805],{"id":19804},"додаткові-ресурси","Додаткові ресурси",[19807,19808,19809,19821,19830,19839],"card-group",{},[19810,19811,19814],"card",{"icon":19812,"title":19813},"i-heroicons-book-open","Офіційна документація",[19815,19816,19820],"a",{"href":19817,"rel":19818},"https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters",[19819],"nofollow","Filters in ASP.NET Core",[19810,19822,19825],{"icon":19823,"title":19824},"i-heroicons-arrow-path","Filter Pipeline",[19815,19826,19829],{"href":19827,"rel":19828},"https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#filter-types",[19819],"Understanding the Filter Pipeline",[19810,19831,19834],{"icon":19832,"title":19833},"i-heroicons-exclamation-triangle","ProblemDetails",[19815,19835,19838],{"href":19836,"rel":19837},"https://www.rfc-editor.org/rfc/rfc9457.html",[19819],"RFC 9457 - Problem Details for HTTP APIs",[19810,19840,19843],{"icon":19841,"title":19842},"i-heroicons-light-bulb","Best Practices",[19815,19844,19847],{"href":19845,"rel":19846},"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices",[19819],"ASP.NET Core Best Practices",[3652,19849],{},[3416,19851,19853,4201,19856,19858,19859,19862],{"icon":19852},"i-heroicons-arrow-right",[2974,19854,19855],{},"Наступна стаття:",[19815,19857,1319],{"href":1320}," — практична реалізація ",[3019,19860,19861],{},"PagedList\u003CT>",", query-based фільтрація, dynamic сортування та HATEOAS links.",[19864,19865,19866],"style",{},"html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .sCDza, html code.shiki .sCDza{--shiki-light:#AF00DB;--shiki-default:#CE92A4;--shiki-dark:#CE92A4}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sLwNe, html code.shiki .sLwNe{--shiki-light:#0451A5;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .s0P7L, html code.shiki .s0P7L{--shiki-light:#800000;--shiki-default:#808080;--shiki-dark:#808080}html pre.shiki code .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}",{"title":3017,"searchDepth":3046,"depth":3046,"links":19868},[19869,19872,19877,19888,19894,19907,19912],{"id":2961,"depth":3046,"text":2962,"children":19870},[19871],{"id":3425,"depth":3084,"text":3426},{"id":3656,"depth":3046,"text":3657,"children":19873},[19874,19875,19876],{"id":3660,"depth":3084,"text":3661},{"id":3815,"depth":3084,"text":3816},{"id":3963,"depth":3084,"text":3964},{"id":4209,"depth":3046,"text":4210,"children":19878},[19879,19880,19881,19882,19883,19884,19885,19886,19887],{"id":4213,"depth":3084,"text":4214},{"id":4220,"depth":3084,"text":4221},{"id":4282,"depth":3084,"text":4283},{"id":4738,"depth":3084,"text":4739},{"id":8194,"depth":3084,"text":8195},{"id":8615,"depth":3084,"text":8616},{"id":9220,"depth":3084,"text":9221},{"id":9329,"depth":3084,"text":9330},{"id":10564,"depth":3084,"text":10565},{"id":10817,"depth":3046,"text":10818,"children":19889},[19890,19891,19892,19893],{"id":10821,"depth":3084,"text":10822},{"id":11041,"depth":3084,"text":11042},{"id":11388,"depth":3084,"text":11389},{"id":11645,"depth":3084,"text":11646},{"id":11824,"depth":3046,"text":11825,"children":19895},[19896,19897,19898,19899,19900,19901,19902,19903,19904,19905,19906],{"id":11828,"depth":3084,"text":11829},{"id":11834,"depth":3084,"text":11835},{"id":11947,"depth":3084,"text":11948},{"id":12007,"depth":3084,"text":12008},{"id":12013,"depth":3084,"text":12014},{"id":12703,"depth":3084,"text":12704},{"id":13449,"depth":3084,"text":13450},{"id":13774,"depth":3084,"text":13775},{"id":13780,"depth":3084,"text":13781},{"id":16182,"depth":3084,"text":16183},{"id":17965,"depth":3084,"text":17966},{"id":19535,"depth":3046,"text":19536,"children":19908},[19909,19910,19911],{"id":19549,"depth":3084,"text":19550},{"id":19679,"depth":3084,"text":19680},{"id":19716,"depth":3084,"text":19717},{"id":19804,"depth":3046,"text":19805},"Action Filters, Exception Filters, Result Filters для API. Централізована валідація DTO, response wrapping (envelope pattern), correlation IDs, API key authentication та performance monitoring.","md",null,{},{"title":1315,"description":19913},"tfBMeBFDpokBY9INKON28EsDv0zSAXi50ko1wXXXveE",[19920,19922],{"title":1311,"path":1312,"stem":1313,"description":19921,"children":-1},"Реалізація RFC 9457 для консистентних помилок API. GlobalExceptionHandler, IExceptionHandler, IProblemDetailsService, кастомні error codes та traceability.",{"title":1319,"path":1320,"stem":1321,"description":19923,"children":-1},"Практична реалізація PagedList\u003CT>, query-based фільтрація через DTO, dynamic сортування, X-Pagination headers, HATEOAS links та cursor-based пагінація для великих датасетів.",1777912428534]