Introduction
Every ASP.NET Core application deals with user input forms, query strings, JSON payloads, or file uploads.
If input is not correctly validated and sanitized, attackers can exploit it to inject malicious scripts (XSS), perform SQL Injection, or launch denial-of-service (DoS) attacks.
In this article, we’ll build a complete end-to-end example using DTOs, validation, sanitization, controllers, and Entity Framework Core.
Step 1. Create DTOs (Data Transfer Objects)
DTOs represent the input contracts that users send to our API or MVC controller.
public class RegisterDto
{
[Required(ErrorMessage = "Username is required")]
[StringLength(50, MinimumLength = 3)]
public string Username { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; }
[Required]
[RegularExpression(@"^[a-zA-Z0-9_]*$", ErrorMessage = "Password can only contain letters, numbers, and underscores.")]
public string Password { get; set; }
}
DTO ensures that the data coming in is validated automatically via annotations.
Step 2. Sanitization Helper
We need to sanitize HTML or text inputs to prevent malicious scripts from executing.
For this, we use the Ganss.XSS HtmlSanitizer NuGet package.
using Ganss.XSS;
public static class InputSanitizer
{
private static readonly HtmlSanitizer _sanitizer = new HtmlSanitizer();
public static string Clean(string input)
{
if (string.IsNullOrWhiteSpace(input))
return input;
return _sanitizer.Sanitize(input.Trim());
}
}
Step 3. Entity (Database Model)
We’ll persist valid and sanitized data into a database using Entity Framework Core.
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string Username { get; set; }
[MaxLength(100)]
public string Email { get; set; }
[MaxLength(200)]
public string PasswordHash { get; set; }
}
Step 4. Database Context
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
Step 5. Controller with Validation and Sanitization
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using System.Text;
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
private readonly AppDbContext _context;
public AccountController(AppDbContext context)
{
_context = context;
}
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterDto dto)
{
// Step 1: Validate input
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Step 2: Sanitize inputs
var safeUsername = InputSanitizer.Clean(dto.Username);
var safeEmail = InputSanitizer.Clean(dto.Email);
// Step 3: Prevent duplicate users
if (await _context.Users.AnyAsync(u => u.Email == safeEmail))
{
return BadRequest("Email already exists.");
}
// Step 4: Hash password (never store plain text)
var hashedPassword = HashPassword(dto.Password);
// Step 5: Save to DB
var user = new User
{
Username = safeUsername,
Email = safeEmail,
PasswordHash = hashedPassword
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return Ok("User registered successfully.");
}
private string HashPassword(string password)
{
using var sha256 = SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(bytes);
}
}
Join the conversation! Your thoughts help the community grow.