.NET ProgrammingUnit 811 min read

ASP.NET Core: MVC, Razor Pages, Middleware & Deployment

Unit 8 of .NET Programming covers ASP.NET Core fundamentals—MVC architecture, Razor Pages, middleware pipeline design, dependency injection, and deployment strategies—with hands-on examples, real-world comparisons, and exam-focused visuals.

Core Concepts

What is ASP.NET Core?

ASP.NET Core is a cross-platform, high-performance framework for building modern web applications and APIs. It replaces the older ASP.NET (WebForms/MVC) and is optimized for cloud deployment, microservices, and containerization.

classDiagram
    class ASPNETCore {
        + Cross-platform (Windows/Linux/macOS)
        + Modular (choose only needed components)
        + High performance (Kestrel web server)
        + Dependency Injection built-in
        + Supports MVC, Razor Pages, Blazor, Web APIs
    }
    ASPNETCore --> "Uses" Kestrel
    ASPNETCore --> "Integrates" EntityFrameworkCore
    ASPNETCore --> "Deploys to" Docker

Key Features:

  • Cross-platform: Runs on Windows, Linux, and macOS.
  • Modular: Only include what you need (e.g., no WebForms bloat).
  • Performance: Uses Kestrel (a lightweight web server) by default.
  • Dependency Injection (DI): Built-in support for managing object lifetimes.
  • Razor Pages: Simplified page-focused development (replaces MVC for simpler apps).
  • Middleware Pipeline: Request processing as a chain of components.

1. MVC Architecture in ASP.NET Core

MVC (Model-View-Controller) separates concerns into three layers:

  • Model: Business logic/data (e.g., User class).
  • View: UI (Razor .cshtml files).
  • Controller: Handles HTTP requests and returns responses.

How MVC Works

  1. Request: User navigates to /Home/Index.
  2. Routing: Matches URL to HomeController.Index().
  3. Controller: Fetches data (Model) and passes it to a View.
  4. View: Renders HTML (e.g., Index.cshtml).
  5. Response: Sent to the client.
sequenceDiagram
    User->>+Router: GET /Home/Index
    Router->>HomeController: Index()
    HomeController->>+UserModel: GetUsers()
    UserModel-->>-HomeController: List<User>
    HomeController->>IndexView: Render(List<User>)
    IndexView-->>-User: HTML

Example: Simple MVC App

// Model
public class Product {
    public int Id { get; set; }
    public string Name { get; set; }
}

// Controller
public class ProductController : Controller {
    public IActionResult Index() {
        var products = new List<Product> {
            new Product { Id = 1, Name = "Laptop" },
            new Product { Id = 2, Name = "Phone" }
        };
        return View(products); // Renders Index.cshtml
    }
}

// View (Index.cshtml)
@model List<Product>
<h1>Products</h1>
<ul>
    @foreach (var p in Model) {
        <li>@p.Name</li>
    }
</ul>

State After Rendering:

HTML Output:
<h1>Products</h1>
<ul>
    <li>Laptop</li>
    <li>Phone</li>
</ul>

2. Razor Pages

Razor Pages simplify MVC for page-focused apps (e.g., admin dashboards). Each .cshtml file has:

  • Page Model: Logic (e.g., Index.cshtml.cs).
  • View: UI (e.g., Index.cshtml).

Comparison: MVC vs. Razor Pages

Feature MVC Razor Pages
Complexity Better for large apps Simpler for small/medium apps
File Structure Controllers/Views separate Logic + UI in same folder
Routing /Controller/Action /Page (e.g., /Products)
Use Case APIs, complex workflows CRUD apps, admin panels

Example: Razor Page for Products

// Pages/Products/Index.cshtml.cs
public class IndexModel : PageModel {
    public List<Product> Products { get; set; }
    public void OnGet() {
        Products = new List<Product> {
            new Product { Id = 1, Name = "Laptop" }
        };
    }
}

// Pages/Products/Index.cshtml
@page
@model IndexModel
<h1>Products</h1>
<ul>
    @foreach (var p in Model.Products) {
        <li>@p.Name</li>
    }
</ul>

State After Loading:

URL: /Products
Rendered HTML:
<h1>Products</h1>
<ul>
    <li>Laptop</li>
</ul>

3. Middleware Pipeline

Middleware processes HTTP requests/responses in a pipeline (like a chain of handlers). Each middleware can:

  • Modify the request/response.
  • Short-circuit the pipeline (e.g., return a 404).

Default Pipeline Order (from Program.cs):

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Middleware order matters!
app.UseHttpsRedirection();       // 1. Redirect HTTP → HTTPS
app.UseStaticFiles();             // 2. Serve static files (CSS/JS)
app.UseRouting();                 // 3. Determine endpoint (e.g., /Home/Index)
app.UseAuthorization();           // 4. Check permissions
app.MapControllerRoute(          // 5. Route to MVC controllers
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();                       // 6. Terminal middleware (500 if no match)

Visual: Middleware Execution

flowchart TD
    A["Request"] --> B["UseHttpsRedirection"]
    B --> C{"HTTPS?"}
    C -->|"No"| D["Redirect to HTTPS"]
    C -->|"Yes"| E["UseStaticFiles"]
    E --> F{"File exists?"}
    F -->|"Yes"| G["Return file"]
    F -->|"No"| H["UseRouting"]
    H --> I["UseAuthorization"]
    I --> J["MapControllerRoute"]
    J --> K["Run"]

Custom Middleware Example

// CustomMiddleware.cs
public class CustomMiddleware {
    private readonly RequestDelegate _next;
    public CustomMiddleware(RequestDelegate next) => _next = next;
    public async Task InvokeAsync(HttpContext context) {
        Console.WriteLine("Before middleware");
        await _next(context); // Pass to next middleware
        Console.WriteLine("After middleware");
    }
}

// Register in Program.cs
app.UseMiddleware<CustomMiddleware>();

State After Invocation:

Console Output:
Before middleware
(Controller logic executes...)
After middleware

4. Dependency Injection (DI)

ASP.NET Core has built-in DI for managing object lifetimes:

  • Transient: New instance per request (e.g., IEmailService).
  • Scoped: One instance per HTTP request (e.g., DbContext).
  • Singleton: One instance for the app lifetime (e.g., ILogger).

Example: DI in Action

// Service interface
public interface IProductService {
    List<Product> GetAll();
}

// Implementation
public class ProductService : IProductService {
    public List<Product> GetAll() => new List<Product> {
        new Product { Id = 1, Name = "Laptop" }
    };
}

// Register in Program.cs
builder.Services.AddScoped<IProductService, ProductService>();

// Inject into controller
public class HomeController : Controller {
    private readonly IProductService _service;
    public HomeController(IProductService service) => _service = service;
    public IActionResult Index() {
        var products = _service.GetAll();
        return View(products);
    }
}

Lifetime Comparison

Scope Lifetime Example Use Case
Transient New per injection Stateless services (e.g., IEmailService)
Scoped One per HTTP request DbContext, IHttpContextAccessor
Singleton One for the app ILogger, configuration settings

5. Deployment Strategies

Deploy ASP.NET Core apps to:

  1. IIS (Windows): Traditional hosting (uses IIS HTTP Server).
  2. Kestrel (Linux/Windows): Standalone (default in .NET Core).
  3. Docker: Containerized for scalability.
  4. Azure App Service: Managed PaaS.

Example: Dockerfile for ASP.NET Core

# Use official .NET SDK image
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

# Runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "YourApp.dll"]

Deployment Workflow:

flowchart LR
    A["Code Commit"] --> B["Build Docker Image"]
    B --> C["Push to Docker Hub"]
    C --> D["Deploy to Azure/AWS"]
    D --> E["Scale with Kubernetes"]

In the Real World

  1. eSewa (Nepal):

    • Idea Used: Middleware Pipeline for request validation.
    • How: eSewa’s backend uses middleware to check user authentication before processing payments (e.g., UseAuthentication() in ASP.NET Core). This ensures only logged-in users can initiate transactions, preventing fraud.
  2. Khalti (Nepal):

    • Idea Used: Razor Pages for admin dashboards.
    • How: Khalti’s admin panel (for merchants) uses Razor Pages to manage transactions, refunds, and user reports. The separation of logic (*.cshtml.cs) and UI (*.cshtml) keeps the codebase maintainable.
  3. Daraz (Global):

    • Idea Used: MVC Architecture for product listings.
    • How: Daraz’s product pages use MVC:
      • Model: Product class with Id, Name, Price.
      • Controller: ProductController fetches products from the database.
      • View: Index.cshtml renders the product grid.
    • Real Scenario: When you search for "laptops," Daraz’s backend routes the request to ProductController.Search(), which returns filtered products to the view.
  4. NTC Website (Nepal):

    • Idea Used: Dependency Injection for database access.
    • How: NTC’s website uses DbContext (scoped lifetime) to interact with its database of bus routes, schedules, and ticket bookings. DI ensures each HTTP request gets a fresh DbContext to avoid stale data.

Worked Example: Building a Simple Blog with ASP.NET Core

Scenario: Create a blog with posts (title, content) using MVC.

Step 1: Model

public class Post {
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Step 2: Controller

public class PostsController : Controller {
    private readonly List<Post> _posts = new() {
        new Post { Id = 1, Title = "First Post", Content = "Hello World!" }
    };
    public IActionResult Index() => View(_posts);
}

Step 3: View (Index.cshtml)

@model List<Post>
<h1>Blog Posts</h1>
@foreach (var post in Model) {
    <article>
        <h2>@post.Title</h2>
        <p>@post.Content</p>
    </article>
}

Step 4: Routing

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

State After Accessing /Posts:

Rendered HTML:
<h1>Blog Posts</h1>
<article>
    <h2>First Post</h2>
    <p>Hello World!</p>
</article>

Common Pitfalls and Best Practices

Pitfall Solution
Middleware order wrong Use UseHttpsRedirection() early.
Circular dependencies Use interfaces and DI.
Hardcoded strings in views Use ViewBag or strongly typed models.
Not disposing DbContext Use Scoped lifetime.
Ignoring HTTPS Always use UseHttpsRedirection().

Exam Tip

  1. Understand the Pipeline:

    • Memorize the default middleware order (e.g., UseRouting comes before UseAuthorization).
    • Know that app.Run() is terminal (no further middleware runs).
  2. DI Lifetimes:

    • Transient: New every time (e.g., IEmailService).
    • Scoped: One per request (e.g., DbContext).
    • Singleton: One for the app (e.g., ILogger).
  3. Razor vs. MVC:

    • Razor Pages = Simpler, page-focused (e.g., /Admin/Users).
    • MVC = Better for APIs/complex workflows (e.g., /api/orders).
  4. Deployment:

    • IIS: Windows-only, uses web.config.
    • Kestrel: Cross-platform, default in .NET Core.
    • Docker: Use FROM mcr.microsoft.com/dotnet/aspnet.
  5. Visuals in Exams:

    • Draw the middleware pipeline as a flowchart.
    • Show the state of a List<Product> after a controller populates it.
    • Trace DI resolution (e.g., how IProductService is injected).
  6. Code Snippets:

    • Always include:
      • A controller action.
      • A Razor view.
      • Middleware registration.
    • Trace variable states (e.g., Products list before/after population).

Based on the TU BIM syllabus for .NET Programming (IT275), unit 8.

Discussion

Loading…