.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.
🔑 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
// .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.
# 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
# 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.
# 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
// 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.
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
{
"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.
// 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>();
// 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).
[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 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 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.
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); } }
// 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.
@* 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)
@* 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.
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.
[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
// 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
// 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 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
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
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
# 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
// 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.
// 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
// 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
# 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