ASP.NET CORE MVC - Topic-wise Practice
15 hard problems. Predict output, spot bugs, write code. This is what FAANG asks in rounds.
1. Routing & Middleware - 3 Problems
P1. Predict Output: What HTTP status? Why?
// Program.cs
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/admin", [Authorize] () => "Secret");
// Request: GET /admin without login
Show Solution
401 UnauthorizedWhy: UseRouting matches /admin to endpoint with [Authorize] metadata. UseAuthorization reads metadata, checks HttpContext.User.Identity.IsAuthenticated = false. Returns 401. If Auth was before Routing, GetEndpoint() = null, [Authorize] ignored = 200 bug.
P2. Spot the Bug: All requests return 404. Why?
// Program.cs
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting(); // Sanju's order
app.MapControllers();
Show Solution
Production Impact: Auth runs, GetEndpoint() = null, so all endpoints look unauthorized or unmatchable. Entire app 404s.
Fix: UseRouting(); UseAuthentication(); UseAuthorization(); MapControllers();
P3. Write Code: Middleware that logs request path + time taken. Must not break pipeline.
Show Solution
public class RequestLogMiddleware {
private readonly RequestDelegate _next;
private readonly ILogger<RequestLogMiddleware> _log;
public RequestLogMiddleware(RequestDelegate next, ILogger<RequestLogMiddleware> log) {
_next = next; _log = log;
}
public async Task InvokeAsync(HttpContext ctx) {
var sw = Stopwatch.StartNew();
try {
await _next(ctx); // Must call next
} finally {
sw.Stop();
_log.LogInformation("{Path} took {Ms}ms", ctx.Request.Path, sw.ElapsedMilliseconds);
}
}
}
// Program.cs: app.UseMiddleware<RequestLogMiddleware>();
2. Model Binding & Validation - 3 Problems
P4. Predict Output: What is vm.Name?
// Action
public IActionResult Create(UserVM vm) => Json(vm.Name);
// POST /users
// Body: name=John&name=Kunal
Show Solution
"John,Kunal"Why: FormValueProvider gets multiple values for same key. Default binder for string uses comma-join. If property was List<string> Name, you'd get ["John","Kunal"]. This breaks SQL if you insert directly.
P5. Spot the Bug: User posts Price="abc". Why does DB save 0?
[HttpPost] public IActionResult Create(ProductVM vm) {
// ModelState.IsValid = false here, but we forgot to check
_db.Products.Add(new Product { Price = vm.Price });
_db.SaveChanges(); // Prerna's bug
return RedirectToAction("Index");
}
Show Solution
Fix:
if (!ModelState.IsValid) return View(vm); Return form with "abc is not valid" error. Never trust bound model without validation.
P6. Write Code: Custom validation attribute [NotFutureDate] for DateTime.
Show Solution
public class NotFutureDateAttribute : ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext ctx) {
if (value is DateTime date && date > DateTime.Now) {
return new ValidationResult(ErrorMessage?? "Date cannot be in future");
}
return ValidationResult.Success;
}
}
// Usage: [NotFutureDate] public DateTime BirthDate {get;set;}
3. Razor & Tag Helpers - 3 Problems
P7. Predict Output: Browser View Source shows what?
@model UserVM
<input asp-for="Email" />
// No @addTagHelper in view or _ViewImports.cshtml
Show Solution
<input asp-for="Email" />Why: Tag Helpers are opt-in. Without @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers, Razor treats asp-for as literal HTML attribute. No C# runs. Browser gets asp-for literally. Form posts Email=null.
P8. Spot the Bug: NullReferenceException on production. Why?
// Action
public IActionResult Details(int id) {
var user = _db.Users.Find(id);
return View(user); // user might be null
}
// View
@model User
<h1>@Model.Name</h1> // Kunal's view
Show Solution
Fix 1:
if (user == null) return NotFound(); in action.Fix 2: View:
@if(Model!= null) { <h1>@Model.Name</h1> }
P9. Write Code: Tag Helper <email>kunal@test.com</email> that renders <a href="mailto:kunal@test.com">kunal@test.com</a>
Show Solution
[HtmlTargetElement("email")]
public class EmailTagHelper : TagHelper {
public override async Task ProcessAsync(TagHelperContext ctx, TagHelperOutput output) {
var content = await output.GetChildContentAsync();
var email = content.GetContent();
output.TagName = "a";
output.Attributes.SetAttribute("href", $"mailto:{email}");
output.Content.SetContent(email);
}
}
// _ViewImports.cshtml: @addTagHelper *, YourAssembly
4. DI & Lifetimes - 3 Problems
P10. Predict Output: How many times does "Ctor" print?
builder.Services.AddTransient<IService, MyService>();
public class MyService { public MyService() => Console.Write("Ctor"); }
public class HomeController {
public HomeController(IService s1, IService s2) {}
}
// GET / requests HomeController twice
Show Solution
CtorCtorCtorCtor - 4 timesWhy: Transient = new every injection. 2 params ร 2 requests = 4 instances. If Scoped: 2 times. If Singleton: 1 time.
P11. Spot the Bug: Random data leaks. User A sees User B data. Why?
builder.Services.AddSingleton<IUserService, UserService>();
public class UserService : IUserService {
private readonly AppDbContext _db;
public UserService(AppDbContext db) => _db = db; // Kaushal's service
}
Show Solution
Fix: Make UserService Scoped, or inject IServiceScopeFactory and create scope per method call.
P12. Write Code: BackgroundService that runs every 5min, needs DbContext. Handle lifetime correctly.
Show Solution
public class CleanupService : BackgroundService {
private readonly IServiceScopeFactory _scopeFactory;
public CleanupService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
protected override async Task ExecuteAsync(CancellationToken ct) {
while (!ct.IsCancellationRequested) {
using var scope = _scopeFactory.CreateScope(); // New scope per run
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Users.Where(u => u.LastLogin < DateTime.UtcNow.AddYears(-1)).ExecuteDeleteAsync(ct);
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}
// Program.cs: builder.Services.AddHostedService<CleanupService>();
5. Filters & Security - 3 Problems
P13. Predict Output: Action runs or not?
public class BlockFilter : IAsyncActionFilter {
public async Task OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next) {
ctx.Result = new StatusCodeResult(403); // Don't call next()
}
}
[ServiceFilter(typeof(BlockFilter))]
public IActionResult Index() => Content("Hi"); // Will "Hi" print?
Show Solution
Why: Filter sets context.Result and never calls next(). Pipeline short-circuits. Action never executes. This is how [Authorize] blocks access.
P14. Spot the Bug: Thread pool starvation. Why?
public class SlowFilter : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext ctx) {
Thread.Sleep(5000); // Sanju's logging filter
}
}
Show Solution
Fix: Use IAsyncActionFilter:
public async Task OnActionExecutionAsync(...) and await Task.Delay(5000); Frees thread while waiting.
P15. Write Code: Resource Filter that adds "X-Response-Time" header with action execution time.
Show Solution
public class TimingFilter : IAsyncResourceFilter {
public async Task OnResourceExecutionAsync(ResourceExecutingContext ctx, ResourceExecutionDelegate next) {
var sw = Stopwatch.StartNew();
var executed = await next(); // Runs rest of pipeline
sw.Stop();
executed.HttpContext.Response.Headers["X-Response-Time"] = $"{sw.ElapsedMilliseconds}ms";
}
}
// Program.cs: builder.Services.AddControllers(opt => opt.Filters.Add<TimingFilter>());
No comments yet. Be the first to share your thoughts!