Never expose your Database Entity classes directly to the internet. Doing so is a massive security risk (Mass Assignment vulnerabilities) and tightly couples your database schema to your API contract. We solve this by using Data Transfer Objects (DTOs) to act as a secure middleman.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string PasswordHash { get; set; } // CRITICAL LEAK!
}
[HttpGet("{id}")]
public ActionResult<User> GetUser(int id)
{
// You just leaked the user's password hash to the frontend...
return _db.Users.Find(id);
}
public class UserResponseDto
{
public int Id { get; set; }
public string Name { get; set; }
// No PasswordHash field here!
}
[HttpGet("{id}")]
public ActionResult<UserResponseDto> GetUser(int id)
{
var user = _db.Users.Find(id);
return new UserResponseDto { Id = user.Id, Name = user.Name };
}
Manually mapping properties from an Entity to a DTO (as shown above) becomes incredibly tedious when objects have 50 properties. AutoMapper is an industry-standard library that automates this property-to-property copying.
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
using AutoMapper;
public class UserMappingProfile : Profile
{
public UserMappingProfile()
{
// Source -> Destination
CreateMap<User, UserResponseDto>();
// Destination -> Source (Useful for POST requests)
CreateMap<UserCreateDto, User>();
}
}
// This automatically finds all classes inheriting from "Profile" and registers them
builder.Services.AddAutoMapper(typeof(Program));
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _db;
private readonly IMapper _mapper;
public UsersController(ApplicationDbContext db, IMapper mapper)
{
_db = db;
_mapper = mapper;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserResponseDto>>> GetUsers()
{
var users = await _db.Users.ToListAsync();
// AutoMapper magically converts the List of Entities into a List of DTOs!
var dtos = _mapper.Map<IEnumerable<UserResponseDto>>(users);
return Ok(dtos);
}
}
Q: "AutoMapper's `_mapper.Map` happens in Application Memory. If we have 10,000 users in the database but the frontend only needs 2 columns (Id and Name), does AutoMapper cause an inefficient SQL Query (SELECT *) before mapping?"
Architect Answer: "Yes, if used incorrectly. If you run `await _db.Users.ToListAsync()` first, EF Core downloads every column (including heavy Photo byte[] columns) into RAM resulting in a `SELECT *`, and only THEN does AutoMapper strip them away into the DTO. To fix this, AutoMapper provides the `.ProjectTo<TDto>()` extension method. Instead of pulling data into RAM, `ProjectTo` intercepts the LINQ query and rewrites the physical SQL generated by EF Core into a highly optimized `SELECT Id, Name FROM Users`, downloading only the exact bytes necessary across the network."