Skip to content

Aspnet Models

DodaTech 4 min read

title: ASP.NET Core Models — Complete Guide to Data and Validation description: 'Learn ASP.NET Core models: data annotations for validation, model binding, ViewModels, DTOs, AutoMapper, and custom validation attributes for clean data handling.' date: 2026-06-28 lastmod: 2026-06-28 weight: 17 tags: [backend, aspnet]


ASP.NET Core models represent application data with validation rules via data annotations, supporting model binding from HTTP requests and automatic validation in controllers.

## What You'll Learn

By the end of this tutorial, you'll create models with data annotations, implement custom validation, use ViewModels and DTOs, configure AutoMapper, and handle model binding errors.

## Real-World Use

A registration form model uses [Required], [EmailAddress], and [StringLength] attributes. Invalid submissions return validation errors to the UI with field-level messages.

## Models Learning Path

```mermaid
flowchart LR
  A[Views] --> B[Models]
  B --> C[Routing]
  C --> D[Middleware]
  D --> E[DI]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Model with Data Annotations

using System.ComponentModel.DataAnnotations;
public class RegisterViewModel
{
    [Required(ErrorMessage = "Username is required")]
    [StringLength(50, MinimumLength = 3)]
    public string Username { get; set; } = string.Empty;
    
    [Required]
    [EmailAddress(ErrorMessage = "Invalid email format")]
    public string Email { get; set; } = string.Empty;
    
    [Required]
    [StringLength(100, MinimumLength = 8)]
    [DataType(DataType.Password)]
    public string Password { get; set; } = string.Empty;
    
    [Compare("Password", ErrorMessage = "Passwords do not match")]
    [DataType(DataType.Password)]
    public string ConfirmPassword { get; set; } = string.Empty;
}

Custom Validation

public class MinimumAgeAttribute : ValidationAttribute
{
    private readonly int _minimumAge;
    public MinimumAgeAttribute(int minimumAge)
    {
        _minimumAge = minimumAge;
    }
    protected override ValidationResult? IsValid(object? value, ValidationContext context)
    {
        if (value is DateTime dateOfBirth)
        {
            var age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age)) age--;
            if (age < _minimumAge)
                return new ValidationResult($"Must be at least {_minimumAge} years old");
        }
        return ValidationResult.Success;
    }
}

public class UserProfile
{
    [MinimumAge(18, ErrorMessage = "You must be 18+")]
    public DateTime DateOfBirth { get; set; }
}

ViewModel vs DTO

// Domain Model (Entity Framework)
public class User
{
    public int Id { get; set; }
    public string Username { get; set; } = "";
    public string Email { get; set; } = "";
    public string PasswordHash { get; set; } = "";
    public DateTime CreatedAt { get; set; }
}

// ViewModel (for views)
public class UserProfileViewModel
{
    public string Username { get; set; } = "";
    public string Email { get; set; } = "";
    public string? Bio { get; set; }
}

// DTO (for API responses)
public record UserDto(
    int Id,
    string Username,
    string Email,
    DateTime CreatedAt
);

AutoMapper Configuration

dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
// Mapping profile
public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>();
        CreateMap<User, UserProfileViewModel>();
        CreateMap<RegisterViewModel, User>()
            .ForMember(dest => dest.PasswordHash, 
                       opt => opt.MapFrom(src => BCrypt.HashPassword(src.Password)));
    }
}

// Usage in controller
[ApiController]
public class UsersController : ControllerBase
{
    private readonly IMapper _mapper;
    public UsersController(IMapper mapper) => _mapper = mapper;
    
    [HttpGet("{id}")]
    public ActionResult<UserDto> Get(int id)
    {
        var user = _userService.GetById(id);
        return Ok(_mapper.Map<UserDto>(user));
    }
}

Model Binding in Controller

[HttpPost("register")]
public IActionResult Register(RegisterViewModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);  // Returns all validation errors
    }
    var user = _mapper.Map<User>(model);
    _userService.Create(user);
    return Ok();
}

Common Mistakes

1. Exposing Domain Models Directly

Domain models contain internal data (PasswordHash). Map to ViewModels or DTOs before returning.

2. Not Checking ModelState.IsValid

Without checking, invalid models pass through. Always check ModelState.IsValid in every POST action.

3. Mixing Validation and DB Concerns

Models with both [Required] and [ForeignKey] do too much. Separate validation models from persistence models.

4. Overusing ViewBag for Data

ViewBag is untyped. Create ViewModels with specific properties for strong typing and IntelliSense.

5. Not Using [Bind] for Security

Without [Bind(Include = "Property1,Property2")], over-posting attacks can set properties you didn't intend.

Practice Questions

1. What are data annotations?

Attributes like [Required], [StringLength], [Range] that define validation rules on model properties.

2. How do you create a custom validation attribute?

Inherit from ValidationAttribute and override IsValid() method with your validation logic.

3. What is the difference between ViewModel and DTO?

ViewModel is shaped for a specific view (includes display data). DTO is for API transfers (typically matches the data contract).

4. How does AutoMapper simplify development?

AutoMapper automatically maps properties between objects (User -> UserDto), reducing boilerplate mapping code.

5. Challenge: Create a registration model with 5 validation rules and a custom password strength validator.

public class StrongPasswordAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext ctx)
    {
        var password = value as string;
        if (password == null || !password.Any(char.IsUpper) 
            || !password.Any(char.IsLower) 
            || !password.Any(char.IsDigit))
            return new ValidationResult("Password needs upper, lower, and digit");
        return ValidationResult.Success;
    }
}

FAQ

What is model binding?

The process of mapping HTTP request data (form, query, body) to action method parameters and model properties.

Can I use attributes for JSON serialization?

Yes. Use [JsonPropertyName] for custom JSON names, [JsonIgnore] to exclude properties.

What is the difference between [Required] and [BindRequired]?

[Required] validates the property. [BindRequired] ensures the value is present in the request (even if empty).

How do I validate across multiple properties?

Implement IValidatableObject on the model for cross-property validation logic.

What is over-posting?

An attack where extra form fields are submitted to set properties you didn't intend. Prevent with [Bind] or ViewModels.

Mini Project: Registration Model

Create a complete user registration model with validation.

public class RegisterViewModel : IValidatableObject
{
    [Required, StringLength(50, MinimumLength = 3)]
    public string Username { get; set; } = "";
    [Required, EmailAddress]
    public string Email { get; set; } = "";
    [Required, DataType(DataType.Password)]
    public string Password { get; set; } = "";
    [Compare("Password")]
    public string ConfirmPassword { get; set; } = "";
    public IEnumerable<ValidationResult> Validate(ValidationContext ctx)
    {
        if (Password == Username)
            yield return new ValidationResult("Password cannot match username");
    }
}

What's Next

ASP.NET Core Routing ASP.NET Core Middleware

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro