.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" DockerKey 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.,
Userclass). - View: UI (Razor
.cshtmlfiles). - Controller: Handles HTTP requests and returns responses.
How MVC Works
- Request: User navigates to
/Home/Index. - Routing: Matches URL to
HomeController.Index(). - Controller: Fetches data (Model) and passes it to a View.
- View: Renders HTML (e.g.,
Index.cshtml). - 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: HTMLExample: 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:
- IIS (Windows): Traditional hosting (uses IIS HTTP Server).
- Kestrel (Linux/Windows): Standalone (default in .NET Core).
- Docker: Containerized for scalability.
- 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
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.
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.
Daraz (Global):
- Idea Used: MVC Architecture for product listings.
- How: Daraz’s product pages use MVC:
- Model:
Productclass withId,Name,Price. - Controller:
ProductControllerfetches products from the database. - View:
Index.cshtmlrenders the product grid.
- Model:
- Real Scenario: When you search for "laptops," Daraz’s backend routes the request to
ProductController.Search(), which returns filtered products to the view.
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 freshDbContextto 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
Understand the Pipeline:
- Memorize the default middleware order (e.g.,
UseRoutingcomes beforeUseAuthorization). - Know that
app.Run()is terminal (no further middleware runs).
- Memorize the default middleware order (e.g.,
DI Lifetimes:
- Transient: New every time (e.g.,
IEmailService). - Scoped: One per request (e.g.,
DbContext). - Singleton: One for the app (e.g.,
ILogger).
- Transient: New every time (e.g.,
Razor vs. MVC:
- Razor Pages = Simpler, page-focused (e.g.,
/Admin/Users). - MVC = Better for APIs/complex workflows (e.g.,
/api/orders).
- Razor Pages = Simpler, page-focused (e.g.,
Deployment:
- IIS: Windows-only, uses
web.config. - Kestrel: Cross-platform, default in .NET Core.
- Docker: Use
FROM mcr.microsoft.com/dotnet/aspnet.
- IIS: Windows-only, uses
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
IProductServiceis injected).
Code Snippets:
- Always include:
- A controller action.
- A Razor view.
- Middleware registration.
- Trace variable states (e.g.,
Productslist before/after population).
- Always include:
Based on the TU BIM syllabus for .NET Programming (IT275), unit 8.
Discussion
Loading…