Unit 4: Asp.Net MVC and Security

INT402 — Modern Web Programming Tools And Techniques 8 min read

I. Foundations of ASP.NET Core MVC

ASP.NET Core MVC is a cross-platform framework for building web applications and APIs using the Model–View–Controller pattern. It combines an HTTP request pipeline, middleware, dependency injection, Entity Framework Core data access, routing, model binding, authentication, and authorization.

  • Model–View–Controller convention:
    • Model: Represents application data and business rules, such as a Student entity.
    • View: Renders the user interface, usually through Razor .cshtml files.
    • Controller: Receives HTTP requests and coordinates models, services, and views.
  • Request-based operation: A client sends an HTTP request; ASP.NET Core processes it through middleware before an endpoint generates an HTTP response.
  • Configuration model: Application services and the request pipeline are normally configured in Program.cs.
  • Asynchronous execution: Framework operations commonly use async and await to avoid blocking threads during database or network access.
  • Security principle: Applications must establish who the user is through authentication and determine permitted actions through authorization.
  • Data-access convention: Entity Framework Core maps .NET classes to relational database tables and supports both Code First and Database First development.

II. ASP.NET Core Request Processing — The Middleware Pipeline

A. Middleware Request Pipeline

The middleware request pipeline is an ordered sequence of components that processes every HTTP request and response.

  • Middleware component: A middleware component receives an HttpContext and may perform work before and after invoking the next component.
  • Pipeline direction:
    • Requests travel through middleware in registration order.
    • Responses travel back through previously invoked middleware in reverse order.
  • Request delegate: RequestDelegate represents the next pipeline operation and accepts an HttpContext.
  • Continue or terminate:
    1. Use can invoke next() and continue processing.
    2. Run usually terminates the pipeline by generating the response directly.
  • Branching: Map and MapWhen create separate pipelines for matching paths or conditions.
  • Order dependence: Exception handling should generally appear early, while authentication must execute before authorization.
CSHARP
var app = builder.Build();

app.UseExceptionHandler("/Home/Error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
  • Concrete flow: A request for /Products/Details/5 passes through exception handling, HTTPS redirection, static-file checking, routing, authentication, and authorization before reaching ProductsController.Details(5).
  • Endpoint execution: MapControllerRoute maps controller endpoints; app.Run() starts the application and completes pipeline configuration.

III. Middleware Components — Framework and User-Defined Processing

A. Built-In Middleware and Custom Middleware

Built-in middleware provides standard framework behavior, while custom middleware implements application-specific request processing.

  1. Built-in middleware

    • Static files: UseStaticFiles() serves files from wwwroot, such as /css/site.css.
    • Routing: UseRouting() identifies the endpoint matching the request URL.
    • Security: UseAuthentication() establishes identity, and UseAuthorization() enforces access rules.
    • Error handling: UseExceptionHandler() produces controlled error responses in production.
    • HTTPS enforcement: UseHttpsRedirection() redirects HTTP requests to HTTPS.
    • Session support: UseSession() enables server-side session state after session services are registered.
  2. Custom middleware

    • Purpose: It handles concerns such as request logging, correlation IDs, timing, custom headers, or tenant detection.
    • Structure: A conventional middleware class has a constructor accepting RequestDelegate and an InvokeAsync method accepting HttpContext.
CSHARP
public class TimingMiddleware
{
    private readonly RequestDelegate _next;

    public TimingMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;
        await _next(context);
        var elapsed = DateTime.UtcNow - start;
        context.Response.Headers["X-Time-Ms"] =
            elapsed.TotalMilliseconds.ToString("F0");
    }
}
CSHARP
app.UseMiddleware<TimingMiddleware>();
  • Short-circuiting: A custom component can omit await _next(context) and return a response immediately, for example when an API key is absent.
  • Constraint: Response headers must normally be set before the response body begins.

IV. Dependency Management — Inversion of Control

A. Dependency Injection (DI)

Dependency Injection supplies an object’s dependencies externally instead of requiring the object to construct them itself.

  • Dependency example: ProductsController may depend on IProductService rather than directly creating ProductService.
  • Inversion of control: Object creation is transferred to a container, reducing coupling between consumers and implementations.
  • Constructor injection: Required dependencies are declared as constructor parameters, making them explicit and testable.
CSHARP
public class ProductsController : Controller
{
    private readonly IProductService _service;

    public ProductsController(IProductService service)
    {
        _service = service;
    }
}
  • Service abstraction: An interface such as IProductService permits replacement with another implementation or a test double.
  • Service lifetimes:
    • Transient: A new instance is created whenever requested.
    • Scoped: One instance is used within an HTTP request; DbContext normally uses this lifetime.
    • Singleton: One instance exists for the application lifetime.
  • Lifetime safety: A singleton must not directly depend on a scoped service because the scoped object could outlive its valid request scope.
  • Benefits: DI improves modularity, unit testing, maintainability, and centralized lifecycle management.

V. Service Registration — Using the ASP.NET Core Container

