.NET Core

.NET Core Tutorial

Build modern web applications with ASP.NET Core

1 What is .NET Core?

.NET Core (now called .NET 5/6/7/8) is a free, open-source, cross-platform framework developed by Microsoft for building modern applications. It can run on Windows, macOS, and Linux.

.NET Ecosystem
ASP.NET Core
Web Apps & APIs
Entity Framework
Database ORM
Blazor
Web UI with C#
MAUI
Cross-platform Apps

🔑 Key Features

Cross-Platform: Build once, run anywhere (Windows, macOS, Linux)
High Performance: One of the fastest web frameworks
Open Source: Community-driven development on GitHub
Modern: Built-in support for dependency injection, async/await

.NET Core vs .NET Framework

Comparison
// .NET Core / .NET 5+ (Modern)
✓ Cross-platform (Windows, macOS, Linux)
✓ Open source
✓ High performance
✓ Side-by-side versioning
✓ Microservices & containers ready

// .NET Framework (Legacy)
✗ Windows only
✗ Slower release cycles
✗ Machine-wide installation
✓ Mature ecosystem
✓ WinForms, WPF support

2 Setup & Installation

To start developing with .NET Core, you need the .NET SDK installed on your machine.

Installation Commands
# Check if .NET is installed
dotnet --version

# Windows - Download from https://dotnet.microsoft.com/download
# Or use winget:
winget install Microsoft.DotNet.SDK.8

# macOS - Using Homebrew:
brew install --cask dotnet-sdk

# Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y dotnet-sdk-8.0

# Verify installation
dotnet --list-sdks
dotnet --list-runtimes

💡 Recommended IDEs

Visual Studio 2022 - Full-featured IDE for Windows
Visual Studio Code - Lightweight, cross-platform with C# extension
JetBrains Rider - Cross-platform .NET IDE

VS Code Extensions
# Essential VS Code Extensions for .NET
C# (ms-dotnettools.csharp)
C# Dev Kit (ms-dotnettools.csdevkit)
NuGet Package Manager
REST Client (for API testing)

3 First Application

Let's create your first ASP.NET Core web application using the command line.

Terminal
# Create a new web application
dotnet new webapp -n MyFirstApp
cd MyFirstApp

# Run the application
dotnet run

# Output:
# Now listening on: https://localhost:5001
# Now listening on: http://localhost:5000

# Available project templates:
dotnet new list
# webapp     - Razor Pages web app
# mvc        - MVC web app
# webapi     - Web API
# blazor     - Blazor Web App
# console    - Console Application
Program.cs (.NET 8)
// Minimal API - Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();

app.Run();

4 Project Structure

Understanding the project structure is essential for organizing your ASP.NET Core applications.

MVC Project Structure
MyApp/
├── Controllers/          # Request handlers
│   └── HomeController.cs
├── Models/               # Data models
│   └── Product.cs
├── Views/                # Razor views (.cshtml)
│   ├── Home/
│   │   └── Index.cshtml
│   └── Shared/
│       └── _Layout.cshtml
├── wwwroot/              # Static files (CSS, JS, images)
│   ├── css/
│   ├── js/
│   └── lib/
├── appsettings.json      # Configuration
├── Program.cs            # Application entry point
└── MyApp.csproj          # Project file
appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=True;"
  },
  "AllowedHosts": "*"
}

5 Middleware

Middleware are components that form the request pipeline. Each component can process requests and pass them to the next.

Request Pipeline
Request →
Middleware 1 →
Middleware 2 →
Endpoint
Custom Middleware
// Custom logging middleware
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(
        RequestDelegate next, 
        ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Before the next middleware
        _logger.LogInformation(
            $"Request: {context.Request.Method} {context.Request.Path}");

        await _next(context);  // Call next middleware

        // After the next middleware
        _logger.LogInformation(
            $"Response: {context.Response.StatusCode}");
    }
}

// Register in Program.cs
app.UseMiddleware<RequestLoggingMiddleware>();
Built-in Middleware
// Common middleware order in Program.cs
app.UseExceptionHandler("/Error");  // Handle exceptions
app.UseHsts();                        // HTTP Strict Transport Security
app.UseHttpsRedirection();            // Redirect HTTP to HTTPS
app.UseStaticFiles();                 // Serve wwwroot files
app.UseRouting();                     // Enable routing
app.UseCors();                        // Cross-Origin Resource Sharing
app.UseAuthentication();              // Who are you?
app.UseAuthorization();               // What can you do?
app.MapControllers();                 // Map controller endpoints

6 Routing

Routing matches incoming HTTP requests to endpoints (controllers/actions).

Attribute Routing
[ApiController]
[Route("api/[controller]")]  // api/products
public class ProductsController : ControllerBase
{
    // GET api/products
    [HttpGet]
    public IActionResult GetAll() => Ok(products);

    // GET api/products/5
    [HttpGet("{id}")]
    public IActionResult GetById(int id) => Ok(product);

    // GET api/products/category/electronics
    [HttpGet("category/{name}")]
    public IActionResult GetByCategory(string name) => Ok(products);

