ASP.NET Core  

Input Validation and Sanitization in ASP.NET Core – End-to-End Example

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);
    }
}
  

This controller

  1. Validates DTO using annotations.

  2. Sanitizes inputs (removes malicious HTML/JS).

  3. Hashes the password before saving.

  4. Stores safe data into DB.

Step 6. Middleware for Global Input Sanitization

If you want all incoming query strings and form data sanitized automatically, add middleware.

  
    public class InputSanitizationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly HtmlSanitizer _sanitizer = new HtmlSanitizer();

    public InputSanitizationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        foreach (var key in context.Request.Query.Keys.ToList())
        {
            context.Request.Query[key] = _sanitizer.Sanitize(context.Request.Query[key]);
        }

        await _next(context);
    }
}
  

Register in the Program.cs.

  
    app.UseMiddleware<InputSanitizationMiddleware>();
  

Step 7. Protect Against CSRF

In MVC/Razor pages, always use anti-forgery tokens.

  
    <form asp-action="Register" method="post">
    @Html.AntiForgeryToken()
    <input type="text" name="Username" />
    <input type="email" name="Email" />
    <input type="password" name="Password" />
    <button type="submit">Register</button>
</form>
  

Controller

  
    [ValidateAntiForgeryToken]
public IActionResult Register(RegisterDto dto) { ... }
  

Best Practices Recap

  • Use DTOs with data annotations for validation.

  • Sanitize inputs with HtmlSanitizer.

  • Never trust user data; always encode/escape output.

  • Use CSRF protection for forms.

  • Always hash & salt passwords (we used SHA256 here, but use ASP.NET Core Identity for real apps).

  • Limit request size & file uploads.

  • Always use parameterized queries or EF Core to prevent SQL injection.

Conclusion

By combining validation (Data Annotations/FluentValidation) with sanitization (HtmlSanitizer, encoding), and enforcing security best practices like hashing and CSRF tokens, your ASP.NET Core application becomes much more secure and resilient against common attacks.

This end-to-end example showed how to.

  • Build a DTO-based validation model.

  • Sanitize unsafe input before saving.

  • Store safe, hashed user data in the database

  • Add middleware for global sanitization

Further Reading