A. Implementing DI in ASP.NET Core

ASP.NET Core implements DI through the service collection configured during application startup and the service provider used at runtime.

  • Service registration: Services are added to builder.Services before builder.Build().
CSHARP
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddSingleton<ISystemClock, SystemClock>();

var app = builder.Build();
  • Resolution: When MVC creates ProductsController, the container identifies its constructor parameters and resolves the registered implementations.
  • Framework services: Calls such as AddControllersWithViews(), AddDbContext<T>(), and AddAuthentication() register groups of related services.
  • Multiple dependencies: A controller can request services such as ILogger<ProductsController>, AppDbContext, and IProductService through its constructor.
  • Configuration injection: The options pattern provides strongly typed settings.
CSHARP
builder.Services.Configure<MailSettings>(
    builder.Configuration.GetSection("MailSettings"));
  • Preferred practice: Depend on narrow interfaces rather than retrieving services through HttpContext.RequestServices.
  • Limitation: Registering an implementation without its required dependencies causes a runtime service-resolution exception.

VI. Entity Framework Core — Model-Driven Database Development

A. Developing Application with Code First Approach

Code First begins with C# entity classes and uses migrations to create and evolve the database schema.

  • Entity definition: A class represents a table, while properties generally represent columns.
CSHARP
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
}
  • Database context: DbContext coordinates entity mapping, querying, change tracking, and persistence.
CSHARP
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<Product> Products => Set<Product>();
}
  • Registration:
CSHARP
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));
  • Migrations workflow:
    1. dotnet ef migrations add InitialCreate generates migration operations.
    2. dotnet ef database update applies them to the configured database.
  • CRUD operation: context.Products.Add(product) marks an entity as added; await context.SaveChangesAsync() issues the corresponding INSERT.
  • Mapping control: Data annotations such as [Required] and Fluent API calls in OnModelCreating define constraints and relationships.
  • Advantages: The model remains source-controlled, schema changes are repeatable, and development can begin before a database exists.
  • Limitation: Careless migration changes may cause data loss, so production migrations require review and backup planning.

VII. Entity Framework Core — Existing-Database Development

A. Developing Application with Database First Approach

Database First begins with an existing database and reverse-engineers entity classes and a DbContext from its schema.

  • Scaffolding command: EF Core tools inspect tables, primary keys, foreign keys, column types, and relationships.
BASH
dotnet ef dbcontext scaffold \
"Server=.;Database=ShopDb;Trusted_Connection=True;TrustServerCertificate=True" \
Microsoft.EntityFrameworkCore.SqlServer \
--context ShopDbContext --output-dir Models
  • Generated output: A Products table may produce a Product entity and a DbSet<Product> property in ShopDbContext.
  • Required packages: The project typically needs the database provider, Microsoft.EntityFrameworkCore.Design, and the dotnet-ef tool.
  • Schema updates: When the database changes, models may be re-scaffolded; custom code should be placed in partial classes or separate files to avoid overwriting.
  • Connection security: Connection strings should be stored in configuration, environment variables, or secret storage rather than committed in source code.
  • Advantages: The approach suits legacy databases, DBA-controlled schemas, and applications integrating with established data.
  • Limitations: Re-scaffolding can overwrite generated code, and unsupported database objects may require manual mapping.
  • Explicit contrast:
    1. Code First: C# model changes drive database migrations.
    2. Database First: Existing database schema drives generated C# models.

VIII. Application Security — Identity and Access Control

A. Authentication and Authorization in Web Application

Authentication verifies a user’s identity, whereas authorization decides which resources that authenticated identity may access.

  1. Authentication
    • Identity establishment: A successful sign-in creates a ClaimsPrincipal containing claims such as name, role, or user identifier.
    • Cookie authentication: Common in MVC applications; the browser sends an encrypted authentication cookie with later requests.
    • Bearer authentication: APIs commonly accept a JWT bearer token in the Authorization header.
    • Configuration:
CSHARP
builder.Services.AddAuthentication("Cookies")
    .AddCookie("Cookies", options =>
        options.LoginPath = "/Account/Login");
  1. Authorization
    • Controller protection: [Authorize] requires an authenticated user, while [AllowAnonymous] permits public access.
    • Role-based rule: [Authorize(Roles = "Admin")] restricts an action to an identity carrying the Admin role.
    • Policy-based rule:
CSHARP
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdultOnly",
        policy => policy.RequireClaim("Age", "18"));
});
CSHARP
[Authorize(Policy = "AdultOnly")]
public IActionResult Restricted() => View();
  • Pipeline requirement: UseAuthentication() must appear before UseAuthorization().
  • Defense in depth: Authorization must be enforced on server endpoints, not merely by hiding buttons in a view.
  • Supporting protections: HTTPS protects credentials in transit; anti-forgery tokens reduce CSRF risk in cookie-based forms; secure password hashing protects stored credentials.
  • Access outcomes: An unauthenticated user is typically challenged to sign in, whereas an authenticated user lacking permission receives a forbidden response.