Understanding Data Transfer Objects (DTOs)

Here's an post written in the context of Data Transfer Objects (DTOs). This post is designed for developers or technical readers who want to understand what DTOs are, why they are used, and how to implement them properly in a software project.

Understanding Data Transfer Objects (DTOs)

Getting Started

In software development, especially when dealing with large-scale systems, efficient and clean data transfer becomes critical. This is where the Data Transfer Object (DTO) pattern plays a significant role. DTO objects are simple objects used to encapsulate data and send it from one subsystem of an application to another, especially across network boundaries.

Whether you're building a REST API, designing a microservices architecture, or just trying to clean up a messy codebase, DTOs can help make your system more modular, maintainable, and robust.

DTOs are a valuable tool in modern software architecture. They help maintain a clean separation between different layers, improve security, and optimize data transmission. However, like any pattern, they should be used with care and purpose.

What is a Data Transfer Object (DTO)?

A Data Transfer Object is a plain object that holds data but contains no business logic. It's often used to:
  • Transfer data between layers in an application (e.g., Controller to Service, or Service to Repository).
  • Transfer data over the network in APIs (e.g., REST or gRPC).
  • Avoid unnecessary exposure of internal domain models.

Example of DTO
Scenario: You have a User entity, and you want to expose only selected fields through your API — not everything from your database model.
The Domain Entity (Model):
 public class User
{
  public int Id { get; set; }                 // Public ID
  public string Name { get; set; }            // Full Name
  public string Email { get; set; }           // Email
  public string PasswordHash { get; set; }    // Sensitive - DO NOT EXPOSE
  public DateTime CreatedAt { get; set; }     // Internal Use
}

This is the full entity, usually mapped to the database via Entity Framework.

The DTO Object: This is what you send to the client. It contains only the necessary data.
public class UserDTO
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Email { get; set; }
}

This UserDTO might be used to expose a subset of user data to an API client, without revealing sensitive fields like passwords or internal IDs.

Why Use DTOs?

Separation of Concerns

DTOs help separate the domain model (your business logic) from the presentation or data transport layer. This makes your application cleaner and easier to maintain.

Security

DTOs prevent over-posting and accidental data leakage. For example, if your domain model includes a passwordHash, you wouldn't want that sent back in an API response.

Performance

You can trim down the data that gets transferred by sending only the fields required. This is especially important in mobile apps or large datasets.

Flexibility in API Design

DTOs allow you to shape your data exactly how the frontend or another service expects it—without being constrained by how it's modeled in your database or backend.

When Not to Use DTOs?

While DTOs are beneficial, they can also add boilerplate code and increase complexity if used unnecessarily. In small apps, or when using tools like GraphQL (where you can specify exactly what data to retrieve), DTOs may not always be required.

Implementing DTO in ASP.NET Core Web API

Implementing DTOs (Data Transfer Objects) in an ASP.NET Core Web API is a best practice to decouple your API models from your domain models, improve security, and control the data exposed to clients.

Here’s a step-by-step guide to implementing DTOs in an ASP.NET Core Web API project using the example above.:
Create the DTO Classes
You can create separate DTOs for:
  • Reading (UserReadDto)
  • Creating (UserCreateDto
  • Updating (UserUpdateDto)

public class UserReadDto
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}
public class UserCreateDto
{
  public string Name { get; set; }
  public decimal Price { get; set; }
}
public class UserUpdateDto
{
  public string Name { get; set; }
  public decimal Price { get; set; }
}

You don’t expose InternalCode in the DTO, so it won’t be serialized or deserialized from the API.

Map Between Entity and DTO
There are two main ways to map between models:
Option 1: Manually
public static class UserMapper
{
  public static UserReadDto ToReadDto(User User) => new()
  {
    Id = User.Id,
    Name = User.Name,
    Price = User.Price
    };
    public static User ToEntity(UserCreateDto dto) => new()
    {
      Name = dto.Name,
      Price = dto.Price
      };
    }

