Unit 5: ASP.NET Core MVC, Entity Framework Core and Security

CSE253 — .Net Programming 8 min read

I. Orientation — The ASP.NET Core Web Application Model

ASP.NET Core is a cross-platform framework for building web applications and APIs. Its design combines an HTTP request pipeline, built-in dependency injection, MVC separation of concerns, and Entity Framework Core for database access. Security is applied through authenticated identities and authorization policies before protected resources are executed.

  • Request-driven execution: A client sends an HTTP request; middleware processes it and eventually produces an HTTP response.
  • Separation of responsibilities: Controllers handle application requests, models represent data and rules, and views render HTML.
  • Inversion of control: Application classes receive dependencies through constructors instead of creating them directly.
  • ORM-based persistence: Entity Framework Core maps .NET objects to relational database tables.
  • Explicit security decisions: Authentication identifies a user; authorization determines whether that user may perform an operation.
  • Configuration by environment: Development, testing, and production can use different connection strings, middleware, and logging settings.

II. Middleware Request Pipeline — Ordered HTTP Processing

Middleware components are software units arranged in sequence. Each component can inspect or modify an HTTP request, call the next component, and inspect or modify the response on the way back.

A. Middleware Request Pipeline

The middleware request pipeline determines how every request travels through an ASP.NET Core application.

  • Sequential execution: Middleware is registered in Program.cs and runs in registration order.
  • Request and response stages: A component executes code before next(), transfers control through next(), and can execute response logic afterward.
  • Short-circuiting: A middleware component may create a response without calling the next component, such as returning 401 Unauthorized.
  • Order dependency: UseRouting() must occur before endpoint authorization, while UseAuthentication() must precede UseAuthorization().
  • Terminal execution: MapGet, MapControllers, or endpoint middleware can finish the pipeline.
CSHARP
app.Use(async (context, next) =>
{
    Console.WriteLine("Before endpoint");
    await next();
    Console.WriteLine("After endpoint");
});

app.MapGet("/", () => "Hello");

Here, context is the current HttpContext, and next represents the remaining pipeline.

B. Built-In Middleware and Custom Middleware

Built-in middleware supplies common web infrastructure, while custom middleware implements application-specific cross-cutting behavior.

  • Exception handling: app.UseExceptionHandler("/Error") provides centralized production error handling.
  • HTTPS enforcement: app.UseHttpsRedirection() redirects HTTP requests to HTTPS.
  • Static files: app.UseStaticFiles() serves files from the wwwroot directory.
  • Routing: app.UseRouting() matches URLs to endpoints.
  • Authentication and authorization: These middleware components establish identity and enforce access rules.
  • Custom middleware class: A class conventionally contains a RequestDelegate and an InvokeAsync method.
CSHARP
public sealed class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;
        await _next(context);
        Console.WriteLine(DateTime.UtcNow - start);
    }
}

Register it with app.UseMiddleware<RequestTimingMiddleware>(). Middleware is useful for logging, correlation IDs, exception handling, and request validation; it should not contain controller-specific business logic.

III. Dependency Injection in ASP.NET Core — Supplying Application Services

Dependency injection (DI) is a technique in which a class receives required services from an external container. ASP.NET Core includes a built-in service container that constructs objects and manages their lifetimes.

A. Dependency Injection in ASP.NET Core

DI reduces coupling by allowing classes to depend on interfaces or abstractions rather than concrete implementations.

  • Constructor injection: Dependencies are declared in a constructor, making required services explicit.
  • Service registration: AddTransient, AddScoped, and AddSingleton register services with different lifetimes.
  • Transient lifetime: A new instance is created each time the service is requested; suitable for lightweight stateless services.
  • Scoped lifetime: One instance is created per HTTP request; DbContext is normally scoped.
  • Singleton lifetime: One instance exists for the application lifetime; it must be thread-safe and must not depend on scoped services.
  • Controller activation: MVC obtains controller dependencies from the service container automatically.
CSHARP
builder.Services.AddScoped<IOrderService, OrderService>();

public class OrdersController : Controller
{
    private readonly IOrderService _orders;

    public OrdersController(IOrderService orders) => _orders = orders;
}

The interface IOrderService defines the dependency, and OrderService is the registered implementation. Incorrect lifetimes can cause stale data, memory retention, or attempts to use scoped objects from singletons.

IV. Entity Framework Core — Object-Relational Data Access

Entity Framework Core (EF Core) is a .NET object-relational mapper (ORM). It enables applications to query and update relational databases using C# objects and LINQ while still supporting SQL database features.

A. Introduction to Entity Framework Core

EF Core maps entity classes and relationships to database structures and translates LINQ expressions into provider-specific SQL.

  • Entity: A class such as Product represents a row concept in a database table.
  • Property mapping: A property such as Name normally maps to a column named Name.
  • LINQ translation: context.Products.Where(p => p.Price > 100) is translated into a database query.
  • Change tracking: EF Core detects added, modified, and deleted entities before SaveChangesAsync().
  • Provider model: Packages such as Microsoft.EntityFrameworkCore.SqlServer connect EF Core to a specific database engine.
  • Deferred execution: An IQueryable query generally executes when enumerated, for example with ToListAsync().

