ASP.NET CORE MVC - Topic-wise Practice

15 hard problems. Predict output, spot bugs, write code. This is what FAANG asks in rounds.

Rules: Try before expanding solutions. Type the code. Production bugs don't give you hints.

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
Output: 401 Unauthorized
Why: 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
Bug: UseRouting must be before UseAuthorization. Authorization needs GetEndpoint() set by Routing.
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
Output: "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
Bug: Binding fails: "abc" โ†’ decimal. Sets ModelState error + vm.Price = default(decimal) = 0. You didn't check ModelState.IsValid, so you saved 0 to DB.
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
Output: <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
Bug: If id not found, user = null. View gets null Model. @Model.Name = null.Name โ†’ NullReferenceException at runtime. Compiles fine.
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
Output: CtorCtorCtorCtor - 4 times
Why: 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
Bug: Captive Dependency. Singleton UserService captures first request's DbContext. DbContext.ChangeTracker caches entities. Request #2 reuses same DbContext instance with Request #1 data cached.
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
Output: 403 Forbidden. "Hi" never prints.
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
Bug: Sync filter with Thread.Sleep blocks threadpool thread for 5s. At 100 req/s, you exhaust threadpool. CPU 0%, app frozen.
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>());
Done? If you solved 12/15 without peeking, you can debug production MVC. If you failed P2, P11, P14 - you'd crash prod. Study those.
Next: Deep Dive - Pipeline internals, source code walkthrough. Staff level only.

Comments on ASP.NET CORE MVC - Topic-wise Practice (0)

No comments yet. Be the first to share your thoughts!