    // POST api/products
    [HttpPost]
    public IActionResult Create([FromBody] Product product) 
        => CreatedAtAction(nameof(GetById), new { id = product.Id }, product);

    // PUT api/products/5
    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] Product product) 
        => NoContent();

    // DELETE api/products/5
    [HttpDelete("{id}")]
    public IActionResult Delete(int id) => NoContent();
}
Route Constraints
// Route constraints ensure valid parameter types
[HttpGet("{id:int}")]           // id must be integer
[HttpGet("{name:alpha}")]        // name must be alphabetic
[HttpGet("{id:int:min(1)}")]    // id >= 1
[HttpGet("{date:datetime}")]    // valid datetime
[HttpGet("{id:guid}")]          // valid GUID
[HttpGet("{slug:regex(^[a-z]+$)}")]  // regex pattern

7 MVC Introduction

Model-View-Controller (MVC) is a design pattern that separates an application into three components.

MVC Architecture
Model
Data & Logic
View
UI / HTML
Controller
Request Handler

🔑 MVC Components

Model: Represents data and business logic
View: Displays data to the user (Razor templates)
Controller: Handles requests, updates model, returns view

8 Controllers

Controllers handle incoming requests, process them, and return responses.

MVC Controller
using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    // GET /Home/Index or /
    public IActionResult Index()
    {
        return View();  // Returns Views/Home/Index.cshtml
    }

    // GET /Home/About
    public IActionResult About()
    {
        ViewData["Message"] = "About page";
        return View();
    }

    // GET /Home/Details/5
    public IActionResult Details(int id)
    {
        var product = _productService.GetById(id);
        if (product == null)
            return NotFound();
        
        return View(product);  // Pass model to view
    }

    // POST /Home/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(Product product)
    {
        if (ModelState.IsValid)
        {
            _productService.Add(product);
            return RedirectToAction(nameof(Index));
        }
        return View(product);
    }
}
Action Results
// Common Action Results
return View();                    // Return a view
return View(model);               // Return view with model
return PartialView("_Partial");  // Return partial view
return Json(data);                // Return JSON
return RedirectToAction("Index"); // Redirect to action
return Redirect("/home");        // Redirect to URL
return NotFound();                // 404
return BadRequest();              // 400
return Unauthorized();            // 401
return Ok(data);                  // 200 with data
return Content("Hello");         // Plain text
return File(bytes, "image/png"); // File download

9 Views & Razor

Razor is the view engine that combines C# with HTML to create dynamic web pages.

Razor Syntax
@* Views/Home/Index.cshtml *@
@model List<Product>

<h1>Products</h1>

@* Single expression *@
<p>Count: @Model.Count</p>

@* Code block *@
@{
    var title = "Welcome";
    ViewData["Title"] = title;
}

@* Conditional rendering *@
@if (Model.Any())
{
    <ul>
    @foreach (var product in Model)
    {
        <li>@product.Name - $@product.Price</li>
    }
    </ul>
}
else
{
    <p>No products found.</p>
}

@* HTML encoding (automatic) *@
<p>@userInput</p>  // Encoded - safe

@* Raw HTML (use with caution) *@
@Html.Raw(htmlContent)
Layout & Sections
@* Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html>
<head>
    <title>@ViewData["Title"] - MyApp</title>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <nav>...</nav>
    
    <main>
        @RenderBody()  @* Content goes here *@
    </main>
    
    <footer>...</footer>
    
    @RenderSection("Scripts", required: false)
</body>
</html>

@* In a view file *@
@{
    Layout = "_Layout";
}

@section Scripts {
    <script src="~/js/custom.js"></script>
}

10 Models

Models represent data and include validation attributes.

Model with Validation
using System.ComponentModel.DataAnnotations;

public class Product
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Name is required")]
    [StringLength(100, MinimumLength = 3)]
    public string Name { get; set; }

    [Required]
    [Range(0.01, 10000, ErrorMessage = "Price must be between 0.01 and 10000")]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [StringLength(500)]
    public string? Description { get; set; }

    [Required]
    [EmailAddress]
    public string SupplierEmail { get; set; }

    [Url]
    public string? ImageUrl { get; set; }

    [Display(Name = "In Stock")]
    public bool IsAvailable { get; set; }

    [DataType(DataType.Date)]
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

11 Web API Basics

ASP.NET Core Web API is used to build RESTful services that return JSON data.

API Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    // GET: api/products
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        var products = await _productService.GetAllAsync();
        return Ok(products);
    }

    // GET: api/products/5
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        
        if (product == null)
            return NotFound();
        
        return Ok(product);
    }

    // POST: api/products
    [HttpPost]
    public async Task<ActionResult<Product>> CreateProduct(Product product)
    {
        await _productService.CreateAsync(product);
        return CreatedAtAction(
            nameof(GetProduct), 
            new { id = product.Id }, 
            product);
    }
}

12 CRUD Operations

Complete CRUD API
// PUT: api/products/5
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(int id, Product product)
{
    if (id != product.Id)
        return BadRequest();

    var exists = await _productService.ExistsAsync(id);
    if (!exists)
        return NotFound();

    await _productService.UpdateAsync(product);
    return NoContent();
}