B. DbContext and Entity Configuration

DbContext represents a session with the database and coordinates querying, tracking, configuration, and persistence.

  • DbSet properties: A DbSet<Product> Products property exposes product records for querying.
  • Connection configuration: AddDbContext<AppDbContext uses a connection string stored in configuration.
  • Model configuration: OnModelCreating defines keys, lengths, precision, relationships, and constraints.
  • Fluent API priority: Fluent configuration is centralized and can override conventions and data annotations.
  • Relationship mapping: A foreign key such as CategoryId connects a Product to a Category.
CSHARP
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasKey(p => p.Id);

    modelBuilder.Entity<Product>()
        .Property(p => p.Price)
        .HasPrecision(18, 2)
        .IsRequired();
}

Here, Id is the primary key and Price is stored with 18 total digits and 2 decimal places.

C. Code First Approach

The Code First Approach begins with C# entity classes and generates or updates the database schema from the model.

  • Model as source: Developers define classes, keys, navigation properties, and constraints in code.
  • Schema generation: EF Core migrations convert model changes into database operations.
  • Advantages: Code First supports version control, automated deployment, and domain-driven modeling.
  • Relationship example: public int CategoryId { get; set; } and public Category Category { get; set; } express a product-category relationship.
  • Best use: It is appropriate when the application owns a new database or the schema changes frequently.

D. Database First Approach

The Database First Approach begins with an existing database and generates entity classes and a DbContext from its schema.

  • Reverse engineering: dotnet ef dbcontext scaffold reads tables, keys, and relationships.
  • Existing schema: It is useful when a database already belongs to an organization or legacy system.
  • Generated artifacts: Scaffolded classes describe database tables; partial classes can preserve custom code across regeneration.
  • Tradeoff: Database changes must be reflected by repeating or managing scaffolding carefully.
  • Best use: It suits applications integrating with a stable, pre-existing relational database.

E. Database Migrations

Database migrations provide versioned, repeatable transformations between EF Core model states and database schemas.

  • Create migration: dotnet ef migrations add InitialCreate records the initial model.
  • Apply migration: dotnet ef database update executes pending migrations against the configured database.
  • Migration contents: A migration commonly contains Up, which applies changes, and Down, which reverses them.
  • Schema history: EF Core stores applied migration names in a history table.
  • Deployment discipline: Review generated SQL and apply migrations through controlled deployment processes rather than relying blindly on application startup.
  • Concrete change: Adding Description to Product may generate an AddColumn operation.

F. CRUD Operations using Entity Framework Core

CRUD means Create, Read, Update, and Delete; EF Core implements these operations through a DbContext.

  • Create: Add an object to a DbSet, then persist it.
  • Read: Use LINQ and asynchronous methods such as SingleOrDefaultAsync.
  • Update: Load a tracked entity, change its properties, and call SaveChangesAsync.
  • Delete: Remove the entity and save the resulting deletion.
  • Validation and concurrency: Validate input before saving and handle missing rows or concurrency conflicts.
CSHARP
_context.Products.Add(product);                 // Create
var item = await _context.Products.FindAsync(id); // Read
item.Price = 29.99m;                            // Update
_context.Products.Remove(item);                 // Delete
await _context.SaveChangesAsync();

In practice, each operation is normally performed in a separate request, with null checks and authorization before modifying records.

V. Authentication and Authorization in Web Applications — Protecting Resources

Web application security separates identity verification from permission checking. Authentication establishes who the requester is; authorization evaluates whether the identified principal can access a resource.

A. Authentication and Authorization in Web Applications

Authentication and authorization should be configured as coordinated but distinct stages.

  • Authentication: A login process validates credentials and creates a cookie or token representing the user.
  • Authorization: ASP.NET Core evaluates [Authorize], roles, claims, or policies after authentication.
  • Anonymous access: [AllowAnonymous] permits selected actions, such as a login action, to bypass authorization.
  • Cookie authentication: Browser applications commonly use an encrypted authentication cookie maintained by ASP.NET Core Identity.
  • Bearer authentication: APIs commonly receive a JWT in the Authorization: Bearer <token> header.
  • Claims and roles: A claim such as Department=Finance supplies data for policy decisions; a role such as Admin groups permissions.
  • Policy-based authorization: Policies express requirements more flexibly than role checks.
CSHARP
[Authorize(Policy = "CanEditProducts")]
public async Task<IActionResult> Edit(int id)
{
    // Only an authenticated principal satisfying the policy reaches this action.
}

A typical configuration calls app.UseAuthentication() before app.UseAuthorization(), followed by endpoint mapping. Passwords should never be stored in plain text; ASP.NET Core Identity uses password hashing, account management, and security-token support. HTTPS, antiforgery protection for cookie-based form posts, input validation, and least-privilege policies reduce common risks such as credential theft, cross-site request forgery, and unauthorized data changes.