Ngân hàng câu hỏi ASP.NET MVC và Web API
A. MVC Pattern — Nền tảng
1. 🟢 MVC là gì? 3 thành phần?
Model-View-Controller — pattern tách ứng dụng thành 3 phần:
- Model: dữ liệu và business logic. Không biết về UI.
- View: giao diện — nhận data từ Model, render HTML. Không có business logic.
- Controller: điều phối — nhận request, gọi Model, chọn View trả về.
HTTP Request
↓
Controller (nhận, xử lý)
↓
Model (lấy/tính toán data)
↓
Controller (chọn view + truyền data)
↓
View (render HTML)
↓
HTTP Response
2. 🟢 Lợi ích của MVC so với WebForms?
| WebForms | MVC | |
|---|---|---|
| Code-behind | Gắn liền HTML + C# | Tách rõ |
| Testability | Khó test | Dễ unit test |
| URL | Dạng file path (Default.aspx) | Clean URL (/users/123) |
| HTML control | Server controls, viewstate | Full control HTML |
| Lifecycle | Phức tạp (Page lifecycle) | Đơn giản hơn |
3. 🟢 ASP.NET MVC 5 khác ASP.NET Core MVC ra sao?
| ASP.NET MVC 5 (.NET Framework) | ASP.NET Core MVC | |
|---|---|---|
| Platform | Windows only | Cross-platform |
| Hosting | IIS bắt buộc | Kestrel + IIS/Nginx/Apache |
| DI | Cần lib ngoài (Unity, Autofac) | Built-in |
| Config | web.config XML | JSON (appsettings.json) |
| Middleware | HTTP Module/Handler | Pipeline middleware |
| Performance | Chậm hơn | Nhanh hơn nhiều |
| Startup | Global.asax + RouteConfig.cs | Program.cs |
| Còn support | .NET Framework 4.8 (EOL xa) | .NET 8/9/10 LTS |
⭐ Câu hay hỏi: "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.
B. Request Lifecycle — MVC 5
4. 🟡 Request lifecycle trong ASP.NET MVC 5?
Browser Request
↓
1. UrlRoutingModule — match URL với route table
↓
2. MVC Route Handler — xác nhận đây là MVC request
↓
3. MvcHandler + ControllerFactory — tạo instance Controller
↓
4. Action Invoker — chọn Action method phù hợp
↓
5. Model Binding — map request data → action parameters
↓
6. Action Filters (OnActionExecuting) — trước khi action chạy
↓
7. Action Method — business logic, trả ActionResult
↓
8. Action Filters (OnActionExecuted) — sau khi action chạy
↓
9. Result Execution (View/JSON/Redirect)
↓
10. Result Filters — sau khi result render
↓
Response
5. 🟡 Request lifecycle trong ASP.NET Core MVC?
Request
↓
Kestrel Server
↓
Middleware Pipeline (UseRouting → UseAuth → UseEndpoints)
↓
Endpoint Routing — chọn controller/action
↓
Model Binding — bind route/query/body
↓
Action Filters (IActionFilter)
↓
Action Method
↓
Result Filters (IResultFilter)
↓
IActionResult.ExecuteAsync() — render view/JSON
↓
Response
Khác MVC 5: ASP.NET Core không có HttpHandler/HttpModule — thay bằng Middleware pipeline. Mọi thứ đi qua IMiddleware chain.
C. Routing
6. 🟢 Conventional routing vs Attribute routing?
Conventional routing — khai báo template tập trung:
// Program.cs / RouteConfig.cs (MVC 5)
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Attribute routing — đặt ngay trên controller/action:
[Route("api/users")]
public class UsersController : ControllerBase {
[HttpGet("{id:int}")] // GET /api/users/42
public IActionResult Get(int id) { ... }
[HttpPost] // POST /api/users
public IActionResult Create(CreateUserDto dto) { ... }
}
⭐ Web API nên dùng Attribute routing — rõ ràng, version URL dễ.
7. 🟢 Route constraint là gì? Ví dụ?
Ràng buộc giá trị parameter trong route:
[HttpGet("{id:int}")] // id phải là số nguyên
[HttpGet("{slug:alpha}")] // slug chỉ chứa chữ cái
[HttpGet("{age:range(1,120)}")] // age từ 1 đến 120
[HttpGet("{id:guid}")] // id phải là GUID
[HttpGet("{name:minlength(3)}")] // name >= 3 ký tự
Nếu không match constraint → 404, không đi vào action.
8. 🔵 Endpoint routing (ASP.NET Core 3+) khác IRouter cũ ra sao?
Cũ (MVC 5 / Core 2): routing gắn liền với MVC handler. Middleware không biết route nào sẽ chạy.
Endpoint routing (Core 3+): routing tách thành 2 bước:
UseRouting()— chọn endpoint (gắn vàoHttpContext.GetEndpoint()).UseAuthorization()/UseEndpoints()— middleware sau biết endpoint, có thể check auth TRƯỚC khi chạy action.
Lợi ích: middleware biết sớm endpoint → auth check chính xác hơn.
D. Controller và Action
9. 🟢 ActionResult các loại hay dùng?
return View(); // ViewResult — render HTML
return View("MyView", model); // ViewResult với view name + model
return Ok(data); // 200 JSON (Web API)
return Created(uri, data); // 201 Created
return NoContent(); // 204 No Content
return BadRequest("invalid"); // 400
return NotFound(); // 404
return Unauthorized(); // 401
return Forbid(); // 403
return Redirect("/home"); // 302 Redirect
return RedirectToAction("Index"); // Redirect tới action
return Json(obj); // JSON (MVC 5 style)
return File(bytes, "image/png"); // File response
return Content("text", "text/plain"); // Plain text
return StatusCode(429); // Custom status
10. 🟢 IActionResult vs ActionResult<T> khác gì?
IActionResult: return bất kỳ loại result, không type-safe.ActionResult<T>: generic — cũng return bất kỳ IActionResult, hoặc returnTtrực tiếp (sẽ tự wrap Ok(T)).
// IActionResult — không biết return gì
public IActionResult GetUser(int id) {
var u = _service.Get(id);
if (u == null) return NotFound();
return Ok(u);
}
// ActionResult<T> — rõ ràng hơn, Swagger biết type
public ActionResult<UserDto> GetUser(int id) {
var u = _service.Get(id);
if (u == null) return NotFound();
return u; // implicit Ok(u)
}
11. 🔵 [ApiController] attribute làm gì?
- Automatic model validation → BadRequest 400 nếu
ModelState.IsValid == false. - Attribute routing required — không dùng conventional routing.
- Binding source inference —
[FromBody]cho complex type,[FromRoute]cho route param tự động. - ProblemDetails response format theo RFC 7807.
12. 🟢 Model Binding hoạt động ra sao?
Framework tự map data từ request vào action parameters theo thứ tự:
[FromRoute]— route params{id}.[FromQuery]— query string?name=Alice.[FromBody]— JSON/form body.[FromHeader]— HTTP header.[FromForm]— form data.
[HttpPut("{id}")]
public IActionResult Update(
int id, // FromRoute
[FromQuery] bool notify, // FromQuery
[FromBody] UpdateUserDto dto) // FromBody
{ ... }
E. Filters
13. 🔵 Filter trong ASP.NET Core — các loại?
| Filter | Interface | Khi chạy | Dùng cho |
|---|---|---|---|
| Authorization | IAuthorizationFilter | Trước tất cả | Auth check |
| Resource | IResourceFilter | Sau Auth, bao action | Caching, short-circuit |
| Action | IActionFilter | Trước/sau action | Logging, validation |
| Exception | IExceptionFilter | Khi có exception | Error handling |
| Result | IResultFilter | Trước/sau result | Response transform |
Thứ tự chạy:
Authorization → Resource → [Action → ActionMethod → Action] → [Result → ResultMethod → Result] → Exception
14. 🔵 Filter scope — đăng ký ở đâu?
// 1. Global — áp cho tất cả
builder.Services.AddControllers(options => {
options.Filters.Add<GlobalExceptionFilter>();
});
// 2. Controller — áp cho tất cả action trong controller
[ServiceFilter(typeof(LoggingFilter))]
public class UsersController : ControllerBase { }
// 3. Action — chỉ action đó
[HttpGet]
[TypeFilter(typeof(RateLimitFilter))]
public IActionResult GetAll() { }
15. 🔵 Filter vs Middleware — khác gì?
| Middleware | Filter | |
|---|---|---|
| Level | HTTP pipeline toàn app | MVC pipeline |
| Biết về routing | ❌ (trừ dùng sau UseRouting) | ✅ |
| Biết về action | ❌ | ✅ (ActionContext) |
| Short-circuit | ✅ | ✅ |
| Dùng cho | CORS, Auth, logging chung | Validation, caching, business logic |
F. Views và Razor
16. 🟢 Razor syntax cơ bản?
@* Comment *@
@{ var name = "Alice"; } @* Code block *@
<p>Hello @name</p> @* Output biến *@
@if (Model.IsAdmin) {
<span>Admin</span>
}
@foreach (var item in Model.Items) {
<li>@item.Name</li>
}
@Html.ActionLink("Click", "Index", "Home") @* HTML helper *@
<a asp-controller="Home" asp-action="Index">Click</a> @* Tag helper *@
17. 🟢 ViewData vs ViewBag vs TempData vs Session?
| Scope | Type | Khi nào | |
|---|---|---|---|
ViewData | 1 request | Dictionary<string, object> | Truyền data Controller → View |
ViewBag | 1 request | dynamic (wrapper ViewData) | Giống ViewData, cú pháp dynamic |
TempData | 1 redirect (xong xoá) | Dictionary<string, object> | Thông báo sau redirect |
Session | Nhiều request | Key-value | User session state |
// Controller
ViewData["Name"] = "Alice";
ViewBag.Age = 25;
TempData["Success"] = "Saved!";
// View
<p>@ViewData["Name"]</p>
<p>@ViewBag.Age</p>
@if (TempData["Success"] != null) {
<div class="alert">@TempData["Success"]</div>
}
18. 🔵 Partial View vs ViewComponent vs Display Template?
| Dùng khi | Có DI? | Logic phức tạp? | |
|---|---|---|---|
| Partial View | Tái sử dụng HTML fragment | ❌ (nhận ViewModel) | ❌ |
| ViewComponent | Widget độc lập (basket, cart, notification) | ✅ | ✅ |
| Display/Editor Template | Format kiểu dữ liệu (DateTime, Money) | ❌ | ❌ |
// ViewComponent
public class CartSummaryViewComponent : ViewComponent {
private readonly ICartService _cart;
public CartSummaryViewComponent(ICartService cart) => _cart = cart;
public IViewComponentResult Invoke() => View(_cart.GetSummary());
}
// Gọi trong View
@await Component.InvokeAsync("CartSummary")
// Hoặc
<vc:cart-summary />
19. 🟢 Layout trong Razor là gì?
Layout là template cha — define @RenderBody() + @RenderSection() cho phần động:
<!-- _Layout.cshtml -->
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
@await RenderSectionAsync("styles", required: false)
</head>
<body>
<nav>...</nav>
@RenderBody() @* Content của page con *@
@await RenderSectionAsync("scripts", required: false)
</body>
</html>
Page con dùng @section để điền vào:
@{
Layout = "_Layout";
ViewBag.Title = "Home";
}
@section styles {
<link rel="stylesheet" href="~/css/home.css" />
}
<h1>Welcome</h1>
G. Model Validation
20. 🟢 Data Annotation validation?
public class CreateUserDto {
[Required(ErrorMessage = "Name is required")]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = "";
[Required]
[EmailAddress]
public string Email { get; set; } = "";
[Range(18, 120)]
public int Age { get; set; }
[Phone]
public string? Phone { get; set; }
[Url]
public string? Website { get; set; }
[RegularExpression(@"^[A-Z]{2,}$", ErrorMessage = "Code phải là chữ hoa")]
public string? Code { get; set; }
}
21. 🔵 ModelState.IsValid và FluentValidation?
// Manual check (không dùng [ApiController])
if (!ModelState.IsValid)
return BadRequest(ModelState);
// FluentValidation — cho business rule phức tạp
public class CreateUserValidator : AbstractValidator<CreateUserDto> {
public CreateUserValidator(IUserRepository repo) {
RuleFor(x => x.Email).EmailAddress()
.MustAsync(async (email, ct) => !await repo.ExistsAsync(email))
.WithMessage("Email already taken");
RuleFor(x => x.Age).GreaterThanOrEqualTo(18);
}
}
FluentValidation tích hợp ASP.NET Core qua services.AddFluentValidation() — auto chạy trước action.
H. Web API
22. 🟢 REST API best practices?
- URL là resource (danh từ):
/api/users,/api/orders/{id}. - HTTP method = hành động: GET/POST/PUT/PATCH/DELETE.
- Status code đúng: 200/201/204/400/401/403/404/422/500.
- Versioning qua URL:
/api/v1/users. - JSON camelCase response.
- Paging:
?page=1&size=20. - Filtering:
?city=HCM&active=true.
23. ⭐🔵 Content Negotiation là gì?
Client gửi Accept header → server trả format phù hợp:
Accept: application/json → trả JSON
Accept: application/xml → trả XML (nếu support)
Accept: text/csv → trả CSV (custom formatter)
// Thêm XML support
builder.Services.AddControllers()
.AddXmlSerializerFormatters();
// Custom formatter
builder.Services.AddControllers(options => {
options.OutputFormatters.Add(new CsvOutputFormatter());
});
24. 🔵 Versioning API — các cách?
// Dùng Asp.Versioning.Http package
// 1. URL versioning (phổ biến nhất)
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersV1Controller : ControllerBase { }
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersV2Controller : ControllerBase { }
// 2. Header versioning
// Request: Api-Version: 2.0
// 3. Query string
// Request: /api/users?api-version=2.0
// nuxt.config:
builder.Services.AddApiVersioning(opt => {
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true; // header: api-supported-versions
}).AddApiExplorer(opt => {
opt.GroupNameFormat = "'v'VVV";
opt.SubstituteApiVersionInUrl = true;
});
25. ⭐🔵 Rate Limiting (ASP.NET Core 7+ built-in)?
// Program.cs
builder.Services.AddRateLimiter(opt => {
// Fixed window — 10 req / 10s per IP
opt.AddFixedWindowLimiter("fixed", o => {
o.PermitLimit = 10;
o.Window = TimeSpan.FromSeconds(10);
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
o.QueueLimit = 5;
});
// Sliding window — smoothes out traffic
opt.AddSlidingWindowLimiter("sliding", o => {
o.PermitLimit = 100;
o.Window = TimeSpan.FromMinutes(1);
o.SegmentsPerWindow = 4;
});
// Token bucket — cho burst
opt.AddTokenBucketLimiter("token", o => {
o.TokenLimit = 100;
o.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
o.TokensPerPeriod = 10;
});
opt.RejectionStatusCode = 429; // Too Many Requests
});
app.UseRateLimiter();
// Áp dụng per-endpoint
[EnableRateLimiting("fixed")]
[HttpGet]
public IActionResult GetAll() { }
26. 🔵 Output Caching (ASP.NET Core 7+ built-in)?
builder.Services.AddOutputCache(opt => {
opt.AddBasePolicy(b => b.Expire(TimeSpan.FromMinutes(5)));
opt.AddPolicy("ShortCache", b => b.Expire(TimeSpan.FromSeconds(30)));
});
app.UseOutputCache();
// Action
[HttpGet]
[OutputCache(Duration = 60)]
public IActionResult GetProducts() { }
// Với vary by query
[OutputCache(Duration = 120, VaryByQueryKeys = new[] { "category", "page" })]
public IActionResult GetByCategory(string category, int page = 1) { }
Khác ResponseCache: Output Cache store ở server (memory/Redis). ResponseCache chỉ set HTTP header Cache-Control để browser/CDN cache.
27. ⭐🔵 HybridCache (.NET 9 mới)?
// Combine in-memory (L1) + Redis (L2) + stampede protection
builder.Services.AddHybridCache();
// Inject
public class UserService {
private readonly HybridCache _cache;
public UserService(HybridCache cache) => _cache = cache;
public async Task<User?> GetAsync(int id, CancellationToken ct = default) {
return await _cache.GetOrCreateAsync(
$"user:{id}",
async token => await _db.Users.FindAsync(id, token),
cancellationToken: ct);
}
}
Lợi ích vs IMemoryCache + IDistributedCache:
- Không cần tự viết stampede protection (cache stampede: nhiều request cùng lúc cache miss → cùng query DB).
- Tự động L1 → L2 fallback.
- Single API cho cả 2 level.
I. Minimal API (NET 6+)
28. ⭐🔵 Minimal API vs Controller — khi nào chọn?
// Minimal API — ít code hơn, nhanh hơn ~5-10% throughput
var app = WebApplication.Create();
app.MapGet("/users/{id:int}", async (int id, IUserService service) =>
await service.GetByIdAsync(id) is User u ? Results.Ok(u) : Results.NotFound());
app.MapPost("/users", async (CreateUserDto dto, IUserService service) => {
var created = await service.CreateAsync(dto);
return Results.Created($"/users/{created.Id}", created);
});
| Minimal API | Controller-based | |
|---|---|---|
| Verbosity | Ít code | Nhiều boilerplate |
| Performance | ~5-10% nhanh hơn | Tốt |
| Tổ chức lớn | Khó (dùng Route Groups) | Dễ (class + method) |
| Filters | Hạn chế (endpoint filter) | Đầy đủ (Action, Result, Exception) |
| Convention | Ít | Nhiều (routing convention, naming) |
| Khi nào | Microservice, minimal API, demo | App lớn, nhiều dev |
29. 🔵 Route Groups trong Minimal API?
// Nhóm route có prefix + shared middleware
var users = app.MapGroup("/api/v1/users").RequireAuthorization();
users.MapGet("/", async (IUserService s) => await s.GetAllAsync());
users.MapGet("/{id:int}", async (int id, IUserService s) => {
var u = await s.GetByIdAsync(id);
return u is not null ? Results.Ok(u) : Results.NotFound();
});
users.MapPost("/", async (CreateUserDto dto, IUserService s) => {
var created = await s.CreateAsync(dto);
return Results.Created($"/api/v1/users/{created.Id}", created);
});
30. 🔵 Endpoint Filter (Minimal API)?
Tương đương Action Filter nhưng cho Minimal API:
app.MapPost("/users", CreateUser)
.AddEndpointFilter<ValidationFilter<CreateUserDto>>();
public class ValidationFilter<T> : IEndpointFilter where T : class {
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext ctx, EndpointFilterDelegate next) {
var dto = ctx.Arguments.OfType<T>().FirstOrDefault();
if (dto is null) return Results.BadRequest("Invalid body");
// Validate...
return await next(ctx);
}
}
J. Security
31. ⭐🔵 Authentication vs Authorization trong ASP.NET Core?
- Authentication: "Bạn là ai?" — xác minh identity, populate
HttpContext.User(ClaimsPrincipal). - Authorization: "Bạn được làm gì?" — check role/policy/claim.
// Middleware thứ tự BẮT BUỘC:
app.UseAuthentication(); // 1. Populate User
app.UseAuthorization(); // 2. Check User
// [Authorize] attribute
[Authorize] // chỉ require login
[Authorize(Roles = "Admin,Manager")] // require role
[Authorize(Policy = "MinAge18")] // require policy
[AllowAnonymous] // bỏ qua auth
32. 🔵 JWT Authentication setup?
// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt => {
opt.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = config["Jwt:Issuer"],
ValidAudience = config["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(config["Jwt:Key"]!))
};
});
33. 🔵 Policy-based Authorization?
builder.Services.AddAuthorization(opt => {
opt.AddPolicy("AdminOnly", p => p.RequireRole("Admin"));
opt.AddPolicy("MinAge18", p => p.RequireClaim("age", "18", "19", "20"));
opt.AddPolicy("PremiumUser", p =>
p.Requirements.Add(new PremiumRequirement()));
});
// Custom requirement
public class PremiumRequirement : IAuthorizationRequirement {}
public class PremiumHandler : AuthorizationHandler<PremiumRequirement> {
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext ctx, PremiumRequirement req) {
if (ctx.User.HasClaim("subscription", "premium"))
ctx.Succeed(req);
return Task.CompletedTask;
}
}
// Dùng
[Authorize(Policy = "PremiumUser")]
public IActionResult PremiumContent() { }
34. 🔵 CORS setup?
builder.Services.AddCors(opt => opt.AddPolicy("default", policy =>
policy.WithOrigins("https://app.com", "http://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
app.UseCors("default"); // Phải TRƯỚC UseAuthentication
K. Performance và Monitoring
35. 🔵 Response Compression?
builder.Services.AddResponseCompression(opt => {
opt.EnableForHttps = true; // cẩn thận BREACH attack với sensitive data
opt.Providers.Add<BrotliCompressionProvider>();
opt.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();
36. 🟡 Health Check?
builder.Services.AddHealthChecks()
.AddSqlServer(config.GetConnectionString("Default")!)
.AddRedis(config.GetConnectionString("Redis")!)
.AddCheck<CustomHealthCheck>("custom");
app.MapHealthChecks("/health", new HealthCheckOptions {
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// Check endpoint liveness/readiness riêng
app.MapHealthChecks("/health/live", new HealthCheckOptions {
Predicate = _ => false // chỉ check app alive, không check DB
});
37. 🟡 Problem Details (RFC 7807)?
// Chuẩn hoá error response
builder.Services.AddProblemDetails();
// Kết quả khi BadRequest:
{
"type": "https://httpstatuses.com/400",
"title": "Bad Request",
"status": 400,
"detail": "Name is required",
"instance": "/api/users",
"traceId": "00-abc123..."
}
38. 🟡 Minimal API OpenAPI (NET 9+ — không cần Swashbuckle)?
// .NET 9+: Microsoft.AspNetCore.OpenApi
builder.Services.AddOpenApi();
app.MapOpenApi(); // GET /openapi/v1.json
// Với metadata
app.MapGet("/users/{id}", GetUser)
.WithName("GetUser")
.WithSummary("Get user by ID")
.WithDescription("Returns user or 404")
.Produces<UserDto>(200)
.Produces(404)
.WithTags("Users");
L. Câu hỏi hay + Scenario
39. ⭐ Sự khác nhau giữa MVC 5 và Core trong xử lý exception?
MVC 5:
// Global.asax / FilterConfig.cs
filters.Add(new HandleErrorAttribute());
// Dùng customErrors trong web.config
ASP.NET Core:
// Middleware — app-level
app.UseExceptionHandler("/error");
app.UseDeveloperExceptionPage(); // Dev only
// Problem Details middleware (NET 8+)
app.UseExceptionHandler(); // Auto ProblemDetails
app.UseStatusCodePages();
// Custom exception middleware
app.UseMiddleware<GlobalExceptionMiddleware>();
// Filter level
services.AddControllers(o => o.Filters.Add<GlobalExceptionFilter>());
40. ⭐ Khi nào dùng [FromBody] vs [FromForm]?
[FromBody]: JSON/XML payload. Request cóContent-Type: application/json. Bắt buộc với complex type trong Web API.[FromForm]: HTML form (Content-Type: application/x-www-form-urlencodedhoặcmultipart/form-data). Dùng khi nhận file upload.
// Web API — nhận JSON
[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto) { }
// MVC form submit + file
[HttpPost]
public IActionResult Upload([FromForm] IFormFile file, [FromForm] string desc) { }
41. ⭐🔵 Async action method vs sync — khi nào dùng?
// ❌ Sync — block thread khi có I/O
[HttpGet("{id}")]
public IActionResult Get(int id) {
var u = _db.Users.Find(id); // block!
return Ok(u);
}
// ✅ Async — giải phóng thread khi chờ DB/HTTP
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> Get(int id) {
var u = await _db.Users.FindAsync(id);
return u is null ? NotFound() : Ok(u);
}
ASP.NET Core 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.
42. 🔵 Làm sao trả về file từ API?
// PDF/Excel file
[HttpGet("{id}/invoice")]
public async Task<IActionResult> GetInvoice(int id) {
var bytes = await _service.GenerateInvoicePdfAsync(id);
return File(bytes, "application/pdf", $"invoice-{id}.pdf");
}
// Stream (tốt hơn cho file lớn)
[HttpGet("{id}/report")]
public async Task<IActionResult> GetReport(int id) {
var stream = await _service.GetReportStreamAsync(id);
return File(stream, "application/octet-stream", "report.xlsx");
}
// Redirect to blob storage
[HttpGet("{id}/image")]
public IActionResult GetImage(int id) {
var url = _storage.GetSignedUrl($"images/{id}.jpg");
return Redirect(url);
}
43. 🟡 Model Binding với custom complex type?
// Custom model binder
public class DateRangeModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext ctx) {
var from = ctx.ValueProvider.GetValue("from").FirstValue;
var to = ctx.ValueProvider.GetValue("to").FirstValue;
if (DateTime.TryParse(from, out var f) && DateTime.TryParse(to, out var t)) {
ctx.Result = ModelBindingResult.Success(new DateRange(f, t));
}
return Task.CompletedTask;
}
}
// Register
[ModelBinder(typeof(DateRangeModelBinder))]
public class DateRange { ... }
// Action
[HttpGet("report")]
public IActionResult Report(DateRange range) { } // ?from=2026-01-01&to=2026-12-31
44. ⭐🔵 Paging, Sorting, Filtering pattern chuẩn?
public class QueryParams {
public int Page { get; set; } = 1;
public int Size { get; set; } = 20;
public string? SortBy { get; set; }
public bool SortDesc { get; set; }
public string? Search { get; set; }
}
[HttpGet]
public async Task<ActionResult<PagedResult<UserDto>>> GetAll([FromQuery] QueryParams q) {
var query = _db.Users.AsNoTracking();
if (!string.IsNullOrEmpty(q.Search))
query = query.Where(u => u.Name.Contains(q.Search));
var total = await query.CountAsync();
var items = await query
.OrderBy(u => u.Name)
.Skip((q.Page - 1) * q.Size)
.Take(q.Size)
.Select(u => new UserDto { ... })
.ToListAsync();
return Ok(new PagedResult<UserDto>(items, total, q.Page, q.Size));
}
45. ⭐🟡 Global exception handling pattern chuẩn?
// Middleware approach (recommend)
public class GlobalExceptionMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger) {
_next = next; _logger = logger;
}
public async Task InvokeAsync(HttpContext ctx) {
try {
await _next(ctx);
}
catch (NotFoundException ex) {
ctx.Response.StatusCode = 404;
await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
}
catch (ValidationException ex) {
ctx.Response.StatusCode = 422;
await ctx.Response.WriteAsJsonAsync(new { errors = ex.Errors });
}
catch (Exception ex) {
_logger.LogError(ex, "Unhandled exception");
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsJsonAsync(new { error = "Internal server error" });
}
}
}
M. So sánh và khác biệt version
46. ⭐ .NET 5 → 6 → 7 → 8 → 9 — thay đổi đáng chú ý?
| Version | Điểm nổi bật |
|---|---|
| .NET 5 (2020) | Hợp nhất .NET Framework và Core thành .NET |
| .NET 6 LTS (2021) | Minimal API, Hot Reload, DateOnly/TimeOnly, file-scoped namespace |
| .NET 7 (2022) | Rate Limiting built-in, Output Caching, Route Groups, Problem Details |
| .NET 8 LTS (2023) | Blazor fullstack, Keyed DI, TimeProvider, frozen collections |
| .NET 9 (2024) | HybridCache, OpenAPI built-in (no Swashbuckle), LINQ CountBy/AggregateBy, Span<T> LINQ |
| .NET 10 LTS (2025) | Better trimming, C# 14, EF Core 10, OpenAPI 3.1 |
47. 🔵 Startup.cs (cũ) vs Program.cs (mới .NET 6+)?
// Cũ — .NET 5 trở về
// Startup.cs
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
// Mới — .NET 6+, minimal hosting
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Không còn Startup.cs. WebApplicationBuilder kết hợp cả IHostBuilder + IWebHostBuilder + IServiceCollection.
48. 🔵 Keyed DI Services (.NET 8)?
// Đăng ký nhiều implementation cùng interface, phân biệt bằng key
builder.Services.AddKeyedSingleton<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedSingleton<IPaymentGateway, VnPayGateway>("vnpay");
// Inject
public class CheckoutService {
public CheckoutService([FromKeyedServices("stripe")] IPaymentGateway gateway) { }
}
// Hoặc resolve runtime
var gateway = app.Services.GetRequiredKeyedService<IPaymentGateway>("vnpay");
49. 🔵 IExceptionHandler (NET 8 thay vì middleware custom)?
public class GlobalExceptionHandler : IExceptionHandler {
public async ValueTask<bool> TryHandleAsync(
HttpContext ctx, Exception ex, CancellationToken ct) {
var problem = new ProblemDetails {
Status = ex is NotFoundException ? 404 : 500,
Title = ex.Message,
Instance = ctx.Request.Path
};
ctx.Response.StatusCode = problem.Status!.Value;
await ctx.Response.WriteAsJsonAsync(problem, ct);
return true; // true = đã handle, false = next handler
}
}
// Register
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();
N. Câu hỏi thực tế hay hỏi
50. ⭐ Bạn debug performance issue trên ASP.NET Core API ra sao?
- Đo trước tiên: dùng
dotnet-trace, Application Insights hoặc MiniProfiler. Đừng đoán. - Check DB: LINQ generated SQL có N+1 không? Missing index?
- Check async: có
Task.Result/.Wait()chặn thread không? - Check allocations: dùng dotnet-counters, JetBrains dotMemory.
- Output Caching: có thể cache response của endpoint không?
- Response size: có đang trả
SELECT *về cả triệu field?
51. ⭐ Token hết hạn trong lúc user đang dùng — xử lý ra sao?
Refresh token pattern:
- JWT access token — thời gian ngắn (15m–1h).
- Refresh token — lưu DB, thời gian dài (7–30 ngày).
- Client detect 401 → tự call
/auth/refreshvới refresh token. - Server verify refresh token còn hợp lệ → cấp JWT mới.
- Nếu refresh token expired → force logout.
52. ⭐ Làm sao test controller mà không cần chạy full server?
// Unit test controller trực tiếp — mock service
public class UsersControllerTests {
[Fact]
public async Task GetById_Returns404_WhenNotFound() {
var mockService = new Mock<IUserService>();
mockService.Setup(s => s.GetByIdAsync(999)).ReturnsAsync((UserDto?)null);
var controller = new UsersController(mockService.Object);
var result = await controller.Get(999);
Assert.IsType<NotFoundResult>(result.Result);
}
}
// Integration test — WebApplicationFactory
public class ApiTests : IClassFixture<WebApplicationFactory<Program>> {
private readonly HttpClient _client;
public ApiTests(WebApplicationFactory<Program> factory) {
_client = factory.CreateClient();
}
[Fact]
public async Task GetUsers_Returns200() {
var response = await _client.GetAsync("/api/users");
response.EnsureSuccessStatusCode();
}
}
Tham khảo
- codewithmukesh — 45 Web API Questions Actually Asked 2026
- bool.dev — ASP.NET Core Interview 2026
- Dot Net Tutorials — Web API Interview Q&A
- Microsoft Learn — MVC 5 Lifecycle
- Microsoft Learn — APIs Overview ASP.NET Core 9
- Hirist Blog — Top 25 Web API Questions
- KungFuTech VN — ASP.NET MVC Interview
- TopDev VN — Top 5 câu hỏi ASP.NET
- PhongvanIT — ASP.NET MVC Q&A