// DELETE: api/products/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
    var product = await _productService.GetByIdAsync(id);
    if (product == null)
        return NotFound();

    await _productService.DeleteAsync(id);
    return NoContent();
}

// PATCH: api/products/5
[HttpPatch("{id}")]
public async Task<IActionResult> PatchProduct(
    int id, 
    [FromBody] JsonPatchDocument<Product> patchDoc)
{
    var product = await _productService.GetByIdAsync(id);
    if (product == null)
        return NotFound();

    patchDoc.ApplyTo(product);
    await _productService.UpdateAsync(product);
    return Ok(product);
}

13 API Validation

Model Validation
// DTO with validation
public class CreateProductDto
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; }

    [Required]
    [Range(0.01, 10000)]
    public decimal Price { get; set; }
}

// Controller with validation
[HttpPost]
public IActionResult Create(CreateProductDto dto)
{
    // [ApiController] automatically validates and returns 400
    // Manual validation if needed:
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    // Custom validation
    if (await _service.NameExistsAsync(dto.Name))
    {
        ModelState.AddModelError("Name", "Product name already exists");
        return BadRequest(ModelState);
    }

    var product = _mapper.Map<Product>(dto);
    // ...
}

14 Entity Framework Core

Entity Framework Core is an ORM (Object-Relational Mapper) that lets you work with databases using C# objects.

Install EF Core
# Install EF Core packages
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools

# For SQLite
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

# Install EF CLI tools globally
dotnet tool install --global dotnet-ef
Entity Model
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    // Navigation property
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // Collection navigation
    public ICollection<Product> Products { get; set; }
}

15 DbContext

AppDbContext.cs
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) 
        : base(options) { }

    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Fluent API configuration
        modelBuilder.Entity<Product>()
            .Property(p => p.Name)
            .IsRequired()
            .HasMaxLength(100);

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

        // Seed data
        modelBuilder.Entity<Category>().HasData(
            new Category { Id = 1, Name = "Electronics" },
            new Category { Id = 2, Name = "Clothing" }
        );
    }
}

// Register in Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));

16 Migrations

Migration Commands
# Create a migration
dotnet ef migrations add InitialCreate

# Apply migrations to database
dotnet ef database update

# Remove last migration (if not applied)
dotnet ef migrations remove

# Generate SQL script
dotnet ef migrations script

# List all migrations
dotnet ef migrations list

# Rollback to specific migration
dotnet ef database update MigrationName

17 LINQ Queries

EF Core Queries
// Get all
var products = await _context.Products.ToListAsync();

// Get by ID
var product = await _context.Products.FindAsync(id);

// Filter with Where
var expensive = await _context.Products
    .Where(p => p.Price > 100)
    .ToListAsync();

// Include related data (eager loading)
var productsWithCategory = await _context.Products
    .Include(p => p.Category)
    .ToListAsync();

// Sorting and paging
var pagedProducts = await _context.Products
    .OrderBy(p => p.Name)
    .Skip(20)
    .Take(10)
    .ToListAsync();

// Projection
var productNames = await _context.Products
    .Select(p => new { p.Name, p.Price })
    .ToListAsync();

// Aggregation
var avgPrice = await _context.Products.AverageAsync(p => p.Price);
var count = await _context.Products.CountAsync();

// First or default
var first = await _context.Products
    .FirstOrDefaultAsync(p => p.Name == "iPhone");

18 Dependency Injection

ASP.NET Core has built-in support for dependency injection (DI), which helps create loosely coupled code.

Service Registration
// Interface
public interface IProductService
{
    Task<IEnumerable<Product>> GetAllAsync();
    Task<Product?> GetByIdAsync(int id);
    Task CreateAsync(Product product);
}

// Implementation
public class ProductService : IProductService
{
    private readonly AppDbContext _context;

    public ProductService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
        => await _context.Products.ToListAsync();
    
    // ... other methods
}

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

// Service lifetimes:
// Transient  - New instance every time
// Scoped     - One per HTTP request
// Singleton  - One for entire app lifetime

19 Authentication

JWT Authentication
// Install package
// dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

// Program.cs configuration
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

app.UseAuthentication();
app.UseAuthorization();

// Protect controller/action
[Authorize]
public class SecureController : ControllerBase
{
    [Authorize(Roles = "Admin")]
    public IActionResult AdminOnly() => Ok();
}

20 Deployment

Deployment Commands
# Build for production
dotnet publish -c Release -o ./publish

# Run in production
dotnet MyApp.dll

# Docker deployment
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY ./publish .
EXPOSE 80
ENTRYPOINT ["dotnet", "MyApp.dll"]

# Build and run Docker
docker build -t myapp .
docker run -p 8080:80 myapp

# Azure deployment
az webapp up --name myapp --resource-group mygroup

💡 Production Tips

• Use environment variables for secrets
• Enable HTTPS in production
• Configure proper logging
• Use connection pooling for databases
• Set up health checks for monitoring