[{"data":1,"prerenderedAt":5365},["ShallowReactive",2],{"navigation_docs":3,"-phong-van-ngan-hang-cau-hoi-aspnet-mvc-webapi":420,"-phong-van-ngan-hang-cau-hoi-aspnet-mvc-webapi-surround":5360},[4,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":26},"Phỏng vấn .NET","i-lucide-briefcase","\u002Fphong-van","1.phong-van",[10,27,66,111,128],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":26},"Tổng quan","i-lucide-compass","\u002Fphong-van\u002Ftong-quan","1.phong-van\u002F1.tong-quan",[16,21],{"title":17,"path":18,"stem":19,"icon":20},"Thị trường HCM","\u002Fphong-van\u002Ftong-quan\u002Fthi-truong","1.phong-van\u002F1.tong-quan\u002F1.thi-truong","i-lucide-trending-up",{"title":22,"path":23,"stem":24,"icon":25},"Lộ trình 7 ngày","\u002Fphong-van\u002Ftong-quan\u002Flo-trinh","1.phong-van\u002F1.tong-quan\u002F2.lo-trinh","i-lucide-calendar-days",false,{"title":22,"icon":25,"path":28,"stem":29,"children":30,"page":26},"\u002Fphong-van\u002Flo-trinh","1.phong-van\u002F2.lo-trinh",[31,36,41,46,51,56,61],{"title":32,"path":33,"stem":34,"icon":35},"Ngày 1 — C# cơ bản","\u002Fphong-van\u002Flo-trinh\u002Fngay-1-csharp-co-ban","1.phong-van\u002F2.lo-trinh\u002F1.ngay-1-csharp-co-ban","i-lucide-1",{"title":37,"path":38,"stem":39,"icon":40},"Ngày 2 — OOP và SOLID","\u002Fphong-van\u002Flo-trinh\u002Fngay-2-oop-solid","1.phong-van\u002F2.lo-trinh\u002F2.ngay-2-oop-solid","i-lucide-2",{"title":42,"path":43,"stem":44,"icon":45},"Ngày 3 — C# nâng cao","\u002Fphong-van\u002Flo-trinh\u002Fngay-3-csharp-nang-cao","1.phong-van\u002F2.lo-trinh\u002F3.ngay-3-csharp-nang-cao","i-lucide-3",{"title":47,"path":48,"stem":49,"icon":50},"Ngày 4 — Database và SQL","\u002Fphong-van\u002Flo-trinh\u002Fngay-4-database-sql","1.phong-van\u002F2.lo-trinh\u002F4.ngay-4-database-sql","i-lucide-4",{"title":52,"path":53,"stem":54,"icon":55},"Ngày 5 — ASP.NET Core và EF Core","\u002Fphong-van\u002Flo-trinh\u002Fngay-5-aspnet-efcore","1.phong-van\u002F2.lo-trinh\u002F5.ngay-5-aspnet-efcore","i-lucide-5",{"title":57,"path":58,"stem":59,"icon":60},"Ngày 6 — DSA coding","\u002Fphong-van\u002Flo-trinh\u002Fngay-6-dsa","1.phong-van\u002F2.lo-trinh\u002F6.ngay-6-dsa","i-lucide-6",{"title":62,"path":63,"stem":64,"icon":65},"Ngày 7 — Mock và HR","\u002Fphong-van\u002Flo-trinh\u002Fngay-7-mock-interview","1.phong-van\u002F2.lo-trinh\u002F7.ngay-7-mock-interview","i-lucide-7",{"title":67,"icon":68,"path":69,"stem":70,"children":71,"page":26},"Ngân hàng câu hỏi","i-lucide-database","\u002Fphong-van\u002Fngan-hang-cau-hoi","1.phong-van\u002F3.ngan-hang-cau-hoi",[72,77,82,87,92,97,102,107],{"title":73,"path":74,"stem":75,"icon":76},"C# (60+ câu)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fcsharp","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F1.csharp","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"OOP và SOLID (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Foop","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F2.oop","i-lucide-shapes",{"title":83,"path":84,"stem":85,"icon":86},"SQL (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fsql","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F3.sql","i-lucide-database-zap",{"title":88,"path":89,"stem":90,"icon":91},"ASP.NET và EF (50+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-ef","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F4.aspnet-ef","i-lucide-server",{"title":93,"path":94,"stem":95,"icon":96},"DSA và Coding (30+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdsa-coding","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F5.dsa-coding","i-lucide-binary",{"title":98,"path":99,"stem":100,"icon":101},"HR và Soft skill (25+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fhr-behavior","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F6.hr-behavior","i-lucide-users",{"title":103,"path":104,"stem":105,"icon":106},"Design Pattern (55+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdesign-pattern","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F7.design-pattern","i-lucide-puzzle",{"title":108,"path":109,"stem":110,"icon":91},"ASP.NET MVC và Web API","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-mvc-webapi","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi",{"title":112,"itle":113,"icon":114,"path":115,"stem":116,"children":117,"page":26},"Tai Lieu","Tài liệu và Cheat sheet","i-lucide-book-marked","\u002Fphong-van\u002Ftai-lieu","1.phong-van\u002F4.tai-lieu",[118,123],{"title":119,"path":120,"stem":121,"icon":122},"Cheat Sheet","\u002Fphong-van\u002Ftai-lieu\u002Fcheatsheet","1.phong-van\u002F4.tai-lieu\u002F1.cheatsheet","i-lucide-clipboard-list",{"title":124,"path":125,"stem":126,"icon":127},"Nguồn tham khảo","\u002Fphong-van\u002Ftai-lieu\u002Fnguon","1.phong-van\u002F4.tai-lieu\u002F2.nguon","i-lucide-link",{"title":129,"icon":130,"path":131,"stem":132,"children":133,"page":26},"Phỏng vấn English","i-lucide-globe","\u002Fphong-van\u002Fenglish-interview","1.phong-van\u002F5.english-interview",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":130},"Tổng quan English","\u002Fphong-van\u002Fenglish-interview\u002Ftong-quan","1.phong-van\u002F5.english-interview\u002F1.tong-quan",{"title":139,"path":140,"stem":141,"icon":142},"Vocabulary","\u002Fphong-van\u002Fenglish-interview\u002Fvocabulary","1.phong-van\u002F5.english-interview\u002F2.vocabulary","i-lucide-book-a",{"title":144,"path":145,"stem":146,"icon":147},"Technical Q và A (EN)","\u002Fphong-van\u002Fenglish-interview\u002Ftechnical-en","1.phong-van\u002F5.english-interview\u002F3.technical-en","i-lucide-code",{"title":149,"path":150,"stem":151,"icon":152},"Behavioral (EN)","\u002Fphong-van\u002Fenglish-interview\u002Fbehavioral-en","1.phong-van\u002F5.english-interview\u002F4.behavioral-en","i-lucide-message-square-text",{"title":154,"icon":155,"path":156,"stem":157,"children":158},"Tự học","i-lucide-graduation-cap","\u002Ftu-hoc","2.tu-hoc",[159,161,287,310,333,357,384],{"title":154,"path":156,"stem":160,"icon":155},"2.tu-hoc\u002Findex",{"title":162,"icon":68,"path":163,"stem":164,"children":165},"Database","\u002Ftu-hoc\u002Fdatabase","2.tu-hoc\u002F1.database\u002Findex",[166,167,197,222,243,248],{"title":162,"path":163,"stem":164,"icon":68},{"title":168,"icon":91,"path":169,"stem":170,"children":171,"page":26},"SQL Server","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server","2.tu-hoc\u002F1.database\u002F2.sql-server",[172,177,182,187,192],{"title":173,"path":174,"stem":175,"icon":176},"1. Giới thiệu và Cài đặt","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F1.gioi-thieu-cai-dat","i-lucide-play-circle",{"title":178,"path":179,"stem":180,"icon":181},"2. Cú pháp cơ bản","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F2.cu-phap-co-ban","i-lucide-code-2",{"title":183,"path":184,"stem":185,"icon":186},"3. Truy vấn nâng cao","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F3.truy-van-nang-cao","i-lucide-git-merge",{"title":188,"path":189,"stem":190,"icon":191},"4. Đặc thù và Performance","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F4.dac-thu-performance","i-lucide-gauge",{"title":193,"path":194,"stem":195,"icon":196},"5. Q và A phỏng vấn","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F5.cau-hoi-phong-van","i-lucide-message-circle-question",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":26},"PostgreSQL","i-simple-icons-postgresql","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql","2.tu-hoc\u002F1.database\u002F3.postgresql",[203,206,209,212,215,218],{"title":173,"path":204,"stem":205,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":207,"stem":208,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F2.cu-phap-co-ban",{"title":183,"path":210,"stem":211,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F3.truy-van-nang-cao",{"title":188,"path":213,"stem":214,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F4.dac-thu-performance",{"title":193,"path":216,"stem":217,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F5.cau-hoi-phong-van",{"title":219,"path":220,"stem":221,"icon":96},"6. Tích hợp C# và Postgres","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftich-hop-csharp","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F6.tich-hop-csharp",{"title":223,"icon":224,"path":225,"stem":226,"children":227,"page":26},"MySQL","i-simple-icons-mysql","\u002Ftu-hoc\u002Fdatabase\u002Fmysql","2.tu-hoc\u002F1.database\u002F4.mysql",[228,231,234,237,240],{"title":173,"path":229,"stem":230,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F4.mysql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":232,"stem":233,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F4.mysql\u002F2.cu-phap-co-ban",{"title":183,"path":235,"stem":236,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F4.mysql\u002F3.truy-van-nang-cao",{"title":188,"path":238,"stem":239,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F4.mysql\u002F4.dac-thu-performance",{"title":193,"path":241,"stem":242,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F4.mysql\u002F5.cau-hoi-phong-van",{"title":244,"path":245,"stem":246,"icon":247},"So sánh 3 RDBMS","\u002Ftu-hoc\u002Fdatabase\u002Fso-sanh","2.tu-hoc\u002F1.database\u002F5.so-sanh","i-lucide-columns-3",{"title":249,"path":250,"stem":251,"children":252,"icon":254},"Supabase (BaaS)","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002Findex",[253,255,259,264,269,274,278,283],{"title":249,"path":250,"stem":251,"icon":254},"i-simple-icons-supabase",{"title":256,"path":257,"stem":258,"icon":68},"1. Database","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fdatabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002F1.database",{"title":260,"path":261,"stem":262,"icon":263},"2. Authentication","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fauthentication","2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication","i-lucide-key",{"title":265,"path":266,"stem":267,"icon":268},"3. Storage & Realtime","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fstorage-realtime","2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime","i-lucide-zap",{"title":270,"path":271,"stem":272,"icon":273},"4. Edge Functions & AI","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Ffunctions-ai","2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai","i-lucide-brain",{"title":275,"path":276,"stem":277,"icon":181},"5. API & SDKs","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fapi-sdk","2.tu-hoc\u002F1.database\u002F6.supabase\u002F5.api-sdk",{"title":279,"path":280,"stem":281,"icon":282},"6. Platform & Deploy","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fplatform-deploy","2.tu-hoc\u002F1.database\u002F6.supabase\u002F6.platform-deploy","i-lucide-cloud",{"title":284,"path":285,"stem":286,"icon":196},"7. Interview & Q&A","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Finterview","2.tu-hoc\u002F1.database\u002F6.supabase\u002F7.interview",{"title":288,"icon":289,"path":290,"stem":291,"children":292},"TypeScript","i-simple-icons-typescript","\u002Ftu-hoc\u002Ftypescript","2.tu-hoc\u002F2.typescript\u002Findex",[293,294,297,301,306],{"title":288,"path":290,"stem":291,"icon":289},{"title":173,"path":295,"stem":296,"icon":176},"\u002Ftu-hoc\u002Ftypescript\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F2.typescript\u002F1.gioi-thieu-cai-dat",{"title":298,"path":299,"stem":300,"icon":81},"2. Type System","\u002Ftu-hoc\u002Ftypescript\u002Ftype-system","2.tu-hoc\u002F2.typescript\u002F2.type-system",{"title":302,"path":303,"stem":304,"icon":305},"3. Advanced types","\u002Ftu-hoc\u002Ftypescript\u002Fadvanced-types","2.tu-hoc\u002F2.typescript\u002F3.advanced-types","i-lucide-braces",{"title":307,"path":308,"stem":309,"icon":196},"4. Q và A phỏng vấn","\u002Ftu-hoc\u002Ftypescript\u002Fcau-hoi-phong-van","2.tu-hoc\u002F2.typescript\u002F4.cau-hoi-phong-van",{"title":311,"icon":81,"path":312,"stem":313,"children":314},"OOP","\u002Ftu-hoc\u002Foop","2.tu-hoc\u002F3.oop\u002Findex",[315,316,321,326,330],{"title":311,"path":312,"stem":313,"icon":81},{"title":317,"path":318,"stem":319,"icon":320},"1. Bốn tính chất","\u002Ftu-hoc\u002Foop\u002F4-tinh-chat","2.tu-hoc\u002F3.oop\u002F1.4-tinh-chat","i-lucide-circle-dot",{"title":322,"path":323,"stem":324,"icon":325},"2. SOLID","\u002Ftu-hoc\u002Foop\u002Fsolid","2.tu-hoc\u002F3.oop\u002F2.solid","i-lucide-shield",{"title":327,"path":328,"stem":329,"icon":106},"3. Design Pattern","\u002Ftu-hoc\u002Foop\u002Fdesign-pattern","2.tu-hoc\u002F3.oop\u002F3.design-pattern",{"title":307,"path":331,"stem":332,"icon":196},"\u002Ftu-hoc\u002Foop\u002Fcau-hoi-phong-van","2.tu-hoc\u002F3.oop\u002F4.cau-hoi-phong-van",{"title":334,"icon":305,"path":335,"stem":336,"children":337},"Generic","\u002Ftu-hoc\u002Fgeneric","2.tu-hoc\u002F4.generic\u002Findex",[338,339,344,349,354],{"title":334,"path":335,"stem":336,"icon":305},{"title":340,"path":341,"stem":342,"icon":343},"1. Khái niệm và cú pháp","\u002Ftu-hoc\u002Fgeneric\u002Fkhai-niem","2.tu-hoc\u002F4.generic\u002F1.khai-niem","i-lucide-book",{"title":345,"path":346,"stem":347,"icon":348},"2. Constraint và Variance","\u002Ftu-hoc\u002Fgeneric\u002Fconstraint-variance","2.tu-hoc\u002F4.generic\u002F2.constraint-variance","i-lucide-filter",{"title":350,"path":351,"stem":352,"icon":353},"3. C# vs TS","\u002Ftu-hoc\u002Fgeneric\u002Fso-sanh","2.tu-hoc\u002F4.generic\u002F3.so-sanh","i-lucide-git-compare",{"title":307,"path":355,"stem":356,"icon":196},"\u002Ftu-hoc\u002Fgeneric\u002Fcau-hoi-phong-van","2.tu-hoc\u002F4.generic\u002F4.cau-hoi-phong-van",{"title":358,"icon":359,"path":360,"stem":361,"children":362},"Nuxt 4","i-simple-icons-nuxt","\u002Ftu-hoc\u002Fnuxt-4","2.tu-hoc\u002F5.nuxt-4\u002Findex",[363,364,367,372,376,379],{"title":358,"path":360,"stem":361,"icon":359},{"title":173,"path":365,"stem":366,"icon":176},"\u002Ftu-hoc\u002Fnuxt-4\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F5.nuxt-4\u002F1.gioi-thieu-cai-dat",{"title":368,"path":369,"stem":370,"icon":371},"2. Routing và Rendering","\u002Ftu-hoc\u002Fnuxt-4\u002Frouting-rendering","2.tu-hoc\u002F5.nuxt-4\u002F2.routing-rendering","i-lucide-route",{"title":373,"path":374,"stem":375,"icon":91},"3. Data và Server","\u002Ftu-hoc\u002Fnuxt-4\u002Fdata-server","2.tu-hoc\u002F5.nuxt-4\u002F3.data-server",{"title":307,"path":377,"stem":378,"icon":196},"\u002Ftu-hoc\u002Fnuxt-4\u002Fcau-hoi-phong-van","2.tu-hoc\u002F5.nuxt-4\u002F4.cau-hoi-phong-van",{"title":380,"path":381,"stem":382,"icon":383},"5. Practical Todo App","\u002Ftu-hoc\u002Fnuxt-4\u002Fpractical-todo-app","2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app","i-lucide-check-square",{"title":385,"path":386,"stem":387,"children":388,"icon":390},"Tailwind CSS","\u002Ftu-hoc\u002Ftailwindcss","2.tu-hoc\u002F6.tailwindcss\u002Findex",[389,391,396,401,406,411,416],{"title":385,"path":386,"stem":387,"icon":390},"i-simple-icons-tailwindcss",{"title":392,"path":393,"stem":394,"icon":395},"1. Layout & Spacing","\u002Ftu-hoc\u002Ftailwindcss\u002Flayout-spacing","2.tu-hoc\u002F6.tailwindcss\u002F1.layout-spacing","i-lucide-layout",{"title":397,"path":398,"stem":399,"icon":400},"2. Typography & Colors","\u002Ftu-hoc\u002Ftailwindcss\u002Ftypography-colors","2.tu-hoc\u002F6.tailwindcss\u002F2.typography-colors","i-lucide-type",{"title":402,"path":403,"stem":404,"icon":405},"3. Interactive & Effects","\u002Ftu-hoc\u002Ftailwindcss\u002Finteractive-effects","2.tu-hoc\u002F6.tailwindcss\u002F3.interactive-effects","i-lucide-sparkles",{"title":407,"path":408,"stem":409,"icon":410},"4. Responsive & Dark Mode","\u002Ftu-hoc\u002Ftailwindcss\u002Fresponsive-darkmode","2.tu-hoc\u002F6.tailwindcss\u002F4.responsive-darkmode","i-lucide-monitor-smartphone",{"title":412,"path":413,"stem":414,"icon":415},"5. Customization & Theme","\u002Ftu-hoc\u002Ftailwindcss\u002Fcustomization","2.tu-hoc\u002F6.tailwindcss\u002F5.customization","i-lucide-settings-2",{"title":417,"path":418,"stem":419,"icon":181},"6. Practical Projects","\u002Ftu-hoc\u002Ftailwindcss\u002Fpractical-projects","2.tu-hoc\u002F6.tailwindcss\u002F6.practical-projects",{"id":421,"title":422,"body":423,"description":5353,"extension":5354,"links":5355,"meta":5356,"navigation":5357,"path":109,"seo":5358,"stem":110,"__hash__":5359},"docs\u002F1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi.md","Ngân hàng câu hỏi ASP.NET MVC và Web API",{"type":424,"value":425,"toc":5266},"minimark",[426,442,445,450,455,461,483,494,498,582,586,706,718,720,724,728,734,738,744,766,768,772,776,782,815,821,870,876,880,883,913,916,920,926,932,959,962,964,968,972,1059,1071,1091,1158,1166,1203,1207,1210,1246,1281,1283,1287,1291,1401,1406,1412,1416,1484,1488,1554,1556,1560,1564,1715,1719,1807,1865,1869,1925,1983,1987,1997,2122,2129,2214,2216,2220,2224,2334,2342,2415,2422,2424,2428,2432,2475,2479,2486,2492,2534,2538,2671,2675,2842,2846,2921,2939,2943,3019,3024,3035,3037,3041,3045,3097,3179,3183,3245,3249,3252,3314,3316,3320,3324,3341,3390,3394,3467,3471,3576,3580,3619,3621,3625,3629,3667,3671,3737,3741,3804,3808,3876,3878,3882,3886,3891,3911,3916,3983,3992,4019,4057,4061,4126,4133,4137,4237,4241,4335,4339,4450,4454,4590,4592,4596,4600,4692,4696,4791,4810,4814,4870,4874,4963,4965,4969,4973,5026,5030,5035,5056,5060,5190,5192,5196,5262],[427,428,431,439],"callout",{"color":429,"icon":430},"primary","i-lucide-info",[432,433,434,438],"p",{},[435,436,437],"strong",{},"Cách dùng:"," tự trả lời ra giấy, sau đó so với đáp án. Câu có ⭐ là hay hỏi nhất.",[432,440,441],{},"Level: 🟢 Intern | 🔵 Junior | 🟡 Mid | 🔴 Senior",[443,444],"hr",{},[446,447,449],"h2",{"id":448},"a-mvc-pattern-nền-tảng","A. MVC Pattern — Nền tảng",[451,452,454],"h3",{"id":453},"_1-mvc-là-gì-3-thành-phần","1. 🟢 MVC là gì? 3 thành phần?",[432,456,457,460],{},[435,458,459],{},"Model-View-Controller"," — pattern tách ứng dụng thành 3 phần:",[462,463,464,471,477],"ul",{},[465,466,467,470],"li",{},[435,468,469],{},"Model",": dữ liệu và business logic. Không biết về UI.",[465,472,473,476],{},[435,474,475],{},"View",": giao diện — nhận data từ Model, render HTML. Không có business logic.",[465,478,479,482],{},[435,480,481],{},"Controller",": điều phối — nhận request, gọi Model, chọn View trả về.",[484,485,490],"pre",{"className":486,"code":488,"language":489},[487],"language-text","HTTP Request\n    ↓\nController (nhận, xử lý)\n    ↓\nModel (lấy\u002Ftính toán data)\n    ↓\nController (chọn view + truyền data)\n    ↓\nView (render HTML)\n    ↓\nHTTP Response\n","text",[491,492,488],"code",{"__ignoreMap":493},"",[451,495,497],{"id":496},"_2-lợi-ích-của-mvc-so-với-webforms","2. 🟢 Lợi ích của MVC so với WebForms?",[499,500,501,516],"table",{},[502,503,504],"thead",{},[505,506,507,510,513],"tr",{},[508,509],"th",{},[508,511,512],{},"WebForms",[508,514,515],{},"MVC",[517,518,519,531,542,560,571],"tbody",{},[505,520,521,525,528],{},[522,523,524],"td",{},"Code-behind",[522,526,527],{},"Gắn liền HTML + C#",[522,529,530],{},"Tách rõ",[505,532,533,536,539],{},[522,534,535],{},"Testability",[522,537,538],{},"Khó test",[522,540,541],{},"Dễ unit test",[505,543,544,547,554],{},[522,545,546],{},"URL",[522,548,549,550,553],{},"Dạng file path (",[491,551,552],{},"Default.aspx",")",[522,555,556,557,553],{},"Clean URL (",[491,558,559],{},"\u002Fusers\u002F123",[505,561,562,565,568],{},[522,563,564],{},"HTML control",[522,566,567],{},"Server controls, viewstate",[522,569,570],{},"Full control HTML",[505,572,573,576,579],{},[522,574,575],{},"Lifecycle",[522,577,578],{},"Phức tạp (Page lifecycle)",[522,580,581],{},"Đơn giản hơn",[451,583,585],{"id":584},"_3-aspnet-mvc-5-khác-aspnet-core-mvc-ra-sao","3. 🟢 ASP.NET MVC 5 khác ASP.NET Core MVC ra sao?",[499,587,588,600],{},[502,589,590],{},[505,591,592,594,597],{},[508,593],{},[508,595,596],{},"ASP.NET MVC 5 (.NET Framework)",[508,598,599],{},"ASP.NET Core MVC",[517,601,602,613,624,635,652,663,676,695],{},[505,603,604,607,610],{},[522,605,606],{},"Platform",[522,608,609],{},"Windows only",[522,611,612],{},"Cross-platform",[505,614,615,618,621],{},[522,616,617],{},"Hosting",[522,619,620],{},"IIS bắt buộc",[522,622,623],{},"Kestrel + IIS\u002FNginx\u002FApache",[505,625,626,629,632],{},[522,627,628],{},"DI",[522,630,631],{},"Cần lib ngoài (Unity, Autofac)",[522,633,634],{},"Built-in",[505,636,637,640,646],{},[522,638,639],{},"Config",[522,641,642,645],{},[491,643,644],{},"web.config"," XML",[522,647,648,649,553],{},"JSON (",[491,650,651],{},"appsettings.json",[505,653,654,657,660],{},[522,655,656],{},"Middleware",[522,658,659],{},"HTTP Module\u002FHandler",[522,661,662],{},"Pipeline middleware",[505,664,665,668,671],{},[522,666,667],{},"Performance",[522,669,670],{},"Chậm hơn",[522,672,673],{},[435,674,675],{},"Nhanh hơn nhiều",[505,677,678,681,690],{},[522,679,680],{},"Startup",[522,682,683,686,687],{},[491,684,685],{},"Global.asax"," + ",[491,688,689],{},"RouteConfig.cs",[522,691,692],{},[491,693,694],{},"Program.cs",[505,696,697,700,703],{},[522,698,699],{},"Còn support",[522,701,702],{},".NET Framework 4.8 (EOL xa)",[522,704,705],{},".NET 8\u002F9\u002F10 LTS",[432,707,708,709,712,713,717],{},"⭐ ",[435,710,711],{},"Câu hay hỏi:"," ",[714,715,716],"em",{},"\"Tại sao nên dùng ASP.NET Core thay vì MVC 5?\""," → Cross-platform, performance tốt hơn, DI built-in, module nhỏ hơn, cloud-native friendly.",[443,719],{},[446,721,723],{"id":722},"b-request-lifecycle-mvc-5","B. Request Lifecycle — MVC 5",[451,725,727],{"id":726},"_4-request-lifecycle-trong-aspnet-mvc-5","4. 🟡 Request lifecycle trong ASP.NET MVC 5?",[484,729,732],{"className":730,"code":731,"language":489},[487],"Browser Request\n    ↓\n1. UrlRoutingModule           — match URL với route table\n    ↓\n2. MVC Route Handler          — xác nhận đây là MVC request\n    ↓\n3. MvcHandler + ControllerFactory — tạo instance Controller\n    ↓\n4. Action Invoker             — chọn Action method phù hợp\n    ↓\n5. Model Binding              — map request data → action parameters\n    ↓\n6. Action Filters (OnActionExecuting) — trước khi action chạy\n    ↓\n7. Action Method              — business logic, trả ActionResult\n    ↓\n8. Action Filters (OnActionExecuted) — sau khi action chạy\n    ↓\n9. Result Execution (View\u002FJSON\u002FRedirect)\n    ↓\n10. Result Filters            — sau khi result render\n    ↓\nResponse\n",[491,733,731],{"__ignoreMap":493},[451,735,737],{"id":736},"_5-request-lifecycle-trong-aspnet-core-mvc","5. 🟡 Request lifecycle trong ASP.NET Core MVC?",[484,739,742],{"className":740,"code":741,"language":489},[487],"Request\n    ↓\nKestrel Server\n    ↓\nMiddleware Pipeline (UseRouting → UseAuth → UseEndpoints)\n    ↓\nEndpoint Routing               — chọn controller\u002Faction\n    ↓\nModel Binding                  — bind route\u002Fquery\u002Fbody\n    ↓\nAction Filters (IActionFilter)\n    ↓\nAction Method\n    ↓\nResult Filters (IResultFilter)\n    ↓\nIActionResult.ExecuteAsync()   — render view\u002FJSON\n    ↓\nResponse\n",[491,743,741],{"__ignoreMap":493},[432,745,746,749,750,753,754,757,758,761,762,765],{},[435,747,748],{},"Khác MVC 5:"," ASP.NET Core không có ",[491,751,752],{},"HttpHandler","\u002F",[491,755,756],{},"HttpModule"," — thay bằng ",[435,759,760],{},"Middleware pipeline",". Mọi thứ đi qua ",[491,763,764],{},"IMiddleware"," chain.",[443,767],{},[446,769,771],{"id":770},"c-routing","C. Routing",[451,773,775],{"id":774},"_6-conventional-routing-vs-attribute-routing","6. 🟢 Conventional routing vs Attribute routing?",[432,777,778,781],{},[435,779,780],{},"Conventional routing"," — khai báo template tập trung:",[484,783,787],{"className":784,"code":785,"language":786,"meta":493,"style":493},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Program.cs \u002F RouteConfig.cs (MVC 5)\napp.MapControllerRoute(\n    name: \"default\",\n    pattern: \"{controller=Home}\u002F{action=Index}\u002F{id?}\");\n","csharp",[491,788,789,797,803,809],{"__ignoreMap":493},[790,791,794],"span",{"class":792,"line":793},"line",1,[790,795,796],{},"\u002F\u002F Program.cs \u002F RouteConfig.cs (MVC 5)\n",[790,798,800],{"class":792,"line":799},2,[790,801,802],{},"app.MapControllerRoute(\n",[790,804,806],{"class":792,"line":805},3,[790,807,808],{},"    name: \"default\",\n",[790,810,812],{"class":792,"line":811},4,[790,813,814],{},"    pattern: \"{controller=Home}\u002F{action=Index}\u002F{id?}\");\n",[432,816,817,820],{},[435,818,819],{},"Attribute routing"," — đặt ngay trên controller\u002Faction:",[484,822,824],{"className":784,"code":823,"language":786,"meta":493,"style":493},"[Route(\"api\u002Fusers\")]\npublic class UsersController : ControllerBase {\n    [HttpGet(\"{id:int}\")]           \u002F\u002F GET \u002Fapi\u002Fusers\u002F42\n    public IActionResult Get(int id) { ... }\n    \n    [HttpPost]                      \u002F\u002F POST \u002Fapi\u002Fusers\n    public IActionResult Create(CreateUserDto dto) { ... }\n}\n",[491,825,826,831,836,841,846,852,858,864],{"__ignoreMap":493},[790,827,828],{"class":792,"line":793},[790,829,830],{},"[Route(\"api\u002Fusers\")]\n",[790,832,833],{"class":792,"line":799},[790,834,835],{},"public class UsersController : ControllerBase {\n",[790,837,838],{"class":792,"line":805},[790,839,840],{},"    [HttpGet(\"{id:int}\")]           \u002F\u002F GET \u002Fapi\u002Fusers\u002F42\n",[790,842,843],{"class":792,"line":811},[790,844,845],{},"    public IActionResult Get(int id) { ... }\n",[790,847,849],{"class":792,"line":848},5,[790,850,851],{},"    \n",[790,853,855],{"class":792,"line":854},6,[790,856,857],{},"    [HttpPost]                      \u002F\u002F POST \u002Fapi\u002Fusers\n",[790,859,861],{"class":792,"line":860},7,[790,862,863],{},"    public IActionResult Create(CreateUserDto dto) { ... }\n",[790,865,867],{"class":792,"line":866},8,[790,868,869],{},"}\n",[432,871,708,872,875],{},[435,873,874],{},"Web API nên dùng Attribute routing"," — rõ ràng, version URL dễ.",[451,877,879],{"id":878},"_7-route-constraint-là-gì-ví-dụ","7. 🟢 Route constraint là gì? Ví dụ?",[432,881,882],{},"Ràng buộc giá trị parameter trong route:",[484,884,886],{"className":784,"code":885,"language":786,"meta":493,"style":493},"[HttpGet(\"{id:int}\")]          \u002F\u002F id phải là số nguyên\n[HttpGet(\"{slug:alpha}\")]       \u002F\u002F slug chỉ chứa chữ cái\n[HttpGet(\"{age:range(1,120)}\")] \u002F\u002F age từ 1 đến 120\n[HttpGet(\"{id:guid}\")]          \u002F\u002F id phải là GUID\n[HttpGet(\"{name:minlength(3)}\")] \u002F\u002F name >= 3 ký tự\n",[491,887,888,893,898,903,908],{"__ignoreMap":493},[790,889,890],{"class":792,"line":793},[790,891,892],{},"[HttpGet(\"{id:int}\")]          \u002F\u002F id phải là số nguyên\n",[790,894,895],{"class":792,"line":799},[790,896,897],{},"[HttpGet(\"{slug:alpha}\")]       \u002F\u002F slug chỉ chứa chữ cái\n",[790,899,900],{"class":792,"line":805},[790,901,902],{},"[HttpGet(\"{age:range(1,120)}\")] \u002F\u002F age từ 1 đến 120\n",[790,904,905],{"class":792,"line":811},[790,906,907],{},"[HttpGet(\"{id:guid}\")]          \u002F\u002F id phải là GUID\n",[790,909,910],{"class":792,"line":848},[790,911,912],{},"[HttpGet(\"{name:minlength(3)}\")] \u002F\u002F name >= 3 ký tự\n",[432,914,915],{},"Nếu không match constraint → 404, không đi vào action.",[451,917,919],{"id":918},"_8-endpoint-routing-aspnet-core-3-khác-irouter-cũ-ra-sao","8. 🔵 Endpoint routing (ASP.NET Core 3+) khác IRouter cũ ra sao?",[432,921,922,925],{},[435,923,924],{},"Cũ (MVC 5 \u002F Core 2):"," routing gắn liền với MVC handler. Middleware không biết route nào sẽ chạy.",[432,927,928,931],{},[435,929,930],{},"Endpoint routing (Core 3+):"," routing tách thành 2 bước:",[933,934,935,949],"ol",{},[465,936,937,940,941,944,945,948],{},[491,938,939],{},"UseRouting()"," — ",[435,942,943],{},"chọn"," endpoint (gắn vào ",[491,946,947],{},"HttpContext.GetEndpoint()",").",[465,950,951,954,955,958],{},[491,952,953],{},"UseAuthorization()"," \u002F ",[491,956,957],{},"UseEndpoints()"," — middleware sau biết endpoint, có thể check auth TRƯỚC khi chạy action.",[432,960,961],{},"Lợi ích: middleware biết sớm endpoint → auth check chính xác hơn.",[443,963],{},[446,965,967],{"id":966},"d-controller-và-action","D. Controller và Action",[451,969,971],{"id":970},"_9-actionresult-các-loại-hay-dùng","9. 🟢 ActionResult các loại hay dùng?",[484,973,975],{"className":784,"code":974,"language":786,"meta":493,"style":493},"return View();                          \u002F\u002F ViewResult — render HTML\nreturn View(\"MyView\", model);           \u002F\u002F ViewResult với view name + model\nreturn Ok(data);                        \u002F\u002F 200 JSON (Web API)\nreturn Created(uri, data);              \u002F\u002F 201 Created\nreturn NoContent();                     \u002F\u002F 204 No Content\nreturn BadRequest(\"invalid\");           \u002F\u002F 400\nreturn NotFound();                      \u002F\u002F 404\nreturn Unauthorized();                  \u002F\u002F 401\nreturn Forbid();                        \u002F\u002F 403\nreturn Redirect(\"\u002Fhome\");               \u002F\u002F 302 Redirect\nreturn RedirectToAction(\"Index\");       \u002F\u002F Redirect tới action\nreturn Json(obj);                       \u002F\u002F JSON (MVC 5 style)\nreturn File(bytes, \"image\u002Fpng\");        \u002F\u002F File response\nreturn Content(\"text\", \"text\u002Fplain\");   \u002F\u002F Plain text\nreturn StatusCode(429);                 \u002F\u002F Custom status\n",[491,976,977,982,987,992,997,1002,1007,1012,1017,1023,1029,1035,1041,1047,1053],{"__ignoreMap":493},[790,978,979],{"class":792,"line":793},[790,980,981],{},"return View();                          \u002F\u002F ViewResult — render HTML\n",[790,983,984],{"class":792,"line":799},[790,985,986],{},"return View(\"MyView\", model);           \u002F\u002F ViewResult với view name + model\n",[790,988,989],{"class":792,"line":805},[790,990,991],{},"return Ok(data);                        \u002F\u002F 200 JSON (Web API)\n",[790,993,994],{"class":792,"line":811},[790,995,996],{},"return Created(uri, data);              \u002F\u002F 201 Created\n",[790,998,999],{"class":792,"line":848},[790,1000,1001],{},"return NoContent();                     \u002F\u002F 204 No Content\n",[790,1003,1004],{"class":792,"line":854},[790,1005,1006],{},"return BadRequest(\"invalid\");           \u002F\u002F 400\n",[790,1008,1009],{"class":792,"line":860},[790,1010,1011],{},"return NotFound();                      \u002F\u002F 404\n",[790,1013,1014],{"class":792,"line":866},[790,1015,1016],{},"return Unauthorized();                  \u002F\u002F 401\n",[790,1018,1020],{"class":792,"line":1019},9,[790,1021,1022],{},"return Forbid();                        \u002F\u002F 403\n",[790,1024,1026],{"class":792,"line":1025},10,[790,1027,1028],{},"return Redirect(\"\u002Fhome\");               \u002F\u002F 302 Redirect\n",[790,1030,1032],{"class":792,"line":1031},11,[790,1033,1034],{},"return RedirectToAction(\"Index\");       \u002F\u002F Redirect tới action\n",[790,1036,1038],{"class":792,"line":1037},12,[790,1039,1040],{},"return Json(obj);                       \u002F\u002F JSON (MVC 5 style)\n",[790,1042,1044],{"class":792,"line":1043},13,[790,1045,1046],{},"return File(bytes, \"image\u002Fpng\");        \u002F\u002F File response\n",[790,1048,1050],{"class":792,"line":1049},14,[790,1051,1052],{},"return Content(\"text\", \"text\u002Fplain\");   \u002F\u002F Plain text\n",[790,1054,1056],{"class":792,"line":1055},15,[790,1057,1058],{},"return StatusCode(429);                 \u002F\u002F Custom status\n",[451,1060,1062,1063,1066,1067,1070],{"id":1061},"_10-iactionresult-vs-actionresultt-khác-gì","10. 🟢 ",[491,1064,1065],{},"IActionResult"," vs ",[491,1068,1069],{},"ActionResult\u003CT>"," khác gì?",[462,1072,1073,1078],{},[465,1074,1075,1077],{},[491,1076,1065],{},": return bất kỳ loại result, không type-safe.",[465,1079,1080,1082,1083,1086,1087,1090],{},[491,1081,1069],{},": generic — cũng return bất kỳ IActionResult, ",[435,1084,1085],{},"hoặc"," return ",[491,1088,1089],{},"T"," trực tiếp (sẽ tự wrap Ok(T)).",[484,1092,1094],{"className":784,"code":1093,"language":786,"meta":493,"style":493},"\u002F\u002F IActionResult — không biết return gì\npublic IActionResult GetUser(int id) {\n    var u = _service.Get(id);\n    if (u == null) return NotFound();\n    return Ok(u);\n}\n\n\u002F\u002F ActionResult\u003CT> — rõ ràng hơn, Swagger biết type\npublic ActionResult\u003CUserDto> GetUser(int id) {\n    var u = _service.Get(id);\n    if (u == null) return NotFound();\n    return u;        \u002F\u002F implicit Ok(u)\n}\n",[491,1095,1096,1101,1106,1111,1116,1121,1125,1131,1136,1141,1145,1149,1154],{"__ignoreMap":493},[790,1097,1098],{"class":792,"line":793},[790,1099,1100],{},"\u002F\u002F IActionResult — không biết return gì\n",[790,1102,1103],{"class":792,"line":799},[790,1104,1105],{},"public IActionResult GetUser(int id) {\n",[790,1107,1108],{"class":792,"line":805},[790,1109,1110],{},"    var u = _service.Get(id);\n",[790,1112,1113],{"class":792,"line":811},[790,1114,1115],{},"    if (u == null) return NotFound();\n",[790,1117,1118],{"class":792,"line":848},[790,1119,1120],{},"    return Ok(u);\n",[790,1122,1123],{"class":792,"line":854},[790,1124,869],{},[790,1126,1127],{"class":792,"line":860},[790,1128,1130],{"emptyLinePlaceholder":1129},true,"\n",[790,1132,1133],{"class":792,"line":866},[790,1134,1135],{},"\u002F\u002F ActionResult\u003CT> — rõ ràng hơn, Swagger biết type\n",[790,1137,1138],{"class":792,"line":1019},[790,1139,1140],{},"public ActionResult\u003CUserDto> GetUser(int id) {\n",[790,1142,1143],{"class":792,"line":1025},[790,1144,1110],{},[790,1146,1147],{"class":792,"line":1031},[790,1148,1115],{},[790,1150,1151],{"class":792,"line":1037},[790,1152,1153],{},"    return u;        \u002F\u002F implicit Ok(u)\n",[790,1155,1156],{"class":792,"line":1043},[790,1157,869],{},[451,1159,1161,1162,1165],{"id":1160},"_11-apicontroller-attribute-làm-gì","11. 🔵 ",[491,1163,1164],{},"[ApiController]"," attribute làm gì?",[462,1167,1168,1178,1184,1197],{},[465,1169,1170,1173,1174,1177],{},[435,1171,1172],{},"Automatic model validation"," → BadRequest 400 nếu ",[491,1175,1176],{},"ModelState.IsValid == false",".",[465,1179,1180,1183],{},[435,1181,1182],{},"Attribute routing required"," — không dùng conventional routing.",[465,1185,1186,940,1189,1192,1193,1196],{},[435,1187,1188],{},"Binding source inference",[491,1190,1191],{},"[FromBody]"," cho complex type, ",[491,1194,1195],{},"[FromRoute]"," cho route param tự động.",[465,1198,1199,1202],{},[435,1200,1201],{},"ProblemDetails"," response format theo RFC 7807.",[451,1204,1206],{"id":1205},"_12-model-binding-hoạt-động-ra-sao","12. 🟢 Model Binding hoạt động ra sao?",[432,1208,1209],{},"Framework tự map data từ request vào action parameters theo thứ tự:",[933,1211,1212,1220,1229,1234,1240],{},[465,1213,1214,1216,1217,1177],{},[491,1215,1195],{}," — route params ",[491,1218,1219],{},"{id}",[465,1221,1222,1225,1226,1177],{},[491,1223,1224],{},"[FromQuery]"," — query string ",[491,1227,1228],{},"?name=Alice",[465,1230,1231,1233],{},[491,1232,1191],{}," — JSON\u002Fform body.",[465,1235,1236,1239],{},[491,1237,1238],{},"[FromHeader]"," — HTTP header.",[465,1241,1242,1245],{},[491,1243,1244],{},"[FromForm]"," — form data.",[484,1247,1249],{"className":784,"code":1248,"language":786,"meta":493,"style":493},"[HttpPut(\"{id}\")]\npublic IActionResult Update(\n    int id,                          \u002F\u002F FromRoute\n    [FromQuery] bool notify,         \u002F\u002F FromQuery\n    [FromBody] UpdateUserDto dto)    \u002F\u002F FromBody\n{ ... }\n",[491,1250,1251,1256,1261,1266,1271,1276],{"__ignoreMap":493},[790,1252,1253],{"class":792,"line":793},[790,1254,1255],{},"[HttpPut(\"{id}\")]\n",[790,1257,1258],{"class":792,"line":799},[790,1259,1260],{},"public IActionResult Update(\n",[790,1262,1263],{"class":792,"line":805},[790,1264,1265],{},"    int id,                          \u002F\u002F FromRoute\n",[790,1267,1268],{"class":792,"line":811},[790,1269,1270],{},"    [FromQuery] bool notify,         \u002F\u002F FromQuery\n",[790,1272,1273],{"class":792,"line":848},[790,1274,1275],{},"    [FromBody] UpdateUserDto dto)    \u002F\u002F FromBody\n",[790,1277,1278],{"class":792,"line":854},[790,1279,1280],{},"{ ... }\n",[443,1282],{},[446,1284,1286],{"id":1285},"e-filters","E. Filters",[451,1288,1290],{"id":1289},"_13-filter-trong-aspnet-core-các-loại","13. 🔵 Filter trong ASP.NET Core — các loại?",[499,1292,1293,1309],{},[502,1294,1295],{},[505,1296,1297,1300,1303,1306],{},[508,1298,1299],{},"Filter",[508,1301,1302],{},"Interface",[508,1304,1305],{},"Khi chạy",[508,1307,1308],{},"Dùng cho",[517,1310,1311,1329,1347,1365,1383],{},[505,1312,1313,1318,1323,1326],{},[522,1314,1315],{},[435,1316,1317],{},"Authorization",[522,1319,1320],{},[491,1321,1322],{},"IAuthorizationFilter",[522,1324,1325],{},"Trước tất cả",[522,1327,1328],{},"Auth check",[505,1330,1331,1336,1341,1344],{},[522,1332,1333],{},[435,1334,1335],{},"Resource",[522,1337,1338],{},[491,1339,1340],{},"IResourceFilter",[522,1342,1343],{},"Sau Auth, bao action",[522,1345,1346],{},"Caching, short-circuit",[505,1348,1349,1354,1359,1362],{},[522,1350,1351],{},[435,1352,1353],{},"Action",[522,1355,1356],{},[491,1357,1358],{},"IActionFilter",[522,1360,1361],{},"Trước\u002Fsau action",[522,1363,1364],{},"Logging, validation",[505,1366,1367,1372,1377,1380],{},[522,1368,1369],{},[435,1370,1371],{},"Exception",[522,1373,1374],{},[491,1375,1376],{},"IExceptionFilter",[522,1378,1379],{},"Khi có exception",[522,1381,1382],{},"Error handling",[505,1384,1385,1390,1395,1398],{},[522,1386,1387],{},[435,1388,1389],{},"Result",[522,1391,1392],{},[491,1393,1394],{},"IResultFilter",[522,1396,1397],{},"Trước\u002Fsau result",[522,1399,1400],{},"Response transform",[432,1402,1403],{},[435,1404,1405],{},"Thứ tự chạy:",[484,1407,1410],{"className":1408,"code":1409,"language":489},[487],"Authorization → Resource → [Action → ActionMethod → Action] → [Result → ResultMethod → Result] → Exception\n",[491,1411,1409],{"__ignoreMap":493},[451,1413,1415],{"id":1414},"_14-filter-scope-đăng-ký-ở-đâu","14. 🔵 Filter scope — đăng ký ở đâu?",[484,1417,1419],{"className":784,"code":1418,"language":786,"meta":493,"style":493},"\u002F\u002F 1. Global — áp cho tất cả\nbuilder.Services.AddControllers(options => {\n    options.Filters.Add\u003CGlobalExceptionFilter>();\n});\n\n\u002F\u002F 2. Controller — áp cho tất cả action trong controller\n[ServiceFilter(typeof(LoggingFilter))]\npublic class UsersController : ControllerBase { }\n\n\u002F\u002F 3. Action — chỉ action đó\n[HttpGet]\n[TypeFilter(typeof(RateLimitFilter))]\npublic IActionResult GetAll() { }\n",[491,1420,1421,1426,1431,1436,1441,1445,1450,1455,1460,1464,1469,1474,1479],{"__ignoreMap":493},[790,1422,1423],{"class":792,"line":793},[790,1424,1425],{},"\u002F\u002F 1. Global — áp cho tất cả\n",[790,1427,1428],{"class":792,"line":799},[790,1429,1430],{},"builder.Services.AddControllers(options => {\n",[790,1432,1433],{"class":792,"line":805},[790,1434,1435],{},"    options.Filters.Add\u003CGlobalExceptionFilter>();\n",[790,1437,1438],{"class":792,"line":811},[790,1439,1440],{},"});\n",[790,1442,1443],{"class":792,"line":848},[790,1444,1130],{"emptyLinePlaceholder":1129},[790,1446,1447],{"class":792,"line":854},[790,1448,1449],{},"\u002F\u002F 2. Controller — áp cho tất cả action trong controller\n",[790,1451,1452],{"class":792,"line":860},[790,1453,1454],{},"[ServiceFilter(typeof(LoggingFilter))]\n",[790,1456,1457],{"class":792,"line":866},[790,1458,1459],{},"public class UsersController : ControllerBase { }\n",[790,1461,1462],{"class":792,"line":1019},[790,1463,1130],{"emptyLinePlaceholder":1129},[790,1465,1466],{"class":792,"line":1025},[790,1467,1468],{},"\u002F\u002F 3. Action — chỉ action đó\n",[790,1470,1471],{"class":792,"line":1031},[790,1472,1473],{},"[HttpGet]\n",[790,1475,1476],{"class":792,"line":1037},[790,1477,1478],{},"[TypeFilter(typeof(RateLimitFilter))]\n",[790,1480,1481],{"class":792,"line":1043},[790,1482,1483],{},"public IActionResult GetAll() { }\n",[451,1485,1487],{"id":1486},"_15-filter-vs-middleware-khác-gì","15. 🔵 Filter vs Middleware — khác gì?",[499,1489,1490,1500],{},[502,1491,1492],{},[505,1493,1494,1496,1498],{},[508,1495],{},[508,1497,656],{},[508,1499,1299],{},[517,1501,1502,1513,1524,1535,1544],{},[505,1503,1504,1507,1510],{},[522,1505,1506],{},"Level",[522,1508,1509],{},"HTTP pipeline toàn app",[522,1511,1512],{},"MVC pipeline",[505,1514,1515,1518,1521],{},[522,1516,1517],{},"Biết về routing",[522,1519,1520],{},"❌ (trừ dùng sau UseRouting)",[522,1522,1523],{},"✅",[505,1525,1526,1529,1532],{},[522,1527,1528],{},"Biết về action",[522,1530,1531],{},"❌",[522,1533,1534],{},"✅ (ActionContext)",[505,1536,1537,1540,1542],{},[522,1538,1539],{},"Short-circuit",[522,1541,1523],{},[522,1543,1523],{},[505,1545,1546,1548,1551],{},[522,1547,1308],{},[522,1549,1550],{},"CORS, Auth, logging chung",[522,1552,1553],{},"Validation, caching, business logic",[443,1555],{},[446,1557,1559],{"id":1558},"f-views-và-razor","F. Views và Razor",[451,1561,1563],{"id":1562},"_16-razor-syntax-cơ-bản","16. 🟢 Razor syntax cơ bản?",[484,1565,1569],{"className":1566,"code":1567,"language":1568,"meta":493,"style":493},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","@* Comment *@\n@{ var name = \"Alice\"; }       @* Code block *@\n\u003Cp>Hello @name\u003C\u002Fp>             @* Output biến *@\n@if (Model.IsAdmin) {\n    \u003Cspan>Admin\u003C\u002Fspan>\n}\n@foreach (var item in Model.Items) {\n    \u003Cli>@item.Name\u003C\u002Fli>\n}\n@Html.ActionLink(\"Click\", \"Index\", \"Home\")   @* HTML helper *@\n\u003Ca asp-controller=\"Home\" asp-action=\"Index\">Click\u003C\u002Fa>  @* Tag helper *@\n","html",[491,1570,1571,1577,1582,1607,1612,1631,1635,1640,1657,1661,1666],{"__ignoreMap":493},[790,1572,1573],{"class":792,"line":793},[790,1574,1576],{"class":1575},"sTEyZ","@* Comment *@\n",[790,1578,1579],{"class":792,"line":799},[790,1580,1581],{"class":1575},"@{ var name = \"Alice\"; }       @* Code block *@\n",[790,1583,1584,1588,1591,1594,1597,1600,1602,1604],{"class":792,"line":805},[790,1585,1587],{"class":1586},"sMK4o","\u003C",[790,1589,432],{"class":1590},"swJcz",[790,1592,1593],{"class":1586},">",[790,1595,1596],{"class":1575},"Hello @name",[790,1598,1599],{"class":1586},"\u003C\u002F",[790,1601,432],{"class":1590},[790,1603,1593],{"class":1586},[790,1605,1606],{"class":1575},"             @* Output biến *@\n",[790,1608,1609],{"class":792,"line":811},[790,1610,1611],{"class":1575},"@if (Model.IsAdmin) {\n",[790,1613,1614,1617,1619,1621,1624,1626,1628],{"class":792,"line":848},[790,1615,1616],{"class":1586},"    \u003C",[790,1618,790],{"class":1590},[790,1620,1593],{"class":1586},[790,1622,1623],{"class":1575},"Admin",[790,1625,1599],{"class":1586},[790,1627,790],{"class":1590},[790,1629,1630],{"class":1586},">\n",[790,1632,1633],{"class":792,"line":854},[790,1634,869],{"class":1575},[790,1636,1637],{"class":792,"line":860},[790,1638,1639],{"class":1575},"@foreach (var item in Model.Items) {\n",[790,1641,1642,1644,1646,1648,1651,1653,1655],{"class":792,"line":866},[790,1643,1616],{"class":1586},[790,1645,465],{"class":1590},[790,1647,1593],{"class":1586},[790,1649,1650],{"class":1575},"@item.Name",[790,1652,1599],{"class":1586},[790,1654,465],{"class":1590},[790,1656,1630],{"class":1586},[790,1658,1659],{"class":792,"line":1019},[790,1660,869],{"class":1575},[790,1662,1663],{"class":792,"line":1025},[790,1664,1665],{"class":1575},"@Html.ActionLink(\"Click\", \"Index\", \"Home\")   @* HTML helper *@\n",[790,1667,1668,1670,1673,1677,1680,1683,1687,1689,1692,1694,1696,1699,1701,1703,1706,1708,1710,1712],{"class":792,"line":1031},[790,1669,1587],{"class":1586},[790,1671,1672],{"class":1590},"a",[790,1674,1676],{"class":1675},"spNyl"," asp-controller",[790,1678,1679],{"class":1586},"=",[790,1681,1682],{"class":1586},"\"",[790,1684,1686],{"class":1685},"sfazB","Home",[790,1688,1682],{"class":1586},[790,1690,1691],{"class":1675}," asp-action",[790,1693,1679],{"class":1586},[790,1695,1682],{"class":1586},[790,1697,1698],{"class":1685},"Index",[790,1700,1682],{"class":1586},[790,1702,1593],{"class":1586},[790,1704,1705],{"class":1575},"Click",[790,1707,1599],{"class":1586},[790,1709,1672],{"class":1590},[790,1711,1593],{"class":1586},[790,1713,1714],{"class":1575},"  @* Tag helper *@\n",[451,1716,1718],{"id":1717},"_17-viewdata-vs-viewbag-vs-tempdata-vs-session","17. 🟢 ViewData vs ViewBag vs TempData vs Session?",[499,1720,1721,1736],{},[502,1722,1723],{},[505,1724,1725,1727,1730,1733],{},[508,1726],{},[508,1728,1729],{},"Scope",[508,1731,1732],{},"Type",[508,1734,1735],{},"Khi nào",[517,1737,1738,1756,1774,1791],{},[505,1739,1740,1745,1748,1753],{},[522,1741,1742],{},[491,1743,1744],{},"ViewData",[522,1746,1747],{},"1 request",[522,1749,1750],{},[491,1751,1752],{},"Dictionary\u003Cstring, object>",[522,1754,1755],{},"Truyền data Controller → View",[505,1757,1758,1763,1765,1771],{},[522,1759,1760],{},[491,1761,1762],{},"ViewBag",[522,1764,1747],{},[522,1766,1767,1770],{},[491,1768,1769],{},"dynamic"," (wrapper ViewData)",[522,1772,1773],{},"Giống ViewData, cú pháp dynamic",[505,1775,1776,1781,1784,1788],{},[522,1777,1778],{},[491,1779,1780],{},"TempData",[522,1782,1783],{},"1 redirect (xong xoá)",[522,1785,1786],{},[491,1787,1752],{},[522,1789,1790],{},"Thông báo sau redirect",[505,1792,1793,1798,1801,1804],{},[522,1794,1795],{},[491,1796,1797],{},"Session",[522,1799,1800],{},"Nhiều request",[522,1802,1803],{},"Key-value",[522,1805,1806],{},"User session state",[484,1808,1810],{"className":784,"code":1809,"language":786,"meta":493,"style":493},"\u002F\u002F Controller\nViewData[\"Name\"] = \"Alice\";\nViewBag.Age = 25;\nTempData[\"Success\"] = \"Saved!\";\n\n\u002F\u002F View\n\u003Cp>@ViewData[\"Name\"]\u003C\u002Fp>\n\u003Cp>@ViewBag.Age\u003C\u002Fp>\n@if (TempData[\"Success\"] != null) {\n    \u003Cdiv class=\"alert\">@TempData[\"Success\"]\u003C\u002Fdiv>\n}\n",[491,1811,1812,1817,1822,1827,1832,1836,1841,1846,1851,1856,1861],{"__ignoreMap":493},[790,1813,1814],{"class":792,"line":793},[790,1815,1816],{},"\u002F\u002F Controller\n",[790,1818,1819],{"class":792,"line":799},[790,1820,1821],{},"ViewData[\"Name\"] = \"Alice\";\n",[790,1823,1824],{"class":792,"line":805},[790,1825,1826],{},"ViewBag.Age = 25;\n",[790,1828,1829],{"class":792,"line":811},[790,1830,1831],{},"TempData[\"Success\"] = \"Saved!\";\n",[790,1833,1834],{"class":792,"line":848},[790,1835,1130],{"emptyLinePlaceholder":1129},[790,1837,1838],{"class":792,"line":854},[790,1839,1840],{},"\u002F\u002F View\n",[790,1842,1843],{"class":792,"line":860},[790,1844,1845],{},"\u003Cp>@ViewData[\"Name\"]\u003C\u002Fp>\n",[790,1847,1848],{"class":792,"line":866},[790,1849,1850],{},"\u003Cp>@ViewBag.Age\u003C\u002Fp>\n",[790,1852,1853],{"class":792,"line":1019},[790,1854,1855],{},"@if (TempData[\"Success\"] != null) {\n",[790,1857,1858],{"class":792,"line":1025},[790,1859,1860],{},"    \u003Cdiv class=\"alert\">@TempData[\"Success\"]\u003C\u002Fdiv>\n",[790,1862,1863],{"class":792,"line":1031},[790,1864,869],{},[451,1866,1868],{"id":1867},"_18-partial-view-vs-viewcomponent-vs-display-template","18. 🔵 Partial View vs ViewComponent vs Display Template?",[499,1870,1871,1886],{},[502,1872,1873],{},[505,1874,1875,1877,1880,1883],{},[508,1876],{},[508,1878,1879],{},"Dùng khi",[508,1881,1882],{},"Có DI?",[508,1884,1885],{},"Logic phức tạp?",[517,1887,1888,1901,1913],{},[505,1889,1890,1893,1896,1899],{},[522,1891,1892],{},"Partial View",[522,1894,1895],{},"Tái sử dụng HTML fragment",[522,1897,1898],{},"❌ (nhận ViewModel)",[522,1900,1531],{},[505,1902,1903,1906,1909,1911],{},[522,1904,1905],{},"ViewComponent",[522,1907,1908],{},"Widget độc lập (basket, cart, notification)",[522,1910,1523],{},[522,1912,1523],{},[505,1914,1915,1918,1921,1923],{},[522,1916,1917],{},"Display\u002FEditor Template",[522,1919,1920],{},"Format kiểu dữ liệu (DateTime, Money)",[522,1922,1531],{},[522,1924,1531],{},[484,1926,1928],{"className":784,"code":1927,"language":786,"meta":493,"style":493},"\u002F\u002F ViewComponent\npublic class CartSummaryViewComponent : ViewComponent {\n    private readonly ICartService _cart;\n    public CartSummaryViewComponent(ICartService cart) => _cart = cart;\n    public IViewComponentResult Invoke() => View(_cart.GetSummary());\n}\n\n\u002F\u002F Gọi trong View\n@await Component.InvokeAsync(\"CartSummary\")\n\u002F\u002F Hoặc\n\u003Cvc:cart-summary \u002F>\n",[491,1929,1930,1935,1940,1945,1950,1955,1959,1963,1968,1973,1978],{"__ignoreMap":493},[790,1931,1932],{"class":792,"line":793},[790,1933,1934],{},"\u002F\u002F ViewComponent\n",[790,1936,1937],{"class":792,"line":799},[790,1938,1939],{},"public class CartSummaryViewComponent : ViewComponent {\n",[790,1941,1942],{"class":792,"line":805},[790,1943,1944],{},"    private readonly ICartService _cart;\n",[790,1946,1947],{"class":792,"line":811},[790,1948,1949],{},"    public CartSummaryViewComponent(ICartService cart) => _cart = cart;\n",[790,1951,1952],{"class":792,"line":848},[790,1953,1954],{},"    public IViewComponentResult Invoke() => View(_cart.GetSummary());\n",[790,1956,1957],{"class":792,"line":854},[790,1958,869],{},[790,1960,1961],{"class":792,"line":860},[790,1962,1130],{"emptyLinePlaceholder":1129},[790,1964,1965],{"class":792,"line":866},[790,1966,1967],{},"\u002F\u002F Gọi trong View\n",[790,1969,1970],{"class":792,"line":1019},[790,1971,1972],{},"@await Component.InvokeAsync(\"CartSummary\")\n",[790,1974,1975],{"class":792,"line":1025},[790,1976,1977],{},"\u002F\u002F Hoặc\n",[790,1979,1980],{"class":792,"line":1031},[790,1981,1982],{},"\u003Cvc:cart-summary \u002F>\n",[451,1984,1986],{"id":1985},"_19-layout-trong-razor-là-gì","19. 🟢 Layout trong Razor là gì?",[432,1988,1989,1990,686,1993,1996],{},"Layout là template cha — define ",[491,1991,1992],{},"@RenderBody()",[491,1994,1995],{},"@RenderSection()"," cho phần động:",[484,1998,2000],{"className":1566,"code":1999,"language":1568,"meta":493,"style":493},"\u003C!-- _Layout.cshtml -->\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n    \u003Ctitle>@ViewBag.Title\u003C\u002Ftitle>\n    @await RenderSectionAsync(\"styles\", required: false)\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Cnav>...\u003C\u002Fnav>\n    @RenderBody()          @* Content của page con *@\n    @await RenderSectionAsync(\"scripts\", required: false)\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[491,2001,2002,2008,2021,2029,2038,2056,2061,2069,2078,2096,2101,2106,2114],{"__ignoreMap":493},[790,2003,2004],{"class":792,"line":793},[790,2005,2007],{"class":2006},"sHwdD","\u003C!-- _Layout.cshtml -->\n",[790,2009,2010,2013,2016,2019],{"class":792,"line":799},[790,2011,2012],{"class":1586},"\u003C!",[790,2014,2015],{"class":1590},"DOCTYPE",[790,2017,2018],{"class":1675}," html",[790,2020,1630],{"class":1586},[790,2022,2023,2025,2027],{"class":792,"line":805},[790,2024,1587],{"class":1586},[790,2026,1568],{"class":1590},[790,2028,1630],{"class":1586},[790,2030,2031,2033,2036],{"class":792,"line":811},[790,2032,1587],{"class":1586},[790,2034,2035],{"class":1590},"head",[790,2037,1630],{"class":1586},[790,2039,2040,2042,2045,2047,2050,2052,2054],{"class":792,"line":848},[790,2041,1616],{"class":1586},[790,2043,2044],{"class":1590},"title",[790,2046,1593],{"class":1586},[790,2048,2049],{"class":1575},"@ViewBag.Title",[790,2051,1599],{"class":1586},[790,2053,2044],{"class":1590},[790,2055,1630],{"class":1586},[790,2057,2058],{"class":792,"line":854},[790,2059,2060],{"class":1575},"    @await RenderSectionAsync(\"styles\", required: false)\n",[790,2062,2063,2065,2067],{"class":792,"line":860},[790,2064,1599],{"class":1586},[790,2066,2035],{"class":1590},[790,2068,1630],{"class":1586},[790,2070,2071,2073,2076],{"class":792,"line":866},[790,2072,1587],{"class":1586},[790,2074,2075],{"class":1590},"body",[790,2077,1630],{"class":1586},[790,2079,2080,2082,2085,2087,2090,2092,2094],{"class":792,"line":1019},[790,2081,1616],{"class":1586},[790,2083,2084],{"class":1590},"nav",[790,2086,1593],{"class":1586},[790,2088,2089],{"class":1575},"...",[790,2091,1599],{"class":1586},[790,2093,2084],{"class":1590},[790,2095,1630],{"class":1586},[790,2097,2098],{"class":792,"line":1025},[790,2099,2100],{"class":1575},"    @RenderBody()          @* Content của page con *@\n",[790,2102,2103],{"class":792,"line":1031},[790,2104,2105],{"class":1575},"    @await RenderSectionAsync(\"scripts\", required: false)\n",[790,2107,2108,2110,2112],{"class":792,"line":1037},[790,2109,1599],{"class":1586},[790,2111,2075],{"class":1590},[790,2113,1630],{"class":1586},[790,2115,2116,2118,2120],{"class":792,"line":1043},[790,2117,1599],{"class":1586},[790,2119,1568],{"class":1590},[790,2121,1630],{"class":1586},[432,2123,2124,2125,2128],{},"Page con dùng ",[491,2126,2127],{},"@section"," để điền vào:",[484,2130,2132],{"className":1566,"code":2131,"language":1568,"meta":493,"style":493},"@{\n    Layout = \"_Layout\";\n    ViewBag.Title = \"Home\";\n}\n@section styles {\n    \u003Clink rel=\"stylesheet\" href=\"~\u002Fcss\u002Fhome.css\" \u002F>\n}\n\u003Ch1>Welcome\u003C\u002Fh1>\n",[491,2133,2134,2139,2144,2149,2153,2158,2192,2196],{"__ignoreMap":493},[790,2135,2136],{"class":792,"line":793},[790,2137,2138],{"class":1575},"@{\n",[790,2140,2141],{"class":792,"line":799},[790,2142,2143],{"class":1575},"    Layout = \"_Layout\";\n",[790,2145,2146],{"class":792,"line":805},[790,2147,2148],{"class":1575},"    ViewBag.Title = \"Home\";\n",[790,2150,2151],{"class":792,"line":811},[790,2152,869],{"class":1575},[790,2154,2155],{"class":792,"line":848},[790,2156,2157],{"class":1575},"@section styles {\n",[790,2159,2160,2162,2165,2168,2170,2172,2175,2177,2180,2182,2184,2187,2189],{"class":792,"line":854},[790,2161,1616],{"class":1586},[790,2163,2164],{"class":1590},"link",[790,2166,2167],{"class":1675}," rel",[790,2169,1679],{"class":1586},[790,2171,1682],{"class":1586},[790,2173,2174],{"class":1685},"stylesheet",[790,2176,1682],{"class":1586},[790,2178,2179],{"class":1675}," href",[790,2181,1679],{"class":1586},[790,2183,1682],{"class":1586},[790,2185,2186],{"class":1685},"~\u002Fcss\u002Fhome.css",[790,2188,1682],{"class":1586},[790,2190,2191],{"class":1586}," \u002F>\n",[790,2193,2194],{"class":792,"line":860},[790,2195,869],{"class":1575},[790,2197,2198,2200,2203,2205,2208,2210,2212],{"class":792,"line":866},[790,2199,1587],{"class":1586},[790,2201,2202],{"class":1590},"h1",[790,2204,1593],{"class":1586},[790,2206,2207],{"class":1575},"Welcome",[790,2209,1599],{"class":1586},[790,2211,2202],{"class":1590},[790,2213,1630],{"class":1586},[443,2215],{},[446,2217,2219],{"id":2218},"g-model-validation","G. Model Validation",[451,2221,2223],{"id":2222},"_20-data-annotation-validation","20. 🟢 Data Annotation validation?",[484,2225,2227],{"className":784,"code":2226,"language":786,"meta":493,"style":493},"public class CreateUserDto {\n    [Required(ErrorMessage = \"Name is required\")]\n    [StringLength(100, MinimumLength = 2)]\n    public string Name { get; set; } = \"\";\n    \n    [Required]\n    [EmailAddress]\n    public string Email { get; set; } = \"\";\n    \n    [Range(18, 120)]\n    public int Age { get; set; }\n    \n    [Phone]\n    public string? Phone { get; set; }\n    \n    [Url]\n    public string? Website { get; set; }\n    \n    [RegularExpression(@\"^[A-Z]{2,}$\", ErrorMessage = \"Code phải là chữ hoa\")]\n    public string? Code { get; set; }\n}\n",[491,2228,2229,2234,2239,2244,2249,2253,2258,2263,2268,2272,2277,2282,2286,2291,2296,2300,2306,2312,2317,2323,2329],{"__ignoreMap":493},[790,2230,2231],{"class":792,"line":793},[790,2232,2233],{},"public class CreateUserDto {\n",[790,2235,2236],{"class":792,"line":799},[790,2237,2238],{},"    [Required(ErrorMessage = \"Name is required\")]\n",[790,2240,2241],{"class":792,"line":805},[790,2242,2243],{},"    [StringLength(100, MinimumLength = 2)]\n",[790,2245,2246],{"class":792,"line":811},[790,2247,2248],{},"    public string Name { get; set; } = \"\";\n",[790,2250,2251],{"class":792,"line":848},[790,2252,851],{},[790,2254,2255],{"class":792,"line":854},[790,2256,2257],{},"    [Required]\n",[790,2259,2260],{"class":792,"line":860},[790,2261,2262],{},"    [EmailAddress]\n",[790,2264,2265],{"class":792,"line":866},[790,2266,2267],{},"    public string Email { get; set; } = \"\";\n",[790,2269,2270],{"class":792,"line":1019},[790,2271,851],{},[790,2273,2274],{"class":792,"line":1025},[790,2275,2276],{},"    [Range(18, 120)]\n",[790,2278,2279],{"class":792,"line":1031},[790,2280,2281],{},"    public int Age { get; set; }\n",[790,2283,2284],{"class":792,"line":1037},[790,2285,851],{},[790,2287,2288],{"class":792,"line":1043},[790,2289,2290],{},"    [Phone]\n",[790,2292,2293],{"class":792,"line":1049},[790,2294,2295],{},"    public string? Phone { get; set; }\n",[790,2297,2298],{"class":792,"line":1055},[790,2299,851],{},[790,2301,2303],{"class":792,"line":2302},16,[790,2304,2305],{},"    [Url]\n",[790,2307,2309],{"class":792,"line":2308},17,[790,2310,2311],{},"    public string? Website { get; set; }\n",[790,2313,2315],{"class":792,"line":2314},18,[790,2316,851],{},[790,2318,2320],{"class":792,"line":2319},19,[790,2321,2322],{},"    [RegularExpression(@\"^[A-Z]{2,}$\", ErrorMessage = \"Code phải là chữ hoa\")]\n",[790,2324,2326],{"class":792,"line":2325},20,[790,2327,2328],{},"    public string? Code { get; set; }\n",[790,2330,2332],{"class":792,"line":2331},21,[790,2333,869],{},[451,2335,2337,2338,2341],{"id":2336},"_21-modelstateisvalid-và-fluentvalidation","21. 🔵 ",[491,2339,2340],{},"ModelState.IsValid"," và FluentValidation?",[484,2343,2345],{"className":784,"code":2344,"language":786,"meta":493,"style":493},"\u002F\u002F Manual check (không dùng [ApiController])\nif (!ModelState.IsValid)\n    return BadRequest(ModelState);\n\n\u002F\u002F FluentValidation — cho business rule phức tạp\npublic class CreateUserValidator : AbstractValidator\u003CCreateUserDto> {\n    public CreateUserValidator(IUserRepository repo) {\n        RuleFor(x => x.Email).EmailAddress()\n            .MustAsync(async (email, ct) => !await repo.ExistsAsync(email))\n            .WithMessage(\"Email already taken\");\n        \n        RuleFor(x => x.Age).GreaterThanOrEqualTo(18);\n    }\n}\n",[491,2346,2347,2352,2357,2362,2366,2371,2376,2381,2386,2391,2396,2401,2406,2411],{"__ignoreMap":493},[790,2348,2349],{"class":792,"line":793},[790,2350,2351],{},"\u002F\u002F Manual check (không dùng [ApiController])\n",[790,2353,2354],{"class":792,"line":799},[790,2355,2356],{},"if (!ModelState.IsValid)\n",[790,2358,2359],{"class":792,"line":805},[790,2360,2361],{},"    return BadRequest(ModelState);\n",[790,2363,2364],{"class":792,"line":811},[790,2365,1130],{"emptyLinePlaceholder":1129},[790,2367,2368],{"class":792,"line":848},[790,2369,2370],{},"\u002F\u002F FluentValidation — cho business rule phức tạp\n",[790,2372,2373],{"class":792,"line":854},[790,2374,2375],{},"public class CreateUserValidator : AbstractValidator\u003CCreateUserDto> {\n",[790,2377,2378],{"class":792,"line":860},[790,2379,2380],{},"    public CreateUserValidator(IUserRepository repo) {\n",[790,2382,2383],{"class":792,"line":866},[790,2384,2385],{},"        RuleFor(x => x.Email).EmailAddress()\n",[790,2387,2388],{"class":792,"line":1019},[790,2389,2390],{},"            .MustAsync(async (email, ct) => !await repo.ExistsAsync(email))\n",[790,2392,2393],{"class":792,"line":1025},[790,2394,2395],{},"            .WithMessage(\"Email already taken\");\n",[790,2397,2398],{"class":792,"line":1031},[790,2399,2400],{},"        \n",[790,2402,2403],{"class":792,"line":1037},[790,2404,2405],{},"        RuleFor(x => x.Age).GreaterThanOrEqualTo(18);\n",[790,2407,2408],{"class":792,"line":1043},[790,2409,2410],{},"    }\n",[790,2412,2413],{"class":792,"line":1049},[790,2414,869],{},[432,2416,2417,2418,2421],{},"FluentValidation tích hợp ASP.NET Core qua ",[491,2419,2420],{},"services.AddFluentValidation()"," — auto chạy trước action.",[443,2423],{},[446,2425,2427],{"id":2426},"h-web-api","H. Web API",[451,2429,2431],{"id":2430},"_22-rest-api-best-practices","22. 🟢 REST API best practices?",[462,2433,2434,2448,2451,2454,2460,2463,2469],{},[465,2435,2436,2437,2440,2441,2444,2445,1177],{},"URL là ",[435,2438,2439],{},"resource"," (danh từ): ",[491,2442,2443],{},"\u002Fapi\u002Fusers",", ",[491,2446,2447],{},"\u002Fapi\u002Forders\u002F{id}",[465,2449,2450],{},"HTTP method = hành động: GET\u002FPOST\u002FPUT\u002FPATCH\u002FDELETE.",[465,2452,2453],{},"Status code đúng: 200\u002F201\u002F204\u002F400\u002F401\u002F403\u002F404\u002F422\u002F500.",[465,2455,2456,2457,1177],{},"Versioning qua URL: ",[491,2458,2459],{},"\u002Fapi\u002Fv1\u002Fusers",[465,2461,2462],{},"JSON camelCase response.",[465,2464,2465,2466,1177],{},"Paging: ",[491,2467,2468],{},"?page=1&size=20",[465,2470,2471,2472,1177],{},"Filtering: ",[491,2473,2474],{},"?city=HCM&active=true",[451,2476,2478],{"id":2477},"_23-content-negotiation-là-gì","23. ⭐🔵 Content Negotiation là gì?",[432,2480,2481,2482,2485],{},"Client gửi ",[491,2483,2484],{},"Accept"," header → server trả format phù hợp:",[484,2487,2490],{"className":2488,"code":2489,"language":489},[487],"Accept: application\u002Fjson          → trả JSON\nAccept: application\u002Fxml           → trả XML (nếu support)\nAccept: text\u002Fcsv                  → trả CSV (custom formatter)\n",[491,2491,2489],{"__ignoreMap":493},[484,2493,2495],{"className":784,"code":2494,"language":786,"meta":493,"style":493},"\u002F\u002F Thêm XML support\nbuilder.Services.AddControllers()\n    .AddXmlSerializerFormatters();\n\n\u002F\u002F Custom formatter\nbuilder.Services.AddControllers(options => {\n    options.OutputFormatters.Add(new CsvOutputFormatter());\n});\n",[491,2496,2497,2502,2507,2512,2516,2521,2525,2530],{"__ignoreMap":493},[790,2498,2499],{"class":792,"line":793},[790,2500,2501],{},"\u002F\u002F Thêm XML support\n",[790,2503,2504],{"class":792,"line":799},[790,2505,2506],{},"builder.Services.AddControllers()\n",[790,2508,2509],{"class":792,"line":805},[790,2510,2511],{},"    .AddXmlSerializerFormatters();\n",[790,2513,2514],{"class":792,"line":811},[790,2515,1130],{"emptyLinePlaceholder":1129},[790,2517,2518],{"class":792,"line":848},[790,2519,2520],{},"\u002F\u002F Custom formatter\n",[790,2522,2523],{"class":792,"line":854},[790,2524,1430],{},[790,2526,2527],{"class":792,"line":860},[790,2528,2529],{},"    options.OutputFormatters.Add(new CsvOutputFormatter());\n",[790,2531,2532],{"class":792,"line":866},[790,2533,1440],{},[451,2535,2537],{"id":2536},"_24-versioning-api-các-cách","24. 🔵 Versioning API — các cách?",[484,2539,2541],{"className":784,"code":2540,"language":786,"meta":493,"style":493},"\u002F\u002F Dùng Asp.Versioning.Http package\n\n\u002F\u002F 1. URL versioning (phổ biến nhất)\n[ApiVersion(\"1.0\")]\n[Route(\"api\u002Fv{version:apiVersion}\u002Fusers\")]\npublic class UsersV1Controller : ControllerBase { }\n\n[ApiVersion(\"2.0\")]\n[Route(\"api\u002Fv{version:apiVersion}\u002Fusers\")]\npublic class UsersV2Controller : ControllerBase { }\n\n\u002F\u002F 2. Header versioning\n\u002F\u002F Request: Api-Version: 2.0\n\n\u002F\u002F 3. Query string\n\u002F\u002F Request: \u002Fapi\u002Fusers?api-version=2.0\n\n\u002F\u002F nuxt.config:\nbuilder.Services.AddApiVersioning(opt => {\n    opt.DefaultApiVersion = new ApiVersion(1, 0);\n    opt.AssumeDefaultVersionWhenUnspecified = true;\n    opt.ReportApiVersions = true;           \u002F\u002F header: api-supported-versions\n}).AddApiExplorer(opt => {\n    opt.GroupNameFormat = \"'v'VVV\";\n    opt.SubstituteApiVersionInUrl = true;\n});\n",[491,2542,2543,2548,2552,2557,2562,2567,2572,2576,2581,2585,2590,2594,2599,2604,2608,2613,2618,2622,2627,2632,2637,2642,2648,2654,2660,2666],{"__ignoreMap":493},[790,2544,2545],{"class":792,"line":793},[790,2546,2547],{},"\u002F\u002F Dùng Asp.Versioning.Http package\n",[790,2549,2550],{"class":792,"line":799},[790,2551,1130],{"emptyLinePlaceholder":1129},[790,2553,2554],{"class":792,"line":805},[790,2555,2556],{},"\u002F\u002F 1. URL versioning (phổ biến nhất)\n",[790,2558,2559],{"class":792,"line":811},[790,2560,2561],{},"[ApiVersion(\"1.0\")]\n",[790,2563,2564],{"class":792,"line":848},[790,2565,2566],{},"[Route(\"api\u002Fv{version:apiVersion}\u002Fusers\")]\n",[790,2568,2569],{"class":792,"line":854},[790,2570,2571],{},"public class UsersV1Controller : ControllerBase { }\n",[790,2573,2574],{"class":792,"line":860},[790,2575,1130],{"emptyLinePlaceholder":1129},[790,2577,2578],{"class":792,"line":866},[790,2579,2580],{},"[ApiVersion(\"2.0\")]\n",[790,2582,2583],{"class":792,"line":1019},[790,2584,2566],{},[790,2586,2587],{"class":792,"line":1025},[790,2588,2589],{},"public class UsersV2Controller : ControllerBase { }\n",[790,2591,2592],{"class":792,"line":1031},[790,2593,1130],{"emptyLinePlaceholder":1129},[790,2595,2596],{"class":792,"line":1037},[790,2597,2598],{},"\u002F\u002F 2. Header versioning\n",[790,2600,2601],{"class":792,"line":1043},[790,2602,2603],{},"\u002F\u002F Request: Api-Version: 2.0\n",[790,2605,2606],{"class":792,"line":1049},[790,2607,1130],{"emptyLinePlaceholder":1129},[790,2609,2610],{"class":792,"line":1055},[790,2611,2612],{},"\u002F\u002F 3. Query string\n",[790,2614,2615],{"class":792,"line":2302},[790,2616,2617],{},"\u002F\u002F Request: \u002Fapi\u002Fusers?api-version=2.0\n",[790,2619,2620],{"class":792,"line":2308},[790,2621,1130],{"emptyLinePlaceholder":1129},[790,2623,2624],{"class":792,"line":2314},[790,2625,2626],{},"\u002F\u002F nuxt.config:\n",[790,2628,2629],{"class":792,"line":2319},[790,2630,2631],{},"builder.Services.AddApiVersioning(opt => {\n",[790,2633,2634],{"class":792,"line":2325},[790,2635,2636],{},"    opt.DefaultApiVersion = new ApiVersion(1, 0);\n",[790,2638,2639],{"class":792,"line":2331},[790,2640,2641],{},"    opt.AssumeDefaultVersionWhenUnspecified = true;\n",[790,2643,2645],{"class":792,"line":2644},22,[790,2646,2647],{},"    opt.ReportApiVersions = true;           \u002F\u002F header: api-supported-versions\n",[790,2649,2651],{"class":792,"line":2650},23,[790,2652,2653],{},"}).AddApiExplorer(opt => {\n",[790,2655,2657],{"class":792,"line":2656},24,[790,2658,2659],{},"    opt.GroupNameFormat = \"'v'VVV\";\n",[790,2661,2663],{"class":792,"line":2662},25,[790,2664,2665],{},"    opt.SubstituteApiVersionInUrl = true;\n",[790,2667,2669],{"class":792,"line":2668},26,[790,2670,1440],{},[451,2672,2674],{"id":2673},"_25-rate-limiting-aspnet-core-7-built-in","25. ⭐🔵 Rate Limiting (ASP.NET Core 7+ built-in)?",[484,2676,2678],{"className":784,"code":2677,"language":786,"meta":493,"style":493},"\u002F\u002F Program.cs\nbuilder.Services.AddRateLimiter(opt => {\n    \u002F\u002F Fixed window — 10 req \u002F 10s per IP\n    opt.AddFixedWindowLimiter(\"fixed\", o => {\n        o.PermitLimit = 10;\n        o.Window = TimeSpan.FromSeconds(10);\n        o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n        o.QueueLimit = 5;\n    });\n    \n    \u002F\u002F Sliding window — smoothes out traffic\n    opt.AddSlidingWindowLimiter(\"sliding\", o => {\n        o.PermitLimit = 100;\n        o.Window = TimeSpan.FromMinutes(1);\n        o.SegmentsPerWindow = 4;\n    });\n    \n    \u002F\u002F Token bucket — cho burst\n    opt.AddTokenBucketLimiter(\"token\", o => {\n        o.TokenLimit = 100;\n        o.ReplenishmentPeriod = TimeSpan.FromSeconds(1);\n        o.TokensPerPeriod = 10;\n    });\n    \n    opt.RejectionStatusCode = 429;    \u002F\u002F Too Many Requests\n});\n\napp.UseRateLimiter();\n\n\u002F\u002F Áp dụng per-endpoint\n[EnableRateLimiting(\"fixed\")]\n[HttpGet]\npublic IActionResult GetAll() { }\n",[491,2679,2680,2685,2690,2695,2700,2705,2710,2715,2720,2725,2729,2734,2739,2744,2749,2754,2758,2762,2767,2772,2777,2782,2787,2791,2795,2800,2804,2809,2815,2820,2826,2832,2837],{"__ignoreMap":493},[790,2681,2682],{"class":792,"line":793},[790,2683,2684],{},"\u002F\u002F Program.cs\n",[790,2686,2687],{"class":792,"line":799},[790,2688,2689],{},"builder.Services.AddRateLimiter(opt => {\n",[790,2691,2692],{"class":792,"line":805},[790,2693,2694],{},"    \u002F\u002F Fixed window — 10 req \u002F 10s per IP\n",[790,2696,2697],{"class":792,"line":811},[790,2698,2699],{},"    opt.AddFixedWindowLimiter(\"fixed\", o => {\n",[790,2701,2702],{"class":792,"line":848},[790,2703,2704],{},"        o.PermitLimit = 10;\n",[790,2706,2707],{"class":792,"line":854},[790,2708,2709],{},"        o.Window = TimeSpan.FromSeconds(10);\n",[790,2711,2712],{"class":792,"line":860},[790,2713,2714],{},"        o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;\n",[790,2716,2717],{"class":792,"line":866},[790,2718,2719],{},"        o.QueueLimit = 5;\n",[790,2721,2722],{"class":792,"line":1019},[790,2723,2724],{},"    });\n",[790,2726,2727],{"class":792,"line":1025},[790,2728,851],{},[790,2730,2731],{"class":792,"line":1031},[790,2732,2733],{},"    \u002F\u002F Sliding window — smoothes out traffic\n",[790,2735,2736],{"class":792,"line":1037},[790,2737,2738],{},"    opt.AddSlidingWindowLimiter(\"sliding\", o => {\n",[790,2740,2741],{"class":792,"line":1043},[790,2742,2743],{},"        o.PermitLimit = 100;\n",[790,2745,2746],{"class":792,"line":1049},[790,2747,2748],{},"        o.Window = TimeSpan.FromMinutes(1);\n",[790,2750,2751],{"class":792,"line":1055},[790,2752,2753],{},"        o.SegmentsPerWindow = 4;\n",[790,2755,2756],{"class":792,"line":2302},[790,2757,2724],{},[790,2759,2760],{"class":792,"line":2308},[790,2761,851],{},[790,2763,2764],{"class":792,"line":2314},[790,2765,2766],{},"    \u002F\u002F Token bucket — cho burst\n",[790,2768,2769],{"class":792,"line":2319},[790,2770,2771],{},"    opt.AddTokenBucketLimiter(\"token\", o => {\n",[790,2773,2774],{"class":792,"line":2325},[790,2775,2776],{},"        o.TokenLimit = 100;\n",[790,2778,2779],{"class":792,"line":2331},[790,2780,2781],{},"        o.ReplenishmentPeriod = TimeSpan.FromSeconds(1);\n",[790,2783,2784],{"class":792,"line":2644},[790,2785,2786],{},"        o.TokensPerPeriod = 10;\n",[790,2788,2789],{"class":792,"line":2650},[790,2790,2724],{},[790,2792,2793],{"class":792,"line":2656},[790,2794,851],{},[790,2796,2797],{"class":792,"line":2662},[790,2798,2799],{},"    opt.RejectionStatusCode = 429;    \u002F\u002F Too Many Requests\n",[790,2801,2802],{"class":792,"line":2668},[790,2803,1440],{},[790,2805,2807],{"class":792,"line":2806},27,[790,2808,1130],{"emptyLinePlaceholder":1129},[790,2810,2812],{"class":792,"line":2811},28,[790,2813,2814],{},"app.UseRateLimiter();\n",[790,2816,2818],{"class":792,"line":2817},29,[790,2819,1130],{"emptyLinePlaceholder":1129},[790,2821,2823],{"class":792,"line":2822},30,[790,2824,2825],{},"\u002F\u002F Áp dụng per-endpoint\n",[790,2827,2829],{"class":792,"line":2828},31,[790,2830,2831],{},"[EnableRateLimiting(\"fixed\")]\n",[790,2833,2835],{"class":792,"line":2834},32,[790,2836,1473],{},[790,2838,2840],{"class":792,"line":2839},33,[790,2841,1483],{},[451,2843,2845],{"id":2844},"_26-output-caching-aspnet-core-7-built-in","26. 🔵 Output Caching (ASP.NET Core 7+ built-in)?",[484,2847,2849],{"className":784,"code":2848,"language":786,"meta":493,"style":493},"builder.Services.AddOutputCache(opt => {\n    opt.AddBasePolicy(b => b.Expire(TimeSpan.FromMinutes(5)));\n    opt.AddPolicy(\"ShortCache\", b => b.Expire(TimeSpan.FromSeconds(30)));\n});\n\napp.UseOutputCache();\n\n\u002F\u002F Action\n[HttpGet]\n[OutputCache(Duration = 60)]\npublic IActionResult GetProducts() { }\n\n\u002F\u002F Với vary by query\n[OutputCache(Duration = 120, VaryByQueryKeys = new[] { \"category\", \"page\" })]\npublic IActionResult GetByCategory(string category, int page = 1) { }\n",[491,2850,2851,2856,2861,2866,2870,2874,2879,2883,2888,2892,2897,2902,2906,2911,2916],{"__ignoreMap":493},[790,2852,2853],{"class":792,"line":793},[790,2854,2855],{},"builder.Services.AddOutputCache(opt => {\n",[790,2857,2858],{"class":792,"line":799},[790,2859,2860],{},"    opt.AddBasePolicy(b => b.Expire(TimeSpan.FromMinutes(5)));\n",[790,2862,2863],{"class":792,"line":805},[790,2864,2865],{},"    opt.AddPolicy(\"ShortCache\", b => b.Expire(TimeSpan.FromSeconds(30)));\n",[790,2867,2868],{"class":792,"line":811},[790,2869,1440],{},[790,2871,2872],{"class":792,"line":848},[790,2873,1130],{"emptyLinePlaceholder":1129},[790,2875,2876],{"class":792,"line":854},[790,2877,2878],{},"app.UseOutputCache();\n",[790,2880,2881],{"class":792,"line":860},[790,2882,1130],{"emptyLinePlaceholder":1129},[790,2884,2885],{"class":792,"line":866},[790,2886,2887],{},"\u002F\u002F Action\n",[790,2889,2890],{"class":792,"line":1019},[790,2891,1473],{},[790,2893,2894],{"class":792,"line":1025},[790,2895,2896],{},"[OutputCache(Duration = 60)]\n",[790,2898,2899],{"class":792,"line":1031},[790,2900,2901],{},"public IActionResult GetProducts() { }\n",[790,2903,2904],{"class":792,"line":1037},[790,2905,1130],{"emptyLinePlaceholder":1129},[790,2907,2908],{"class":792,"line":1043},[790,2909,2910],{},"\u002F\u002F Với vary by query\n",[790,2912,2913],{"class":792,"line":1049},[790,2914,2915],{},"[OutputCache(Duration = 120, VaryByQueryKeys = new[] { \"category\", \"page\" })]\n",[790,2917,2918],{"class":792,"line":1055},[790,2919,2920],{},"public IActionResult GetByCategory(string category, int page = 1) { }\n",[432,2922,2923,2924,2927,2928,2931,2932,2934,2935,2938],{},"Khác ",[491,2925,2926],{},"ResponseCache",": ",[435,2929,2930],{},"Output Cache"," store ở server (memory\u002FRedis). ",[491,2933,2926],{}," chỉ set HTTP header ",[491,2936,2937],{},"Cache-Control"," để browser\u002FCDN cache.",[451,2940,2942],{"id":2941},"_27-hybridcache-net-9-mới","27. ⭐🔵 HybridCache (.NET 9 mới)?",[484,2944,2946],{"className":784,"code":2945,"language":786,"meta":493,"style":493},"\u002F\u002F Combine in-memory (L1) + Redis (L2) + stampede protection\nbuilder.Services.AddHybridCache();\n\n\u002F\u002F Inject\npublic class UserService {\n    private readonly HybridCache _cache;\n    public UserService(HybridCache cache) => _cache = cache;\n    \n    public async Task\u003CUser?> GetAsync(int id, CancellationToken ct = default) {\n        return await _cache.GetOrCreateAsync(\n            $\"user:{id}\",\n            async token => await _db.Users.FindAsync(id, token),\n            cancellationToken: ct);\n    }\n}\n",[491,2947,2948,2953,2958,2962,2967,2972,2977,2982,2986,2991,2996,3001,3006,3011,3015],{"__ignoreMap":493},[790,2949,2950],{"class":792,"line":793},[790,2951,2952],{},"\u002F\u002F Combine in-memory (L1) + Redis (L2) + stampede protection\n",[790,2954,2955],{"class":792,"line":799},[790,2956,2957],{},"builder.Services.AddHybridCache();\n",[790,2959,2960],{"class":792,"line":805},[790,2961,1130],{"emptyLinePlaceholder":1129},[790,2963,2964],{"class":792,"line":811},[790,2965,2966],{},"\u002F\u002F Inject\n",[790,2968,2969],{"class":792,"line":848},[790,2970,2971],{},"public class UserService {\n",[790,2973,2974],{"class":792,"line":854},[790,2975,2976],{},"    private readonly HybridCache _cache;\n",[790,2978,2979],{"class":792,"line":860},[790,2980,2981],{},"    public UserService(HybridCache cache) => _cache = cache;\n",[790,2983,2984],{"class":792,"line":866},[790,2985,851],{},[790,2987,2988],{"class":792,"line":1019},[790,2989,2990],{},"    public async Task\u003CUser?> GetAsync(int id, CancellationToken ct = default) {\n",[790,2992,2993],{"class":792,"line":1025},[790,2994,2995],{},"        return await _cache.GetOrCreateAsync(\n",[790,2997,2998],{"class":792,"line":1031},[790,2999,3000],{},"            $\"user:{id}\",\n",[790,3002,3003],{"class":792,"line":1037},[790,3004,3005],{},"            async token => await _db.Users.FindAsync(id, token),\n",[790,3007,3008],{"class":792,"line":1043},[790,3009,3010],{},"            cancellationToken: ct);\n",[790,3012,3013],{"class":792,"line":1049},[790,3014,2410],{},[790,3016,3017],{"class":792,"line":1055},[790,3018,869],{},[432,3020,3021],{},[435,3022,3023],{},"Lợi ích vs IMemoryCache + IDistributedCache:",[462,3025,3026,3029,3032],{},[465,3027,3028],{},"Không cần tự viết stampede protection (cache stampede: nhiều request cùng lúc cache miss → cùng query DB).",[465,3030,3031],{},"Tự động L1 → L2 fallback.",[465,3033,3034],{},"Single API cho cả 2 level.",[443,3036],{},[446,3038,3040],{"id":3039},"i-minimal-api-net-6","I. Minimal API (NET 6+)",[451,3042,3044],{"id":3043},"_28-minimal-api-vs-controller-khi-nào-chọn","28. ⭐🔵 Minimal API vs Controller — khi nào chọn?",[484,3046,3048],{"className":784,"code":3047,"language":786,"meta":493,"style":493},"\u002F\u002F Minimal API — ít code hơn, nhanh hơn ~5-10% throughput\nvar app = WebApplication.Create();\n\napp.MapGet(\"\u002Fusers\u002F{id:int}\", async (int id, IUserService service) =>\n    await service.GetByIdAsync(id) is User u ? Results.Ok(u) : Results.NotFound());\n\napp.MapPost(\"\u002Fusers\", async (CreateUserDto dto, IUserService service) => {\n    var created = await service.CreateAsync(dto);\n    return Results.Created($\"\u002Fusers\u002F{created.Id}\", created);\n});\n",[491,3049,3050,3055,3060,3064,3069,3074,3078,3083,3088,3093],{"__ignoreMap":493},[790,3051,3052],{"class":792,"line":793},[790,3053,3054],{},"\u002F\u002F Minimal API — ít code hơn, nhanh hơn ~5-10% throughput\n",[790,3056,3057],{"class":792,"line":799},[790,3058,3059],{},"var app = WebApplication.Create();\n",[790,3061,3062],{"class":792,"line":805},[790,3063,1130],{"emptyLinePlaceholder":1129},[790,3065,3066],{"class":792,"line":811},[790,3067,3068],{},"app.MapGet(\"\u002Fusers\u002F{id:int}\", async (int id, IUserService service) =>\n",[790,3070,3071],{"class":792,"line":848},[790,3072,3073],{},"    await service.GetByIdAsync(id) is User u ? Results.Ok(u) : Results.NotFound());\n",[790,3075,3076],{"class":792,"line":854},[790,3077,1130],{"emptyLinePlaceholder":1129},[790,3079,3080],{"class":792,"line":860},[790,3081,3082],{},"app.MapPost(\"\u002Fusers\", async (CreateUserDto dto, IUserService service) => {\n",[790,3084,3085],{"class":792,"line":866},[790,3086,3087],{},"    var created = await service.CreateAsync(dto);\n",[790,3089,3090],{"class":792,"line":1019},[790,3091,3092],{},"    return Results.Created($\"\u002Fusers\u002F{created.Id}\", created);\n",[790,3094,3095],{"class":792,"line":1025},[790,3096,1440],{},[499,3098,3099,3111],{},[502,3100,3101],{},[505,3102,3103,3105,3108],{},[508,3104],{},[508,3106,3107],{},"Minimal API",[508,3109,3110],{},"Controller-based",[517,3112,3113,3124,3134,3145,3156,3167],{},[505,3114,3115,3118,3121],{},[522,3116,3117],{},"Verbosity",[522,3119,3120],{},"Ít code",[522,3122,3123],{},"Nhiều boilerplate",[505,3125,3126,3128,3131],{},[522,3127,667],{},[522,3129,3130],{},"~5-10% nhanh hơn",[522,3132,3133],{},"Tốt",[505,3135,3136,3139,3142],{},[522,3137,3138],{},"Tổ chức lớn",[522,3140,3141],{},"Khó (dùng Route Groups)",[522,3143,3144],{},"Dễ (class + method)",[505,3146,3147,3150,3153],{},[522,3148,3149],{},"Filters",[522,3151,3152],{},"Hạn chế (endpoint filter)",[522,3154,3155],{},"Đầy đủ (Action, Result, Exception)",[505,3157,3158,3161,3164],{},[522,3159,3160],{},"Convention",[522,3162,3163],{},"Ít",[522,3165,3166],{},"Nhiều (routing convention, naming)",[505,3168,3169,3173,3176],{},[522,3170,3171],{},[435,3172,1735],{},[522,3174,3175],{},"Microservice, minimal API, demo",[522,3177,3178],{},"App lớn, nhiều dev",[451,3180,3182],{"id":3181},"_29-route-groups-trong-minimal-api","29. 🔵 Route Groups trong Minimal API?",[484,3184,3186],{"className":784,"code":3185,"language":786,"meta":493,"style":493},"\u002F\u002F Nhóm route có prefix + shared middleware\nvar users = app.MapGroup(\"\u002Fapi\u002Fv1\u002Fusers\").RequireAuthorization();\n\nusers.MapGet(\"\u002F\", async (IUserService s) => await s.GetAllAsync());\nusers.MapGet(\"\u002F{id:int}\", async (int id, IUserService s) => {\n    var u = await s.GetByIdAsync(id);\n    return u is not null ? Results.Ok(u) : Results.NotFound();\n});\nusers.MapPost(\"\u002F\", async (CreateUserDto dto, IUserService s) => {\n    var created = await s.CreateAsync(dto);\n    return Results.Created($\"\u002Fapi\u002Fv1\u002Fusers\u002F{created.Id}\", created);\n});\n",[491,3187,3188,3193,3198,3202,3207,3212,3217,3222,3226,3231,3236,3241],{"__ignoreMap":493},[790,3189,3190],{"class":792,"line":793},[790,3191,3192],{},"\u002F\u002F Nhóm route có prefix + shared middleware\n",[790,3194,3195],{"class":792,"line":799},[790,3196,3197],{},"var users = app.MapGroup(\"\u002Fapi\u002Fv1\u002Fusers\").RequireAuthorization();\n",[790,3199,3200],{"class":792,"line":805},[790,3201,1130],{"emptyLinePlaceholder":1129},[790,3203,3204],{"class":792,"line":811},[790,3205,3206],{},"users.MapGet(\"\u002F\", async (IUserService s) => await s.GetAllAsync());\n",[790,3208,3209],{"class":792,"line":848},[790,3210,3211],{},"users.MapGet(\"\u002F{id:int}\", async (int id, IUserService s) => {\n",[790,3213,3214],{"class":792,"line":854},[790,3215,3216],{},"    var u = await s.GetByIdAsync(id);\n",[790,3218,3219],{"class":792,"line":860},[790,3220,3221],{},"    return u is not null ? Results.Ok(u) : Results.NotFound();\n",[790,3223,3224],{"class":792,"line":866},[790,3225,1440],{},[790,3227,3228],{"class":792,"line":1019},[790,3229,3230],{},"users.MapPost(\"\u002F\", async (CreateUserDto dto, IUserService s) => {\n",[790,3232,3233],{"class":792,"line":1025},[790,3234,3235],{},"    var created = await s.CreateAsync(dto);\n",[790,3237,3238],{"class":792,"line":1031},[790,3239,3240],{},"    return Results.Created($\"\u002Fapi\u002Fv1\u002Fusers\u002F{created.Id}\", created);\n",[790,3242,3243],{"class":792,"line":1037},[790,3244,1440],{},[451,3246,3248],{"id":3247},"_30-endpoint-filter-minimal-api","30. 🔵 Endpoint Filter (Minimal API)?",[432,3250,3251],{},"Tương đương Action Filter nhưng cho Minimal API:",[484,3253,3255],{"className":784,"code":3254,"language":786,"meta":493,"style":493},"app.MapPost(\"\u002Fusers\", CreateUser)\n    .AddEndpointFilter\u003CValidationFilter\u003CCreateUserDto>>();\n\npublic class ValidationFilter\u003CT> : IEndpointFilter where T : class {\n    public async ValueTask\u003Cobject?> InvokeAsync(\n        EndpointFilterInvocationContext ctx, EndpointFilterDelegate next) {\n        var dto = ctx.Arguments.OfType\u003CT>().FirstOrDefault();\n        if (dto is null) return Results.BadRequest(\"Invalid body\");\n        \u002F\u002F Validate...\n        return await next(ctx);\n    }\n}\n",[491,3256,3257,3262,3267,3271,3276,3281,3286,3291,3296,3301,3306,3310],{"__ignoreMap":493},[790,3258,3259],{"class":792,"line":793},[790,3260,3261],{},"app.MapPost(\"\u002Fusers\", CreateUser)\n",[790,3263,3264],{"class":792,"line":799},[790,3265,3266],{},"    .AddEndpointFilter\u003CValidationFilter\u003CCreateUserDto>>();\n",[790,3268,3269],{"class":792,"line":805},[790,3270,1130],{"emptyLinePlaceholder":1129},[790,3272,3273],{"class":792,"line":811},[790,3274,3275],{},"public class ValidationFilter\u003CT> : IEndpointFilter where T : class {\n",[790,3277,3278],{"class":792,"line":848},[790,3279,3280],{},"    public async ValueTask\u003Cobject?> InvokeAsync(\n",[790,3282,3283],{"class":792,"line":854},[790,3284,3285],{},"        EndpointFilterInvocationContext ctx, EndpointFilterDelegate next) {\n",[790,3287,3288],{"class":792,"line":860},[790,3289,3290],{},"        var dto = ctx.Arguments.OfType\u003CT>().FirstOrDefault();\n",[790,3292,3293],{"class":792,"line":866},[790,3294,3295],{},"        if (dto is null) return Results.BadRequest(\"Invalid body\");\n",[790,3297,3298],{"class":792,"line":1019},[790,3299,3300],{},"        \u002F\u002F Validate...\n",[790,3302,3303],{"class":792,"line":1025},[790,3304,3305],{},"        return await next(ctx);\n",[790,3307,3308],{"class":792,"line":1031},[790,3309,2410],{},[790,3311,3312],{"class":792,"line":1037},[790,3313,869],{},[443,3315],{},[446,3317,3319],{"id":3318},"j-security","J. Security",[451,3321,3323],{"id":3322},"_31-authentication-vs-authorization-trong-aspnet-core","31. ⭐🔵 Authentication vs Authorization trong ASP.NET Core?",[462,3325,3326,3336],{},[465,3327,3328,3331,3332,3335],{},[435,3329,3330],{},"Authentication",": \"Bạn là ai?\" — xác minh identity, populate ",[491,3333,3334],{},"HttpContext.User"," (ClaimsPrincipal).",[465,3337,3338,3340],{},[435,3339,1317],{},": \"Bạn được làm gì?\" — check role\u002Fpolicy\u002Fclaim.",[484,3342,3344],{"className":784,"code":3343,"language":786,"meta":493,"style":493},"\u002F\u002F Middleware thứ tự BẮT BUỘC:\napp.UseAuthentication();   \u002F\u002F 1. Populate User\napp.UseAuthorization();    \u002F\u002F 2. Check User\n\n\u002F\u002F [Authorize] attribute\n[Authorize]                                    \u002F\u002F chỉ require login\n[Authorize(Roles = \"Admin,Manager\")]           \u002F\u002F require role\n[Authorize(Policy = \"MinAge18\")]               \u002F\u002F require policy\n[AllowAnonymous]                               \u002F\u002F bỏ qua auth\n",[491,3345,3346,3351,3356,3361,3365,3370,3375,3380,3385],{"__ignoreMap":493},[790,3347,3348],{"class":792,"line":793},[790,3349,3350],{},"\u002F\u002F Middleware thứ tự BẮT BUỘC:\n",[790,3352,3353],{"class":792,"line":799},[790,3354,3355],{},"app.UseAuthentication();   \u002F\u002F 1. Populate User\n",[790,3357,3358],{"class":792,"line":805},[790,3359,3360],{},"app.UseAuthorization();    \u002F\u002F 2. Check User\n",[790,3362,3363],{"class":792,"line":811},[790,3364,1130],{"emptyLinePlaceholder":1129},[790,3366,3367],{"class":792,"line":848},[790,3368,3369],{},"\u002F\u002F [Authorize] attribute\n",[790,3371,3372],{"class":792,"line":854},[790,3373,3374],{},"[Authorize]                                    \u002F\u002F chỉ require login\n",[790,3376,3377],{"class":792,"line":860},[790,3378,3379],{},"[Authorize(Roles = \"Admin,Manager\")]           \u002F\u002F require role\n",[790,3381,3382],{"class":792,"line":866},[790,3383,3384],{},"[Authorize(Policy = \"MinAge18\")]               \u002F\u002F require policy\n",[790,3386,3387],{"class":792,"line":1019},[790,3388,3389],{},"[AllowAnonymous]                               \u002F\u002F bỏ qua auth\n",[451,3391,3393],{"id":3392},"_32-jwt-authentication-setup","32. 🔵 JWT Authentication setup?",[484,3395,3397],{"className":784,"code":3396,"language":786,"meta":493,"style":493},"\u002F\u002F Program.cs\nbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n    .AddJwtBearer(opt => {\n        opt.TokenValidationParameters = new TokenValidationParameters {\n            ValidateIssuer = true,\n            ValidateAudience = true,\n            ValidateLifetime = true,\n            ValidateIssuerSigningKey = true,\n            ValidIssuer = config[\"Jwt:Issuer\"],\n            ValidAudience = config[\"Jwt:Audience\"],\n            IssuerSigningKey = new SymmetricSecurityKey(\n                Encoding.UTF8.GetBytes(config[\"Jwt:Key\"]!))\n        };\n    });\n",[491,3398,3399,3403,3408,3413,3418,3423,3428,3433,3438,3443,3448,3453,3458,3463],{"__ignoreMap":493},[790,3400,3401],{"class":792,"line":793},[790,3402,2684],{},[790,3404,3405],{"class":792,"line":799},[790,3406,3407],{},"builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n",[790,3409,3410],{"class":792,"line":805},[790,3411,3412],{},"    .AddJwtBearer(opt => {\n",[790,3414,3415],{"class":792,"line":811},[790,3416,3417],{},"        opt.TokenValidationParameters = new TokenValidationParameters {\n",[790,3419,3420],{"class":792,"line":848},[790,3421,3422],{},"            ValidateIssuer = true,\n",[790,3424,3425],{"class":792,"line":854},[790,3426,3427],{},"            ValidateAudience = true,\n",[790,3429,3430],{"class":792,"line":860},[790,3431,3432],{},"            ValidateLifetime = true,\n",[790,3434,3435],{"class":792,"line":866},[790,3436,3437],{},"            ValidateIssuerSigningKey = true,\n",[790,3439,3440],{"class":792,"line":1019},[790,3441,3442],{},"            ValidIssuer = config[\"Jwt:Issuer\"],\n",[790,3444,3445],{"class":792,"line":1025},[790,3446,3447],{},"            ValidAudience = config[\"Jwt:Audience\"],\n",[790,3449,3450],{"class":792,"line":1031},[790,3451,3452],{},"            IssuerSigningKey = new SymmetricSecurityKey(\n",[790,3454,3455],{"class":792,"line":1037},[790,3456,3457],{},"                Encoding.UTF8.GetBytes(config[\"Jwt:Key\"]!))\n",[790,3459,3460],{"class":792,"line":1043},[790,3461,3462],{},"        };\n",[790,3464,3465],{"class":792,"line":1049},[790,3466,2724],{},[451,3468,3470],{"id":3469},"_33-policy-based-authorization","33. 🔵 Policy-based Authorization?",[484,3472,3474],{"className":784,"code":3473,"language":786,"meta":493,"style":493},"builder.Services.AddAuthorization(opt => {\n    opt.AddPolicy(\"AdminOnly\", p => p.RequireRole(\"Admin\"));\n    opt.AddPolicy(\"MinAge18\", p => p.RequireClaim(\"age\", \"18\", \"19\", \"20\"));\n    opt.AddPolicy(\"PremiumUser\", p =>\n        p.Requirements.Add(new PremiumRequirement()));\n});\n\n\u002F\u002F Custom requirement\npublic class PremiumRequirement : IAuthorizationRequirement {}\npublic class PremiumHandler : AuthorizationHandler\u003CPremiumRequirement> {\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext ctx, PremiumRequirement req) {\n        if (ctx.User.HasClaim(\"subscription\", \"premium\"))\n            ctx.Succeed(req);\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Dùng\n[Authorize(Policy = \"PremiumUser\")]\npublic IActionResult PremiumContent() { }\n",[491,3475,3476,3481,3486,3491,3496,3501,3505,3509,3514,3519,3524,3529,3534,3539,3544,3549,3553,3557,3561,3566,3571],{"__ignoreMap":493},[790,3477,3478],{"class":792,"line":793},[790,3479,3480],{},"builder.Services.AddAuthorization(opt => {\n",[790,3482,3483],{"class":792,"line":799},[790,3484,3485],{},"    opt.AddPolicy(\"AdminOnly\", p => p.RequireRole(\"Admin\"));\n",[790,3487,3488],{"class":792,"line":805},[790,3489,3490],{},"    opt.AddPolicy(\"MinAge18\", p => p.RequireClaim(\"age\", \"18\", \"19\", \"20\"));\n",[790,3492,3493],{"class":792,"line":811},[790,3494,3495],{},"    opt.AddPolicy(\"PremiumUser\", p =>\n",[790,3497,3498],{"class":792,"line":848},[790,3499,3500],{},"        p.Requirements.Add(new PremiumRequirement()));\n",[790,3502,3503],{"class":792,"line":854},[790,3504,1440],{},[790,3506,3507],{"class":792,"line":860},[790,3508,1130],{"emptyLinePlaceholder":1129},[790,3510,3511],{"class":792,"line":866},[790,3512,3513],{},"\u002F\u002F Custom requirement\n",[790,3515,3516],{"class":792,"line":1019},[790,3517,3518],{},"public class PremiumRequirement : IAuthorizationRequirement {}\n",[790,3520,3521],{"class":792,"line":1025},[790,3522,3523],{},"public class PremiumHandler : AuthorizationHandler\u003CPremiumRequirement> {\n",[790,3525,3526],{"class":792,"line":1031},[790,3527,3528],{},"    protected override Task HandleRequirementAsync(\n",[790,3530,3531],{"class":792,"line":1037},[790,3532,3533],{},"        AuthorizationHandlerContext ctx, PremiumRequirement req) {\n",[790,3535,3536],{"class":792,"line":1043},[790,3537,3538],{},"        if (ctx.User.HasClaim(\"subscription\", \"premium\"))\n",[790,3540,3541],{"class":792,"line":1049},[790,3542,3543],{},"            ctx.Succeed(req);\n",[790,3545,3546],{"class":792,"line":1055},[790,3547,3548],{},"        return Task.CompletedTask;\n",[790,3550,3551],{"class":792,"line":2302},[790,3552,2410],{},[790,3554,3555],{"class":792,"line":2308},[790,3556,869],{},[790,3558,3559],{"class":792,"line":2314},[790,3560,1130],{"emptyLinePlaceholder":1129},[790,3562,3563],{"class":792,"line":2319},[790,3564,3565],{},"\u002F\u002F Dùng\n",[790,3567,3568],{"class":792,"line":2325},[790,3569,3570],{},"[Authorize(Policy = \"PremiumUser\")]\n",[790,3572,3573],{"class":792,"line":2331},[790,3574,3575],{},"public IActionResult PremiumContent() { }\n",[451,3577,3579],{"id":3578},"_34-cors-setup","34. 🔵 CORS setup?",[484,3581,3583],{"className":784,"code":3582,"language":786,"meta":493,"style":493},"builder.Services.AddCors(opt => opt.AddPolicy(\"default\", policy =>\n    policy.WithOrigins(\"https:\u002F\u002Fapp.com\", \"http:\u002F\u002Flocalhost:3000\")\n          .AllowAnyHeader()\n          .AllowAnyMethod()\n          .AllowCredentials()));\n\napp.UseCors(\"default\");  \u002F\u002F Phải TRƯỚC UseAuthentication\n",[491,3584,3585,3590,3595,3600,3605,3610,3614],{"__ignoreMap":493},[790,3586,3587],{"class":792,"line":793},[790,3588,3589],{},"builder.Services.AddCors(opt => opt.AddPolicy(\"default\", policy =>\n",[790,3591,3592],{"class":792,"line":799},[790,3593,3594],{},"    policy.WithOrigins(\"https:\u002F\u002Fapp.com\", \"http:\u002F\u002Flocalhost:3000\")\n",[790,3596,3597],{"class":792,"line":805},[790,3598,3599],{},"          .AllowAnyHeader()\n",[790,3601,3602],{"class":792,"line":811},[790,3603,3604],{},"          .AllowAnyMethod()\n",[790,3606,3607],{"class":792,"line":848},[790,3608,3609],{},"          .AllowCredentials()));\n",[790,3611,3612],{"class":792,"line":854},[790,3613,1130],{"emptyLinePlaceholder":1129},[790,3615,3616],{"class":792,"line":860},[790,3617,3618],{},"app.UseCors(\"default\");  \u002F\u002F Phải TRƯỚC UseAuthentication\n",[443,3620],{},[446,3622,3624],{"id":3623},"k-performance-và-monitoring","K. Performance và Monitoring",[451,3626,3628],{"id":3627},"_35-response-compression","35. 🔵 Response Compression?",[484,3630,3632],{"className":784,"code":3631,"language":786,"meta":493,"style":493},"builder.Services.AddResponseCompression(opt => {\n    opt.EnableForHttps = true;      \u002F\u002F cẩn thận BREACH attack với sensitive data\n    opt.Providers.Add\u003CBrotliCompressionProvider>();\n    opt.Providers.Add\u003CGzipCompressionProvider>();\n});\n\napp.UseResponseCompression();\n",[491,3633,3634,3639,3644,3649,3654,3658,3662],{"__ignoreMap":493},[790,3635,3636],{"class":792,"line":793},[790,3637,3638],{},"builder.Services.AddResponseCompression(opt => {\n",[790,3640,3641],{"class":792,"line":799},[790,3642,3643],{},"    opt.EnableForHttps = true;      \u002F\u002F cẩn thận BREACH attack với sensitive data\n",[790,3645,3646],{"class":792,"line":805},[790,3647,3648],{},"    opt.Providers.Add\u003CBrotliCompressionProvider>();\n",[790,3650,3651],{"class":792,"line":811},[790,3652,3653],{},"    opt.Providers.Add\u003CGzipCompressionProvider>();\n",[790,3655,3656],{"class":792,"line":848},[790,3657,1440],{},[790,3659,3660],{"class":792,"line":854},[790,3661,1130],{"emptyLinePlaceholder":1129},[790,3663,3664],{"class":792,"line":860},[790,3665,3666],{},"app.UseResponseCompression();\n",[451,3668,3670],{"id":3669},"_36-health-check","36. 🟡 Health Check?",[484,3672,3674],{"className":784,"code":3673,"language":786,"meta":493,"style":493},"builder.Services.AddHealthChecks()\n    .AddSqlServer(config.GetConnectionString(\"Default\")!)\n    .AddRedis(config.GetConnectionString(\"Redis\")!)\n    .AddCheck\u003CCustomHealthCheck>(\"custom\");\n\napp.MapHealthChecks(\"\u002Fhealth\", new HealthCheckOptions {\n    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n});\n\n\u002F\u002F Check endpoint liveness\u002Freadiness riêng\napp.MapHealthChecks(\"\u002Fhealth\u002Flive\", new HealthCheckOptions {\n    Predicate = _ => false  \u002F\u002F chỉ check app alive, không check DB\n});\n",[491,3675,3676,3681,3686,3691,3696,3700,3705,3710,3714,3718,3723,3728,3733],{"__ignoreMap":493},[790,3677,3678],{"class":792,"line":793},[790,3679,3680],{},"builder.Services.AddHealthChecks()\n",[790,3682,3683],{"class":792,"line":799},[790,3684,3685],{},"    .AddSqlServer(config.GetConnectionString(\"Default\")!)\n",[790,3687,3688],{"class":792,"line":805},[790,3689,3690],{},"    .AddRedis(config.GetConnectionString(\"Redis\")!)\n",[790,3692,3693],{"class":792,"line":811},[790,3694,3695],{},"    .AddCheck\u003CCustomHealthCheck>(\"custom\");\n",[790,3697,3698],{"class":792,"line":848},[790,3699,1130],{"emptyLinePlaceholder":1129},[790,3701,3702],{"class":792,"line":854},[790,3703,3704],{},"app.MapHealthChecks(\"\u002Fhealth\", new HealthCheckOptions {\n",[790,3706,3707],{"class":792,"line":860},[790,3708,3709],{},"    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse\n",[790,3711,3712],{"class":792,"line":866},[790,3713,1440],{},[790,3715,3716],{"class":792,"line":1019},[790,3717,1130],{"emptyLinePlaceholder":1129},[790,3719,3720],{"class":792,"line":1025},[790,3721,3722],{},"\u002F\u002F Check endpoint liveness\u002Freadiness riêng\n",[790,3724,3725],{"class":792,"line":1031},[790,3726,3727],{},"app.MapHealthChecks(\"\u002Fhealth\u002Flive\", new HealthCheckOptions {\n",[790,3729,3730],{"class":792,"line":1037},[790,3731,3732],{},"    Predicate = _ => false  \u002F\u002F chỉ check app alive, không check DB\n",[790,3734,3735],{"class":792,"line":1043},[790,3736,1440],{},[451,3738,3740],{"id":3739},"_37-problem-details-rfc-7807","37. 🟡 Problem Details (RFC 7807)?",[484,3742,3744],{"className":784,"code":3743,"language":786,"meta":493,"style":493},"\u002F\u002F Chuẩn hoá error response\nbuilder.Services.AddProblemDetails();\n\n\u002F\u002F Kết quả khi BadRequest:\n{\n    \"type\": \"https:\u002F\u002Fhttpstatuses.com\u002F400\",\n    \"title\": \"Bad Request\",\n    \"status\": 400,\n    \"detail\": \"Name is required\",\n    \"instance\": \"\u002Fapi\u002Fusers\",\n    \"traceId\": \"00-abc123...\"\n}\n",[491,3745,3746,3751,3756,3760,3765,3770,3775,3780,3785,3790,3795,3800],{"__ignoreMap":493},[790,3747,3748],{"class":792,"line":793},[790,3749,3750],{},"\u002F\u002F Chuẩn hoá error response\n",[790,3752,3753],{"class":792,"line":799},[790,3754,3755],{},"builder.Services.AddProblemDetails();\n",[790,3757,3758],{"class":792,"line":805},[790,3759,1130],{"emptyLinePlaceholder":1129},[790,3761,3762],{"class":792,"line":811},[790,3763,3764],{},"\u002F\u002F Kết quả khi BadRequest:\n",[790,3766,3767],{"class":792,"line":848},[790,3768,3769],{},"{\n",[790,3771,3772],{"class":792,"line":854},[790,3773,3774],{},"    \"type\": \"https:\u002F\u002Fhttpstatuses.com\u002F400\",\n",[790,3776,3777],{"class":792,"line":860},[790,3778,3779],{},"    \"title\": \"Bad Request\",\n",[790,3781,3782],{"class":792,"line":866},[790,3783,3784],{},"    \"status\": 400,\n",[790,3786,3787],{"class":792,"line":1019},[790,3788,3789],{},"    \"detail\": \"Name is required\",\n",[790,3791,3792],{"class":792,"line":1025},[790,3793,3794],{},"    \"instance\": \"\u002Fapi\u002Fusers\",\n",[790,3796,3797],{"class":792,"line":1031},[790,3798,3799],{},"    \"traceId\": \"00-abc123...\"\n",[790,3801,3802],{"class":792,"line":1037},[790,3803,869],{},[451,3805,3807],{"id":3806},"_38-minimal-api-openapi-net-9-không-cần-swashbuckle","38. 🟡 Minimal API OpenAPI (NET 9+ — không cần Swashbuckle)?",[484,3809,3811],{"className":784,"code":3810,"language":786,"meta":493,"style":493},"\u002F\u002F .NET 9+: Microsoft.AspNetCore.OpenApi\nbuilder.Services.AddOpenApi();\n\napp.MapOpenApi();    \u002F\u002F GET \u002Fopenapi\u002Fv1.json\n\n\u002F\u002F Với metadata\napp.MapGet(\"\u002Fusers\u002F{id}\", GetUser)\n    .WithName(\"GetUser\")\n    .WithSummary(\"Get user by ID\")\n    .WithDescription(\"Returns user or 404\")\n    .Produces\u003CUserDto>(200)\n    .Produces(404)\n    .WithTags(\"Users\");\n",[491,3812,3813,3818,3823,3827,3832,3836,3841,3846,3851,3856,3861,3866,3871],{"__ignoreMap":493},[790,3814,3815],{"class":792,"line":793},[790,3816,3817],{},"\u002F\u002F .NET 9+: Microsoft.AspNetCore.OpenApi\n",[790,3819,3820],{"class":792,"line":799},[790,3821,3822],{},"builder.Services.AddOpenApi();\n",[790,3824,3825],{"class":792,"line":805},[790,3826,1130],{"emptyLinePlaceholder":1129},[790,3828,3829],{"class":792,"line":811},[790,3830,3831],{},"app.MapOpenApi();    \u002F\u002F GET \u002Fopenapi\u002Fv1.json\n",[790,3833,3834],{"class":792,"line":848},[790,3835,1130],{"emptyLinePlaceholder":1129},[790,3837,3838],{"class":792,"line":854},[790,3839,3840],{},"\u002F\u002F Với metadata\n",[790,3842,3843],{"class":792,"line":860},[790,3844,3845],{},"app.MapGet(\"\u002Fusers\u002F{id}\", GetUser)\n",[790,3847,3848],{"class":792,"line":866},[790,3849,3850],{},"    .WithName(\"GetUser\")\n",[790,3852,3853],{"class":792,"line":1019},[790,3854,3855],{},"    .WithSummary(\"Get user by ID\")\n",[790,3857,3858],{"class":792,"line":1025},[790,3859,3860],{},"    .WithDescription(\"Returns user or 404\")\n",[790,3862,3863],{"class":792,"line":1031},[790,3864,3865],{},"    .Produces\u003CUserDto>(200)\n",[790,3867,3868],{"class":792,"line":1037},[790,3869,3870],{},"    .Produces(404)\n",[790,3872,3873],{"class":792,"line":1043},[790,3874,3875],{},"    .WithTags(\"Users\");\n",[443,3877],{},[446,3879,3881],{"id":3880},"l-câu-hỏi-hay-scenario","L. Câu hỏi hay + Scenario",[451,3883,3885],{"id":3884},"_39-sự-khác-nhau-giữa-mvc-5-và-core-trong-xử-lý-exception","39. ⭐ Sự khác nhau giữa MVC 5 và Core trong xử lý exception?",[432,3887,3888],{},[435,3889,3890],{},"MVC 5:",[484,3892,3894],{"className":784,"code":3893,"language":786,"meta":493,"style":493},"\u002F\u002F Global.asax \u002F FilterConfig.cs\nfilters.Add(new HandleErrorAttribute());\n\u002F\u002F Dùng customErrors trong web.config\n",[491,3895,3896,3901,3906],{"__ignoreMap":493},[790,3897,3898],{"class":792,"line":793},[790,3899,3900],{},"\u002F\u002F Global.asax \u002F FilterConfig.cs\n",[790,3902,3903],{"class":792,"line":799},[790,3904,3905],{},"filters.Add(new HandleErrorAttribute());\n",[790,3907,3908],{"class":792,"line":805},[790,3909,3910],{},"\u002F\u002F Dùng customErrors trong web.config\n",[432,3912,3913],{},[435,3914,3915],{},"ASP.NET Core:",[484,3917,3919],{"className":784,"code":3918,"language":786,"meta":493,"style":493},"\u002F\u002F Middleware — app-level\napp.UseExceptionHandler(\"\u002Ferror\");\napp.UseDeveloperExceptionPage();   \u002F\u002F Dev only\n\n\u002F\u002F Problem Details middleware (NET 8+)\napp.UseExceptionHandler();         \u002F\u002F Auto ProblemDetails\napp.UseStatusCodePages();\n\n\u002F\u002F Custom exception middleware\napp.UseMiddleware\u003CGlobalExceptionMiddleware>();\n\n\u002F\u002F Filter level\nservices.AddControllers(o => o.Filters.Add\u003CGlobalExceptionFilter>());\n",[491,3920,3921,3926,3931,3936,3940,3945,3950,3955,3959,3964,3969,3973,3978],{"__ignoreMap":493},[790,3922,3923],{"class":792,"line":793},[790,3924,3925],{},"\u002F\u002F Middleware — app-level\n",[790,3927,3928],{"class":792,"line":799},[790,3929,3930],{},"app.UseExceptionHandler(\"\u002Ferror\");\n",[790,3932,3933],{"class":792,"line":805},[790,3934,3935],{},"app.UseDeveloperExceptionPage();   \u002F\u002F Dev only\n",[790,3937,3938],{"class":792,"line":811},[790,3939,1130],{"emptyLinePlaceholder":1129},[790,3941,3942],{"class":792,"line":848},[790,3943,3944],{},"\u002F\u002F Problem Details middleware (NET 8+)\n",[790,3946,3947],{"class":792,"line":854},[790,3948,3949],{},"app.UseExceptionHandler();         \u002F\u002F Auto ProblemDetails\n",[790,3951,3952],{"class":792,"line":860},[790,3953,3954],{},"app.UseStatusCodePages();\n",[790,3956,3957],{"class":792,"line":866},[790,3958,1130],{"emptyLinePlaceholder":1129},[790,3960,3961],{"class":792,"line":1019},[790,3962,3963],{},"\u002F\u002F Custom exception middleware\n",[790,3965,3966],{"class":792,"line":1025},[790,3967,3968],{},"app.UseMiddleware\u003CGlobalExceptionMiddleware>();\n",[790,3970,3971],{"class":792,"line":1031},[790,3972,1130],{"emptyLinePlaceholder":1129},[790,3974,3975],{"class":792,"line":1037},[790,3976,3977],{},"\u002F\u002F Filter level\n",[790,3979,3980],{"class":792,"line":1043},[790,3981,3982],{},"services.AddControllers(o => o.Filters.Add\u003CGlobalExceptionFilter>());\n",[451,3984,3986,3987,1066,3989,3991],{"id":3985},"_40-khi-nào-dùng-frombody-vs-fromform","40. ⭐ Khi nào dùng ",[491,3988,1191],{},[491,3990,1244],{},"?",[462,3993,3994,4006],{},[465,3995,3996,3998,3999,4002,4003,1177],{},[491,3997,1191],{},": JSON\u002FXML payload. Request có ",[491,4000,4001],{},"Content-Type: application\u002Fjson",". ",[435,4004,4005],{},"Bắt buộc với complex type trong Web API",[465,4007,4008,4010,4011,4014,4015,4018],{},[491,4009,1244],{},": HTML form (",[491,4012,4013],{},"Content-Type: application\u002Fx-www-form-urlencoded"," hoặc ",[491,4016,4017],{},"multipart\u002Fform-data","). Dùng khi nhận file upload.",[484,4020,4022],{"className":784,"code":4021,"language":786,"meta":493,"style":493},"\u002F\u002F Web API — nhận JSON\n[HttpPost]\npublic IActionResult Create([FromBody] CreateUserDto dto) { }\n\n\u002F\u002F MVC form submit + file\n[HttpPost]\npublic IActionResult Upload([FromForm] IFormFile file, [FromForm] string desc) { }\n",[491,4023,4024,4029,4034,4039,4043,4048,4052],{"__ignoreMap":493},[790,4025,4026],{"class":792,"line":793},[790,4027,4028],{},"\u002F\u002F Web API — nhận JSON\n",[790,4030,4031],{"class":792,"line":799},[790,4032,4033],{},"[HttpPost]\n",[790,4035,4036],{"class":792,"line":805},[790,4037,4038],{},"public IActionResult Create([FromBody] CreateUserDto dto) { }\n",[790,4040,4041],{"class":792,"line":811},[790,4042,1130],{"emptyLinePlaceholder":1129},[790,4044,4045],{"class":792,"line":848},[790,4046,4047],{},"\u002F\u002F MVC form submit + file\n",[790,4049,4050],{"class":792,"line":854},[790,4051,4033],{},[790,4053,4054],{"class":792,"line":860},[790,4055,4056],{},"public IActionResult Upload([FromForm] IFormFile file, [FromForm] string desc) { }\n",[451,4058,4060],{"id":4059},"_41-async-action-method-vs-sync-khi-nào-dùng","41. ⭐🔵 Async action method vs sync — khi nào dùng?",[484,4062,4064],{"className":784,"code":4063,"language":786,"meta":493,"style":493},"\u002F\u002F ❌ Sync — block thread khi có I\u002FO\n[HttpGet(\"{id}\")]\npublic IActionResult Get(int id) {\n    var u = _db.Users.Find(id);    \u002F\u002F block!\n    return Ok(u);\n}\n\n\u002F\u002F ✅ Async — giải phóng thread khi chờ DB\u002FHTTP\n[HttpGet(\"{id}\")]\npublic async Task\u003CActionResult\u003CUserDto>> Get(int id) {\n    var u = await _db.Users.FindAsync(id);\n    return u is null ? NotFound() : Ok(u);\n}\n",[491,4065,4066,4071,4076,4081,4086,4090,4094,4098,4103,4107,4112,4117,4122],{"__ignoreMap":493},[790,4067,4068],{"class":792,"line":793},[790,4069,4070],{},"\u002F\u002F ❌ Sync — block thread khi có I\u002FO\n",[790,4072,4073],{"class":792,"line":799},[790,4074,4075],{},"[HttpGet(\"{id}\")]\n",[790,4077,4078],{"class":792,"line":805},[790,4079,4080],{},"public IActionResult Get(int id) {\n",[790,4082,4083],{"class":792,"line":811},[790,4084,4085],{},"    var u = _db.Users.Find(id);    \u002F\u002F block!\n",[790,4087,4088],{"class":792,"line":848},[790,4089,1120],{},[790,4091,4092],{"class":792,"line":854},[790,4093,869],{},[790,4095,4096],{"class":792,"line":860},[790,4097,1130],{"emptyLinePlaceholder":1129},[790,4099,4100],{"class":792,"line":866},[790,4101,4102],{},"\u002F\u002F ✅ Async — giải phóng thread khi chờ DB\u002FHTTP\n",[790,4104,4105],{"class":792,"line":1019},[790,4106,4075],{},[790,4108,4109],{"class":792,"line":1025},[790,4110,4111],{},"public async Task\u003CActionResult\u003CUserDto>> Get(int id) {\n",[790,4113,4114],{"class":792,"line":1031},[790,4115,4116],{},"    var u = await _db.Users.FindAsync(id);\n",[790,4118,4119],{"class":792,"line":1037},[790,4120,4121],{},"    return u is null ? NotFound() : Ok(u);\n",[790,4123,4124],{"class":792,"line":1043},[790,4125,869],{},[432,4127,4128,4129,4132],{},"ASP.NET Core ",[435,4130,4131],{},"thread pool"," có giới hạn. Với 100 concurrent request, sync + chờ DB 200ms = 100 threads bị block → depleted. Async → chỉ khi DB trả về mới chiếm thread.",[451,4134,4136],{"id":4135},"_42-làm-sao-trả-về-file-từ-api","42. 🔵 Làm sao trả về file từ API?",[484,4138,4140],{"className":784,"code":4139,"language":786,"meta":493,"style":493},"\u002F\u002F PDF\u002FExcel file\n[HttpGet(\"{id}\u002Finvoice\")]\npublic async Task\u003CIActionResult> GetInvoice(int id) {\n    var bytes = await _service.GenerateInvoicePdfAsync(id);\n    return File(bytes, \"application\u002Fpdf\", $\"invoice-{id}.pdf\");\n}\n\n\u002F\u002F Stream (tốt hơn cho file lớn)\n[HttpGet(\"{id}\u002Freport\")]\npublic async Task\u003CIActionResult> GetReport(int id) {\n    var stream = await _service.GetReportStreamAsync(id);\n    return File(stream, \"application\u002Foctet-stream\", \"report.xlsx\");\n}\n\n\u002F\u002F Redirect to blob storage\n[HttpGet(\"{id}\u002Fimage\")]\npublic IActionResult GetImage(int id) {\n    var url = _storage.GetSignedUrl($\"images\u002F{id}.jpg\");\n    return Redirect(url);\n}\n",[491,4141,4142,4147,4152,4157,4162,4167,4171,4175,4180,4185,4190,4195,4200,4204,4208,4213,4218,4223,4228,4233],{"__ignoreMap":493},[790,4143,4144],{"class":792,"line":793},[790,4145,4146],{},"\u002F\u002F PDF\u002FExcel file\n",[790,4148,4149],{"class":792,"line":799},[790,4150,4151],{},"[HttpGet(\"{id}\u002Finvoice\")]\n",[790,4153,4154],{"class":792,"line":805},[790,4155,4156],{},"public async Task\u003CIActionResult> GetInvoice(int id) {\n",[790,4158,4159],{"class":792,"line":811},[790,4160,4161],{},"    var bytes = await _service.GenerateInvoicePdfAsync(id);\n",[790,4163,4164],{"class":792,"line":848},[790,4165,4166],{},"    return File(bytes, \"application\u002Fpdf\", $\"invoice-{id}.pdf\");\n",[790,4168,4169],{"class":792,"line":854},[790,4170,869],{},[790,4172,4173],{"class":792,"line":860},[790,4174,1130],{"emptyLinePlaceholder":1129},[790,4176,4177],{"class":792,"line":866},[790,4178,4179],{},"\u002F\u002F Stream (tốt hơn cho file lớn)\n",[790,4181,4182],{"class":792,"line":1019},[790,4183,4184],{},"[HttpGet(\"{id}\u002Freport\")]\n",[790,4186,4187],{"class":792,"line":1025},[790,4188,4189],{},"public async Task\u003CIActionResult> GetReport(int id) {\n",[790,4191,4192],{"class":792,"line":1031},[790,4193,4194],{},"    var stream = await _service.GetReportStreamAsync(id);\n",[790,4196,4197],{"class":792,"line":1037},[790,4198,4199],{},"    return File(stream, \"application\u002Foctet-stream\", \"report.xlsx\");\n",[790,4201,4202],{"class":792,"line":1043},[790,4203,869],{},[790,4205,4206],{"class":792,"line":1049},[790,4207,1130],{"emptyLinePlaceholder":1129},[790,4209,4210],{"class":792,"line":1055},[790,4211,4212],{},"\u002F\u002F Redirect to blob storage\n",[790,4214,4215],{"class":792,"line":2302},[790,4216,4217],{},"[HttpGet(\"{id}\u002Fimage\")]\n",[790,4219,4220],{"class":792,"line":2308},[790,4221,4222],{},"public IActionResult GetImage(int id) {\n",[790,4224,4225],{"class":792,"line":2314},[790,4226,4227],{},"    var url = _storage.GetSignedUrl($\"images\u002F{id}.jpg\");\n",[790,4229,4230],{"class":792,"line":2319},[790,4231,4232],{},"    return Redirect(url);\n",[790,4234,4235],{"class":792,"line":2325},[790,4236,869],{},[451,4238,4240],{"id":4239},"_43-model-binding-với-custom-complex-type","43. 🟡 Model Binding với custom complex type?",[484,4242,4244],{"className":784,"code":4243,"language":786,"meta":493,"style":493},"\u002F\u002F Custom model binder\npublic class DateRangeModelBinder : IModelBinder {\n    public Task BindModelAsync(ModelBindingContext ctx) {\n        var from = ctx.ValueProvider.GetValue(\"from\").FirstValue;\n        var to = ctx.ValueProvider.GetValue(\"to\").FirstValue;\n        if (DateTime.TryParse(from, out var f) && DateTime.TryParse(to, out var t)) {\n            ctx.Result = ModelBindingResult.Success(new DateRange(f, t));\n        }\n        return Task.CompletedTask;\n    }\n}\n\n\u002F\u002F Register\n[ModelBinder(typeof(DateRangeModelBinder))]\npublic class DateRange { ... }\n\n\u002F\u002F Action\n[HttpGet(\"report\")]\npublic IActionResult Report(DateRange range) { }  \u002F\u002F ?from=2026-01-01&to=2026-12-31\n",[491,4245,4246,4251,4256,4261,4266,4271,4276,4281,4286,4290,4294,4298,4302,4307,4312,4317,4321,4325,4330],{"__ignoreMap":493},[790,4247,4248],{"class":792,"line":793},[790,4249,4250],{},"\u002F\u002F Custom model binder\n",[790,4252,4253],{"class":792,"line":799},[790,4254,4255],{},"public class DateRangeModelBinder : IModelBinder {\n",[790,4257,4258],{"class":792,"line":805},[790,4259,4260],{},"    public Task BindModelAsync(ModelBindingContext ctx) {\n",[790,4262,4263],{"class":792,"line":811},[790,4264,4265],{},"        var from = ctx.ValueProvider.GetValue(\"from\").FirstValue;\n",[790,4267,4268],{"class":792,"line":848},[790,4269,4270],{},"        var to = ctx.ValueProvider.GetValue(\"to\").FirstValue;\n",[790,4272,4273],{"class":792,"line":854},[790,4274,4275],{},"        if (DateTime.TryParse(from, out var f) && DateTime.TryParse(to, out var t)) {\n",[790,4277,4278],{"class":792,"line":860},[790,4279,4280],{},"            ctx.Result = ModelBindingResult.Success(new DateRange(f, t));\n",[790,4282,4283],{"class":792,"line":866},[790,4284,4285],{},"        }\n",[790,4287,4288],{"class":792,"line":1019},[790,4289,3548],{},[790,4291,4292],{"class":792,"line":1025},[790,4293,2410],{},[790,4295,4296],{"class":792,"line":1031},[790,4297,869],{},[790,4299,4300],{"class":792,"line":1037},[790,4301,1130],{"emptyLinePlaceholder":1129},[790,4303,4304],{"class":792,"line":1043},[790,4305,4306],{},"\u002F\u002F Register\n",[790,4308,4309],{"class":792,"line":1049},[790,4310,4311],{},"[ModelBinder(typeof(DateRangeModelBinder))]\n",[790,4313,4314],{"class":792,"line":1055},[790,4315,4316],{},"public class DateRange { ... }\n",[790,4318,4319],{"class":792,"line":2302},[790,4320,1130],{"emptyLinePlaceholder":1129},[790,4322,4323],{"class":792,"line":2308},[790,4324,2887],{},[790,4326,4327],{"class":792,"line":2314},[790,4328,4329],{},"[HttpGet(\"report\")]\n",[790,4331,4332],{"class":792,"line":2319},[790,4333,4334],{},"public IActionResult Report(DateRange range) { }  \u002F\u002F ?from=2026-01-01&to=2026-12-31\n",[451,4336,4338],{"id":4337},"_44-paging-sorting-filtering-pattern-chuẩn","44. ⭐🔵 Paging, Sorting, Filtering pattern chuẩn?",[484,4340,4342],{"className":784,"code":4341,"language":786,"meta":493,"style":493},"public class QueryParams {\n    public int Page { get; set; } = 1;\n    public int Size { get; set; } = 20;\n    public string? SortBy { get; set; }\n    public bool SortDesc { get; set; }\n    public string? Search { get; set; }\n}\n\n[HttpGet]\npublic async Task\u003CActionResult\u003CPagedResult\u003CUserDto>>> GetAll([FromQuery] QueryParams q) {\n    var query = _db.Users.AsNoTracking();\n    if (!string.IsNullOrEmpty(q.Search))\n        query = query.Where(u => u.Name.Contains(q.Search));\n    var total = await query.CountAsync();\n    var items = await query\n        .OrderBy(u => u.Name)\n        .Skip((q.Page - 1) * q.Size)\n        .Take(q.Size)\n        .Select(u => new UserDto { ... })\n        .ToListAsync();\n    return Ok(new PagedResult\u003CUserDto>(items, total, q.Page, q.Size));\n}\n",[491,4343,4344,4349,4354,4359,4364,4369,4374,4378,4382,4386,4391,4396,4401,4406,4411,4416,4421,4426,4431,4436,4441,4446],{"__ignoreMap":493},[790,4345,4346],{"class":792,"line":793},[790,4347,4348],{},"public class QueryParams {\n",[790,4350,4351],{"class":792,"line":799},[790,4352,4353],{},"    public int Page { get; set; } = 1;\n",[790,4355,4356],{"class":792,"line":805},[790,4357,4358],{},"    public int Size { get; set; } = 20;\n",[790,4360,4361],{"class":792,"line":811},[790,4362,4363],{},"    public string? SortBy { get; set; }\n",[790,4365,4366],{"class":792,"line":848},[790,4367,4368],{},"    public bool SortDesc { get; set; }\n",[790,4370,4371],{"class":792,"line":854},[790,4372,4373],{},"    public string? Search { get; set; }\n",[790,4375,4376],{"class":792,"line":860},[790,4377,869],{},[790,4379,4380],{"class":792,"line":866},[790,4381,1130],{"emptyLinePlaceholder":1129},[790,4383,4384],{"class":792,"line":1019},[790,4385,1473],{},[790,4387,4388],{"class":792,"line":1025},[790,4389,4390],{},"public async Task\u003CActionResult\u003CPagedResult\u003CUserDto>>> GetAll([FromQuery] QueryParams q) {\n",[790,4392,4393],{"class":792,"line":1031},[790,4394,4395],{},"    var query = _db.Users.AsNoTracking();\n",[790,4397,4398],{"class":792,"line":1037},[790,4399,4400],{},"    if (!string.IsNullOrEmpty(q.Search))\n",[790,4402,4403],{"class":792,"line":1043},[790,4404,4405],{},"        query = query.Where(u => u.Name.Contains(q.Search));\n",[790,4407,4408],{"class":792,"line":1049},[790,4409,4410],{},"    var total = await query.CountAsync();\n",[790,4412,4413],{"class":792,"line":1055},[790,4414,4415],{},"    var items = await query\n",[790,4417,4418],{"class":792,"line":2302},[790,4419,4420],{},"        .OrderBy(u => u.Name)\n",[790,4422,4423],{"class":792,"line":2308},[790,4424,4425],{},"        .Skip((q.Page - 1) * q.Size)\n",[790,4427,4428],{"class":792,"line":2314},[790,4429,4430],{},"        .Take(q.Size)\n",[790,4432,4433],{"class":792,"line":2319},[790,4434,4435],{},"        .Select(u => new UserDto { ... })\n",[790,4437,4438],{"class":792,"line":2325},[790,4439,4440],{},"        .ToListAsync();\n",[790,4442,4443],{"class":792,"line":2331},[790,4444,4445],{},"    return Ok(new PagedResult\u003CUserDto>(items, total, q.Page, q.Size));\n",[790,4447,4448],{"class":792,"line":2644},[790,4449,869],{},[451,4451,4453],{"id":4452},"_45-global-exception-handling-pattern-chuẩn","45. ⭐🟡 Global exception handling pattern chuẩn?",[484,4455,4457],{"className":784,"code":4456,"language":786,"meta":493,"style":493},"\u002F\u002F Middleware approach (recommend)\npublic class GlobalExceptionMiddleware {\n    private readonly RequestDelegate _next;\n    private readonly ILogger\u003CGlobalExceptionMiddleware> _logger;\n    \n    public GlobalExceptionMiddleware(RequestDelegate next, ILogger\u003CGlobalExceptionMiddleware> logger) {\n        _next = next; _logger = logger;\n    }\n    \n    public async Task InvokeAsync(HttpContext ctx) {\n        try {\n            await _next(ctx);\n        }\n        catch (NotFoundException ex) {\n            ctx.Response.StatusCode = 404;\n            await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });\n        }\n        catch (ValidationException ex) {\n            ctx.Response.StatusCode = 422;\n            await ctx.Response.WriteAsJsonAsync(new { errors = ex.Errors });\n        }\n        catch (Exception ex) {\n            _logger.LogError(ex, \"Unhandled exception\");\n            ctx.Response.StatusCode = 500;\n            await ctx.Response.WriteAsJsonAsync(new { error = \"Internal server error\" });\n        }\n    }\n}\n",[491,4458,4459,4464,4469,4474,4479,4483,4488,4493,4497,4501,4506,4511,4516,4520,4525,4530,4535,4539,4544,4549,4554,4558,4563,4568,4573,4578,4582,4586],{"__ignoreMap":493},[790,4460,4461],{"class":792,"line":793},[790,4462,4463],{},"\u002F\u002F Middleware approach (recommend)\n",[790,4465,4466],{"class":792,"line":799},[790,4467,4468],{},"public class GlobalExceptionMiddleware {\n",[790,4470,4471],{"class":792,"line":805},[790,4472,4473],{},"    private readonly RequestDelegate _next;\n",[790,4475,4476],{"class":792,"line":811},[790,4477,4478],{},"    private readonly ILogger\u003CGlobalExceptionMiddleware> _logger;\n",[790,4480,4481],{"class":792,"line":848},[790,4482,851],{},[790,4484,4485],{"class":792,"line":854},[790,4486,4487],{},"    public GlobalExceptionMiddleware(RequestDelegate next, ILogger\u003CGlobalExceptionMiddleware> logger) {\n",[790,4489,4490],{"class":792,"line":860},[790,4491,4492],{},"        _next = next; _logger = logger;\n",[790,4494,4495],{"class":792,"line":866},[790,4496,2410],{},[790,4498,4499],{"class":792,"line":1019},[790,4500,851],{},[790,4502,4503],{"class":792,"line":1025},[790,4504,4505],{},"    public async Task InvokeAsync(HttpContext ctx) {\n",[790,4507,4508],{"class":792,"line":1031},[790,4509,4510],{},"        try {\n",[790,4512,4513],{"class":792,"line":1037},[790,4514,4515],{},"            await _next(ctx);\n",[790,4517,4518],{"class":792,"line":1043},[790,4519,4285],{},[790,4521,4522],{"class":792,"line":1049},[790,4523,4524],{},"        catch (NotFoundException ex) {\n",[790,4526,4527],{"class":792,"line":1055},[790,4528,4529],{},"            ctx.Response.StatusCode = 404;\n",[790,4531,4532],{"class":792,"line":2302},[790,4533,4534],{},"            await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });\n",[790,4536,4537],{"class":792,"line":2308},[790,4538,4285],{},[790,4540,4541],{"class":792,"line":2314},[790,4542,4543],{},"        catch (ValidationException ex) {\n",[790,4545,4546],{"class":792,"line":2319},[790,4547,4548],{},"            ctx.Response.StatusCode = 422;\n",[790,4550,4551],{"class":792,"line":2325},[790,4552,4553],{},"            await ctx.Response.WriteAsJsonAsync(new { errors = ex.Errors });\n",[790,4555,4556],{"class":792,"line":2331},[790,4557,4285],{},[790,4559,4560],{"class":792,"line":2644},[790,4561,4562],{},"        catch (Exception ex) {\n",[790,4564,4565],{"class":792,"line":2650},[790,4566,4567],{},"            _logger.LogError(ex, \"Unhandled exception\");\n",[790,4569,4570],{"class":792,"line":2656},[790,4571,4572],{},"            ctx.Response.StatusCode = 500;\n",[790,4574,4575],{"class":792,"line":2662},[790,4576,4577],{},"            await ctx.Response.WriteAsJsonAsync(new { error = \"Internal server error\" });\n",[790,4579,4580],{"class":792,"line":2668},[790,4581,4285],{},[790,4583,4584],{"class":792,"line":2806},[790,4585,2410],{},[790,4587,4588],{"class":792,"line":2811},[790,4589,869],{},[443,4591],{},[446,4593,4595],{"id":4594},"m-so-sánh-và-khác-biệt-version","M. So sánh và khác biệt version",[451,4597,4599],{"id":4598},"_46-net-5-6-7-8-9-thay-đổi-đáng-chú-ý","46. ⭐ .NET 5 → 6 → 7 → 8 → 9 — thay đổi đáng chú ý?",[499,4601,4602,4612],{},[502,4603,4604],{},[505,4605,4606,4609],{},[508,4607,4608],{},"Version",[508,4610,4611],{},"Điểm nổi bật",[517,4613,4614,4624,4641,4651,4665,4682],{},[505,4615,4616,4621],{},[522,4617,4618],{},[435,4619,4620],{},".NET 5 (2020)",[522,4622,4623],{},"Hợp nhất .NET Framework và Core thành .NET",[505,4625,4626,4631],{},[522,4627,4628],{},[435,4629,4630],{},".NET 6 LTS (2021)",[522,4632,4633,4634,753,4637,4640],{},"Minimal API, Hot Reload, ",[491,4635,4636],{},"DateOnly",[491,4638,4639],{},"TimeOnly",", file-scoped namespace",[505,4642,4643,4648],{},[522,4644,4645],{},[435,4646,4647],{},".NET 7 (2022)",[522,4649,4650],{},"Rate Limiting built-in, Output Caching, Route Groups, Problem Details",[505,4652,4653,4658],{},[522,4654,4655],{},[435,4656,4657],{},".NET 8 LTS (2023)",[522,4659,4660,4661,4664],{},"Blazor fullstack, Keyed DI, ",[491,4662,4663],{},"TimeProvider",", frozen collections",[505,4666,4667,4672],{},[522,4668,4669],{},[435,4670,4671],{},".NET 9 (2024)",[522,4673,4674,4675,2444,4678,4681],{},"HybridCache, OpenAPI built-in (no Swashbuckle), LINQ ",[491,4676,4677],{},"CountBy\u002FAggregateBy",[491,4679,4680],{},"Span\u003CT>"," LINQ",[505,4683,4684,4689],{},[522,4685,4686],{},[435,4687,4688],{},".NET 10 LTS (2025)",[522,4690,4691],{},"Better trimming, C# 14, EF Core 10, OpenAPI 3.1",[451,4693,4695],{"id":4694},"_47-startupcs-cũ-vs-programcs-mới-net-6","47. 🔵 Startup.cs (cũ) vs Program.cs (mới .NET 6+)?",[484,4697,4699],{"className":784,"code":4698,"language":786,"meta":493,"style":493},"\u002F\u002F Cũ — .NET 5 trở về\n\u002F\u002F Startup.cs\npublic class Startup {\n    public void ConfigureServices(IServiceCollection services) {\n        services.AddControllers();\n    }\n    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {\n        app.UseRouting();\n        app.UseEndpoints(endpoints => endpoints.MapControllers());\n    }\n}\n\n\u002F\u002F Mới — .NET 6+, minimal hosting\n\u002F\u002F Program.cs\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddControllers();\nvar app = builder.Build();\napp.MapControllers();\napp.Run();\n",[491,4700,4701,4706,4711,4716,4721,4726,4730,4735,4740,4745,4749,4753,4757,4762,4766,4771,4776,4781,4786],{"__ignoreMap":493},[790,4702,4703],{"class":792,"line":793},[790,4704,4705],{},"\u002F\u002F Cũ — .NET 5 trở về\n",[790,4707,4708],{"class":792,"line":799},[790,4709,4710],{},"\u002F\u002F Startup.cs\n",[790,4712,4713],{"class":792,"line":805},[790,4714,4715],{},"public class Startup {\n",[790,4717,4718],{"class":792,"line":811},[790,4719,4720],{},"    public void ConfigureServices(IServiceCollection services) {\n",[790,4722,4723],{"class":792,"line":848},[790,4724,4725],{},"        services.AddControllers();\n",[790,4727,4728],{"class":792,"line":854},[790,4729,2410],{},[790,4731,4732],{"class":792,"line":860},[790,4733,4734],{},"    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {\n",[790,4736,4737],{"class":792,"line":866},[790,4738,4739],{},"        app.UseRouting();\n",[790,4741,4742],{"class":792,"line":1019},[790,4743,4744],{},"        app.UseEndpoints(endpoints => endpoints.MapControllers());\n",[790,4746,4747],{"class":792,"line":1025},[790,4748,2410],{},[790,4750,4751],{"class":792,"line":1031},[790,4752,869],{},[790,4754,4755],{"class":792,"line":1037},[790,4756,1130],{"emptyLinePlaceholder":1129},[790,4758,4759],{"class":792,"line":1043},[790,4760,4761],{},"\u002F\u002F Mới — .NET 6+, minimal hosting\n",[790,4763,4764],{"class":792,"line":1049},[790,4765,2684],{},[790,4767,4768],{"class":792,"line":1055},[790,4769,4770],{},"var builder = WebApplication.CreateBuilder(args);\n",[790,4772,4773],{"class":792,"line":2302},[790,4774,4775],{},"builder.Services.AddControllers();\n",[790,4777,4778],{"class":792,"line":2308},[790,4779,4780],{},"var app = builder.Build();\n",[790,4782,4783],{"class":792,"line":2314},[790,4784,4785],{},"app.MapControllers();\n",[790,4787,4788],{"class":792,"line":2319},[790,4789,4790],{},"app.Run();\n",[432,4792,4793,4794,4002,4797,4800,4801,686,4804,686,4807,1177],{},"Không còn ",[491,4795,4796],{},"Startup.cs",[491,4798,4799],{},"WebApplicationBuilder"," kết hợp cả ",[491,4802,4803],{},"IHostBuilder",[491,4805,4806],{},"IWebHostBuilder",[491,4808,4809],{},"IServiceCollection",[451,4811,4813],{"id":4812},"_48-keyed-di-services-net-8","48. 🔵 Keyed DI Services (.NET 8)?",[484,4815,4817],{"className":784,"code":4816,"language":786,"meta":493,"style":493},"\u002F\u002F Đăng ký nhiều implementation cùng interface, phân biệt bằng key\nbuilder.Services.AddKeyedSingleton\u003CIPaymentGateway, StripeGateway>(\"stripe\");\nbuilder.Services.AddKeyedSingleton\u003CIPaymentGateway, VnPayGateway>(\"vnpay\");\n\n\u002F\u002F Inject\npublic class CheckoutService {\n    public CheckoutService([FromKeyedServices(\"stripe\")] IPaymentGateway gateway) { }\n}\n\n\u002F\u002F Hoặc resolve runtime\nvar gateway = app.Services.GetRequiredKeyedService\u003CIPaymentGateway>(\"vnpay\");\n",[491,4818,4819,4824,4829,4834,4838,4842,4847,4852,4856,4860,4865],{"__ignoreMap":493},[790,4820,4821],{"class":792,"line":793},[790,4822,4823],{},"\u002F\u002F Đăng ký nhiều implementation cùng interface, phân biệt bằng key\n",[790,4825,4826],{"class":792,"line":799},[790,4827,4828],{},"builder.Services.AddKeyedSingleton\u003CIPaymentGateway, StripeGateway>(\"stripe\");\n",[790,4830,4831],{"class":792,"line":805},[790,4832,4833],{},"builder.Services.AddKeyedSingleton\u003CIPaymentGateway, VnPayGateway>(\"vnpay\");\n",[790,4835,4836],{"class":792,"line":811},[790,4837,1130],{"emptyLinePlaceholder":1129},[790,4839,4840],{"class":792,"line":848},[790,4841,2966],{},[790,4843,4844],{"class":792,"line":854},[790,4845,4846],{},"public class CheckoutService {\n",[790,4848,4849],{"class":792,"line":860},[790,4850,4851],{},"    public CheckoutService([FromKeyedServices(\"stripe\")] IPaymentGateway gateway) { }\n",[790,4853,4854],{"class":792,"line":866},[790,4855,869],{},[790,4857,4858],{"class":792,"line":1019},[790,4859,1130],{"emptyLinePlaceholder":1129},[790,4861,4862],{"class":792,"line":1025},[790,4863,4864],{},"\u002F\u002F Hoặc resolve runtime\n",[790,4866,4867],{"class":792,"line":1031},[790,4868,4869],{},"var gateway = app.Services.GetRequiredKeyedService\u003CIPaymentGateway>(\"vnpay\");\n",[451,4871,4873],{"id":4872},"_49-iexceptionhandler-net-8-thay-vì-middleware-custom","49. 🔵 IExceptionHandler (NET 8 thay vì middleware custom)?",[484,4875,4877],{"className":784,"code":4876,"language":786,"meta":493,"style":493},"public class GlobalExceptionHandler : IExceptionHandler {\n    public async ValueTask\u003Cbool> TryHandleAsync(\n        HttpContext ctx, Exception ex, CancellationToken ct) {\n        var problem = new ProblemDetails {\n            Status = ex is NotFoundException ? 404 : 500,\n            Title = ex.Message,\n            Instance = ctx.Request.Path\n        };\n        ctx.Response.StatusCode = problem.Status!.Value;\n        await ctx.Response.WriteAsJsonAsync(problem, ct);\n        return true;        \u002F\u002F true = đã handle, false = next handler\n    }\n}\n\n\u002F\u002F Register\nbuilder.Services.AddExceptionHandler\u003CGlobalExceptionHandler>();\nbuilder.Services.AddProblemDetails();\napp.UseExceptionHandler();\n",[491,4878,4879,4884,4889,4894,4899,4904,4909,4914,4918,4923,4928,4933,4937,4941,4945,4949,4954,4958],{"__ignoreMap":493},[790,4880,4881],{"class":792,"line":793},[790,4882,4883],{},"public class GlobalExceptionHandler : IExceptionHandler {\n",[790,4885,4886],{"class":792,"line":799},[790,4887,4888],{},"    public async ValueTask\u003Cbool> TryHandleAsync(\n",[790,4890,4891],{"class":792,"line":805},[790,4892,4893],{},"        HttpContext ctx, Exception ex, CancellationToken ct) {\n",[790,4895,4896],{"class":792,"line":811},[790,4897,4898],{},"        var problem = new ProblemDetails {\n",[790,4900,4901],{"class":792,"line":848},[790,4902,4903],{},"            Status = ex is NotFoundException ? 404 : 500,\n",[790,4905,4906],{"class":792,"line":854},[790,4907,4908],{},"            Title = ex.Message,\n",[790,4910,4911],{"class":792,"line":860},[790,4912,4913],{},"            Instance = ctx.Request.Path\n",[790,4915,4916],{"class":792,"line":866},[790,4917,3462],{},[790,4919,4920],{"class":792,"line":1019},[790,4921,4922],{},"        ctx.Response.StatusCode = problem.Status!.Value;\n",[790,4924,4925],{"class":792,"line":1025},[790,4926,4927],{},"        await ctx.Response.WriteAsJsonAsync(problem, ct);\n",[790,4929,4930],{"class":792,"line":1031},[790,4931,4932],{},"        return true;        \u002F\u002F true = đã handle, false = next handler\n",[790,4934,4935],{"class":792,"line":1037},[790,4936,2410],{},[790,4938,4939],{"class":792,"line":1043},[790,4940,869],{},[790,4942,4943],{"class":792,"line":1049},[790,4944,1130],{"emptyLinePlaceholder":1129},[790,4946,4947],{"class":792,"line":1055},[790,4948,4306],{},[790,4950,4951],{"class":792,"line":2302},[790,4952,4953],{},"builder.Services.AddExceptionHandler\u003CGlobalExceptionHandler>();\n",[790,4955,4956],{"class":792,"line":2308},[790,4957,3755],{},[790,4959,4960],{"class":792,"line":2314},[790,4961,4962],{},"app.UseExceptionHandler();\n",[443,4964],{},[446,4966,4968],{"id":4967},"n-câu-hỏi-thực-tế-hay-hỏi","N. Câu hỏi thực tế hay hỏi",[451,4970,4972],{"id":4971},"_50-bạn-debug-performance-issue-trên-aspnet-core-api-ra-sao","50. ⭐ Bạn debug performance issue trên ASP.NET Core API ra sao?",[933,4974,4975,4985,4991,5004,5010,5016],{},[465,4976,4977,4980,4981,4984],{},[435,4978,4979],{},"Đo trước tiên",": dùng ",[491,4982,4983],{},"dotnet-trace",", Application Insights hoặc MiniProfiler. Đừng đoán.",[465,4986,4987,4990],{},[435,4988,4989],{},"Check DB",": LINQ generated SQL có N+1 không? Missing index?",[465,4992,4993,4996,4997,954,5000,5003],{},[435,4994,4995],{},"Check async",": có ",[491,4998,4999],{},"Task.Result",[491,5001,5002],{},".Wait()"," chặn thread không?",[465,5005,5006,5009],{},[435,5007,5008],{},"Check allocations",": dùng dotnet-counters, JetBrains dotMemory.",[465,5011,5012,5015],{},[435,5013,5014],{},"Output Caching",": có thể cache response của endpoint không?",[465,5017,5018,5021,5022,5025],{},[435,5019,5020],{},"Response size",": có đang trả ",[491,5023,5024],{},"SELECT *"," về cả triệu field?",[451,5027,5029],{"id":5028},"_51-token-hết-hạn-trong-lúc-user-đang-dùng-xử-lý-ra-sao","51. ⭐ Token hết hạn trong lúc user đang dùng — xử lý ra sao?",[432,5031,5032],{},[435,5033,5034],{},"Refresh token pattern:",[933,5036,5037,5040,5043,5050,5053],{},[465,5038,5039],{},"JWT access token — thời gian ngắn (15m–1h).",[465,5041,5042],{},"Refresh token — lưu DB, thời gian dài (7–30 ngày).",[465,5044,5045,5046,5049],{},"Client detect 401 → tự call ",[491,5047,5048],{},"\u002Fauth\u002Frefresh"," với refresh token.",[465,5051,5052],{},"Server verify refresh token còn hợp lệ → cấp JWT mới.",[465,5054,5055],{},"Nếu refresh token expired → force logout.",[451,5057,5059],{"id":5058},"_52-làm-sao-test-controller-mà-không-cần-chạy-full-server","52. ⭐ Làm sao test controller mà không cần chạy full server?",[484,5061,5063],{"className":784,"code":5062,"language":786,"meta":493,"style":493},"\u002F\u002F Unit test controller trực tiếp — mock service\npublic class UsersControllerTests {\n    [Fact]\n    public async Task GetById_Returns404_WhenNotFound() {\n        var mockService = new Mock\u003CIUserService>();\n        mockService.Setup(s => s.GetByIdAsync(999)).ReturnsAsync((UserDto?)null);\n        \n        var controller = new UsersController(mockService.Object);\n        var result = await controller.Get(999);\n        \n        Assert.IsType\u003CNotFoundResult>(result.Result);\n    }\n}\n\n\u002F\u002F Integration test — WebApplicationFactory\npublic class ApiTests : IClassFixture\u003CWebApplicationFactory\u003CProgram>> {\n    private readonly HttpClient _client;\n    public ApiTests(WebApplicationFactory\u003CProgram> factory) {\n        _client = factory.CreateClient();\n    }\n    \n    [Fact]\n    public async Task GetUsers_Returns200() {\n        var response = await _client.GetAsync(\"\u002Fapi\u002Fusers\");\n        response.EnsureSuccessStatusCode();\n    }\n}\n",[491,5064,5065,5070,5075,5080,5085,5090,5095,5099,5104,5109,5113,5118,5122,5126,5130,5135,5140,5145,5150,5155,5159,5163,5167,5172,5177,5182,5186],{"__ignoreMap":493},[790,5066,5067],{"class":792,"line":793},[790,5068,5069],{},"\u002F\u002F Unit test controller trực tiếp — mock service\n",[790,5071,5072],{"class":792,"line":799},[790,5073,5074],{},"public class UsersControllerTests {\n",[790,5076,5077],{"class":792,"line":805},[790,5078,5079],{},"    [Fact]\n",[790,5081,5082],{"class":792,"line":811},[790,5083,5084],{},"    public async Task GetById_Returns404_WhenNotFound() {\n",[790,5086,5087],{"class":792,"line":848},[790,5088,5089],{},"        var mockService = new Mock\u003CIUserService>();\n",[790,5091,5092],{"class":792,"line":854},[790,5093,5094],{},"        mockService.Setup(s => s.GetByIdAsync(999)).ReturnsAsync((UserDto?)null);\n",[790,5096,5097],{"class":792,"line":860},[790,5098,2400],{},[790,5100,5101],{"class":792,"line":866},[790,5102,5103],{},"        var controller = new UsersController(mockService.Object);\n",[790,5105,5106],{"class":792,"line":1019},[790,5107,5108],{},"        var result = await controller.Get(999);\n",[790,5110,5111],{"class":792,"line":1025},[790,5112,2400],{},[790,5114,5115],{"class":792,"line":1031},[790,5116,5117],{},"        Assert.IsType\u003CNotFoundResult>(result.Result);\n",[790,5119,5120],{"class":792,"line":1037},[790,5121,2410],{},[790,5123,5124],{"class":792,"line":1043},[790,5125,869],{},[790,5127,5128],{"class":792,"line":1049},[790,5129,1130],{"emptyLinePlaceholder":1129},[790,5131,5132],{"class":792,"line":1055},[790,5133,5134],{},"\u002F\u002F Integration test — WebApplicationFactory\n",[790,5136,5137],{"class":792,"line":2302},[790,5138,5139],{},"public class ApiTests : IClassFixture\u003CWebApplicationFactory\u003CProgram>> {\n",[790,5141,5142],{"class":792,"line":2308},[790,5143,5144],{},"    private readonly HttpClient _client;\n",[790,5146,5147],{"class":792,"line":2314},[790,5148,5149],{},"    public ApiTests(WebApplicationFactory\u003CProgram> factory) {\n",[790,5151,5152],{"class":792,"line":2319},[790,5153,5154],{},"        _client = factory.CreateClient();\n",[790,5156,5157],{"class":792,"line":2325},[790,5158,2410],{},[790,5160,5161],{"class":792,"line":2331},[790,5162,851],{},[790,5164,5165],{"class":792,"line":2644},[790,5166,5079],{},[790,5168,5169],{"class":792,"line":2650},[790,5170,5171],{},"    public async Task GetUsers_Returns200() {\n",[790,5173,5174],{"class":792,"line":2656},[790,5175,5176],{},"        var response = await _client.GetAsync(\"\u002Fapi\u002Fusers\");\n",[790,5178,5179],{"class":792,"line":2662},[790,5180,5181],{},"        response.EnsureSuccessStatusCode();\n",[790,5183,5184],{"class":792,"line":2668},[790,5185,2410],{},[790,5187,5188],{"class":792,"line":2806},[790,5189,869],{},[443,5191],{},[446,5193,5195],{"id":5194},"tham-khảo","Tham khảo",[462,5197,5198,5206,5213,5220,5227,5234,5241,5248,5255],{},[465,5199,5200],{},[1672,5201,5205],{"href":5202,"rel":5203},"https:\u002F\u002Fcodewithmukesh.com\u002Fblog\u002Fdotnet-webapi-interview-questions\u002F",[5204],"nofollow","codewithmukesh — 45 Web API Questions Actually Asked 2026",[465,5207,5208],{},[1672,5209,5212],{"href":5210,"rel":5211},"https:\u002F\u002Fbool.dev\u002Fblog\u002Fdetail\u002Fpart6-aspnet-core-csharp-interview-questions",[5204],"bool.dev — ASP.NET Core Interview 2026",[465,5214,5215],{},[1672,5216,5219],{"href":5217,"rel":5218},"https:\u002F\u002Fdotnettutorials.net\u002Flesson\u002Fasp-net-core-web-api-basic-interview-questions-and-answers\u002F",[5204],"Dot Net Tutorials — Web API Interview Q&A",[465,5221,5222],{},[1672,5223,5226],{"href":5224,"rel":5225},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Faspnet\u002Fmvc\u002Foverview\u002Fgetting-started\u002Flifecycle-of-an-aspnet-mvc-5-application",[5204],"Microsoft Learn — MVC 5 Lifecycle",[465,5228,5229],{},[1672,5230,5233],{"href":5231,"rel":5232},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Faspnet\u002Fcore\u002Ffundamentals\u002Fapis?view=aspnetcore-9.0",[5204],"Microsoft Learn — APIs Overview ASP.NET Core 9",[465,5235,5236],{},[1672,5237,5240],{"href":5238,"rel":5239},"https:\u002F\u002Fwww.hirist.tech\u002Fblog\u002Ftop-25-web-api-interview-questions-and-answers\u002F",[5204],"Hirist Blog — Top 25 Web API Questions",[465,5242,5243],{},[1672,5244,5247],{"href":5245,"rel":5246},"https:\u002F\u002Fkungfutech.edu.vn\u002Fphong-van-pro\u002Ffull-stack-web-and-mobile\u002Faspnet-mvc",[5204],"KungFuTech VN — ASP.NET MVC Interview",[465,5249,5250],{},[1672,5251,5254],{"href":5252,"rel":5253},"https:\u002F\u002Ftopdev.vn\u002Fblog\u002Ftop-5-cau-hoi-phong-van-asp-net\u002F",[5204],"TopDev VN — Top 5 câu hỏi ASP.NET",[465,5256,5257],{},[1672,5258,5261],{"href":5259,"rel":5260},"https:\u002F\u002Fwww.phongvanit.com\u002Fky-nang\u002Faspnetaspnet-mvc-1013",[5204],"PhongvanIT — ASP.NET MVC Q&A",[5263,5264,5265],"style",{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":493,"searchDepth":799,"depth":799,"links":5267},[5268,5273,5277,5282,5290,5295,5301,5306,5314,5319,5325,5331,5341,5347,5352],{"id":448,"depth":799,"text":449,"children":5269},[5270,5271,5272],{"id":453,"depth":805,"text":454},{"id":496,"depth":805,"text":497},{"id":584,"depth":805,"text":585},{"id":722,"depth":799,"text":723,"children":5274},[5275,5276],{"id":726,"depth":805,"text":727},{"id":736,"depth":805,"text":737},{"id":770,"depth":799,"text":771,"children":5278},[5279,5280,5281],{"id":774,"depth":805,"text":775},{"id":878,"depth":805,"text":879},{"id":918,"depth":805,"text":919},{"id":966,"depth":799,"text":967,"children":5283},[5284,5285,5287,5289],{"id":970,"depth":805,"text":971},{"id":1061,"depth":805,"text":5286},"10. 🟢 IActionResult vs ActionResult\u003CT> khác gì?",{"id":1160,"depth":805,"text":5288},"11. 🔵 [ApiController] attribute làm gì?",{"id":1205,"depth":805,"text":1206},{"id":1285,"depth":799,"text":1286,"children":5291},[5292,5293,5294],{"id":1289,"depth":805,"text":1290},{"id":1414,"depth":805,"text":1415},{"id":1486,"depth":805,"text":1487},{"id":1558,"depth":799,"text":1559,"children":5296},[5297,5298,5299,5300],{"id":1562,"depth":805,"text":1563},{"id":1717,"depth":805,"text":1718},{"id":1867,"depth":805,"text":1868},{"id":1985,"depth":805,"text":1986},{"id":2218,"depth":799,"text":2219,"children":5302},[5303,5304],{"id":2222,"depth":805,"text":2223},{"id":2336,"depth":805,"text":5305},"21. 🔵 ModelState.IsValid và FluentValidation?",{"id":2426,"depth":799,"text":2427,"children":5307},[5308,5309,5310,5311,5312,5313],{"id":2430,"depth":805,"text":2431},{"id":2477,"depth":805,"text":2478},{"id":2536,"depth":805,"text":2537},{"id":2673,"depth":805,"text":2674},{"id":2844,"depth":805,"text":2845},{"id":2941,"depth":805,"text":2942},{"id":3039,"depth":799,"text":3040,"children":5315},[5316,5317,5318],{"id":3043,"depth":805,"text":3044},{"id":3181,"depth":805,"text":3182},{"id":3247,"depth":805,"text":3248},{"id":3318,"depth":799,"text":3319,"children":5320},[5321,5322,5323,5324],{"id":3322,"depth":805,"text":3323},{"id":3392,"depth":805,"text":3393},{"id":3469,"depth":805,"text":3470},{"id":3578,"depth":805,"text":3579},{"id":3623,"depth":799,"text":3624,"children":5326},[5327,5328,5329,5330],{"id":3627,"depth":805,"text":3628},{"id":3669,"depth":805,"text":3670},{"id":3739,"depth":805,"text":3740},{"id":3806,"depth":805,"text":3807},{"id":3880,"depth":799,"text":3881,"children":5332},[5333,5334,5336,5337,5338,5339,5340],{"id":3884,"depth":805,"text":3885},{"id":3985,"depth":805,"text":5335},"40. ⭐ Khi nào dùng [FromBody] vs [FromForm]?",{"id":4059,"depth":805,"text":4060},{"id":4135,"depth":805,"text":4136},{"id":4239,"depth":805,"text":4240},{"id":4337,"depth":805,"text":4338},{"id":4452,"depth":805,"text":4453},{"id":4594,"depth":799,"text":4595,"children":5342},[5343,5344,5345,5346],{"id":4598,"depth":805,"text":4599},{"id":4694,"depth":805,"text":4695},{"id":4812,"depth":805,"text":4813},{"id":4872,"depth":805,"text":4873},{"id":4967,"depth":799,"text":4968,"children":5348},[5349,5350,5351],{"id":4971,"depth":805,"text":4972},{"id":5028,"depth":805,"text":5029},{"id":5058,"depth":805,"text":5059},{"id":5194,"depth":799,"text":5195},"80+ câu hỏi ASP.NET MVC 5 tới ASP.NET Core 8\u002F9\u002F10, Minimal API, versioning, rate limiting, output caching. Phỏng vấn fresher tới senior.","md",null,{},{"title":108,"icon":91},{"title":422,"description":5353},"_ym9SYN9H91D_4fJPaA-L7Z33_wxSc4ls1Gr1i0Cj8s",[5361,5363],{"title":103,"path":104,"stem":105,"description":5362,"icon":106,"children":-1},"55+ câu hỏi Design Pattern hay hỏi nhất trong phỏng vấn .NET — GoF, .NET-specific patterns (Repository, UoW, Options, MediatR, CQRS), anti-pattern.",{"title":119,"path":120,"stem":121,"description":5364,"icon":122,"children":-1},"Tóm tắt 1 trang các khái niệm quan trọng nhất cho fresher .NET — đọc 30 phút sáng trước khi đi phỏng vấn.",1783490355827]