Use AutoMapper
AutoMapper is recommended for large apps
Install AutoMapper:
  1. Open your project in Visual Studio.
  2. In the Solution Explorer, right-click on your project name.
  3. Select "Manage NuGet Packages..." from the context menu.
  4. Click on the "Browse" tab.
  5. In the search box, type:
    AutoMapper.Extensions.Microsoft.DependencyInjection
    
  6. Select the package from the search results.
  7. On the right side, make sure the correct project is selected (if you have multiple projects).
  8. Click the "Install" button.
  9. Accept any license agreements that appear.

Create a mapping profile:
using AutoMapper;
public class UserProfile : Profile
{
  public UserProfile()
  {
    CreateMap<User, UserReadDto>();
    CreateMap<UserCreateDto, User>();
    CreateMap<UserUpdateDto, User>();
    CreateMap<User, UserUpdateDto>();
  }
}

Register in Program.cs:
 builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

Use DTOs in Controller
 [ApiController]  
 [Route("api/users")]  
 public class UsersController : ControllerBase  
 {  
   private readonly IUserRepository _repo;  
   private readonly IMapper _mapper;  
   public UsersController(IUserRepository repo, IMapper mapper)  
   {  
     _repo = repo;  
     _mapper = mapper;  
   }  
   [HttpGet]  
   public ActionResult<IEnumerable<UserReadDto>> GetAll()  
   {  
     var Users = _repo.GetAll();  
     return Ok(_mapper.Map<IEnumerable<UserReadDto>>(Users));  
   }  
   [HttpGet("{id}")]  
   public ActionResult<UserReadDto> GetById(int id)  
   {  
     var User = _repo.GetById(id);  
     if (User == null) return NotFound();  
     return Ok(_mapper.Map<UserReadDto>(User));  
   }  
   [HttpPost]  
   public ActionResult<UserReadDto> Create(UserCreateDto dto)  
   {  
     var User = _mapper.Map<User>(dto);  
     _repo.Add(User);  
     _repo.SaveChanges();  
     var readDto = _mapper.Map<UserReadDto>(User);  
     return CreatedAtAction(nameof(GetById), new { id = readDto.Id }, readDto);  
   }  
   [HttpPut("{id}")]  
   public IActionResult Update(int id, UserUpdateDto dto)  
   {  
     var User = _repo.GetById(id);  
     if (User == null) return NotFound();  
     _mapper.Map(dto, User); // Updates existing entity  
     _repo.SaveChanges();  
     return NoContent();  
   }  
 }  

DTO Vs Entity Model

The terms DTO (Data Transfer Object) and Entity Model (or simply Entity) refer to different layers and responsibilities in a software application, especially in architectures like MVC, Clean Architecture, or Domain-Driven Design (DDD).

Aspect DTO (Data Transfer Object) Entity Model (Domain or Database Entity)
Purpose Used to transfer data between layers (e.g., API ↔ service). Represents the business/domain model or database schema.
Location in App Often in controller or service layers. Found in the domain or persistence layer.
Contains Logic? No business logic; just plain data (fields only). May contain business logic, validation, or methods.
Used For Serialization/deserialization of data (e.g., JSON ↔ object). ORM mapping (e.g., JPA, Hibernate), domain logic.
Structure Often a subset of entity fields. Contains full set of fields as per domain or DB design.
Security Safer to expose externally (e.g., to clients via APIs). Should not be exposed directly outside the app.
Example UserDTO { id, name, email } UserEntity { id, name, email, password, createdAt }

Summary

DTOs are a fundamental design pattern for structuring data communication across layers or systems. While simple in concept, their thoughtful application can significantly enhance system performance, clarity, and maintainability. Understanding when and how to use DTOs can help developers build more robust and scalable applications.

Thanks

Kailash Chandra Behera

I am an IT professional with over 13 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies.

Previous Post Next Post

نموذج الاتصال