Sunday, June 8, 2025
HomeProductsADO.NET Data ProvidersHow to Create and Use Data Transfer Objects (DTOs) in ASP.NET Core   

How to Create and Use Data Transfer Objects (DTOs) in ASP.NET Core   

One of the major design constraints or flaws in an application is exposing the models of the application to the presentation layer or the external world (external apps, services, etc.). Here’s where Data Transfer Objects (DTOs) can help!

DTOs minimize coupling between different layers of the application, facilitate API versioning, improve scalability and performance, ensure efficient and accurate data mapping, foster a clean separation of concerns and enable secure data transfer between the various layers of the application.

This article presents a discussion on Data Transfer Objects, why they are used and how you can work with DTOs in ASP.NET Core applications. This article builds a simple application to demonstrate the concepts covered and uses PostgreSql as the database to store data and dotConnect for PostgreSQL as the data provider for PostgreSQL.

In this article, we’ll connect to PostgreSQL using dotConnect for PostgreSQL which is high performance and enhanced data provider for PostgreSQL that is built on top of ADO.NET and can work on both connected and disconnected modes.

Pre-requisites

You’ll need the following tools to deal with code examples:

Contents

In this article we’ll learn what Data Transfer Objects are, why they are needed and how to work in ASP.NET Core. Here are the steps we’ll follow throughout this article to accomplish this:

  1. What is a Data Transfer Object (DTO)?
  2. Benefits of Using DTOs
  3. What is AutoMapper? Why should we use it?
  4. Project Setup and Package Installation
  5. Use AutoMapper to Map a Model to a DTO 
  6. Best Practices of Using DTOs
  7. Summary
  8. Frequently asked questions

In the following sections of this article, we will explore the significance of Data Transfer Objects (DTOs) in ASP.NET Core development. Additionally, we will examine recommended practices and techniques for effectively implementing and utilizing DTOs.

What is a Data Transfer Object (DTO)?

In an application, a Data Transfer Object (DTO) is an object of a class that transports data between layers. These lightweight objects represent specific portions of the data (i.e., a subset of the actual data) pertaining to a domain model. Once you’ve created DTOs, you can use them as responses to your API endpoints or in your views to display a subset of the actual data. By using DTOs, you can easily transfer data between layers, such as the presentation layer and API endpoints. DTOs are a great choice when you need to pass objects over a bandwidth-constrained medium.

DTOs facilitate loose coupling between the components of your application. This leads to reduced data transfer and improved performance and maintainability. Additionally, DTOs afford greater control over the specific data that is transferred and consumed by different parts of an application, thanks to their customized represe­ntation of the data.

Now that you understand what a DTO is in .NET Core, let’s explore its key advantages. 

Benefits of Using DTOs

There are several benefits of using DTOs:

Reduced coupling: DTOs facilitate modularity and help separate concerns in an application by eliminating tight dependencies between the components thus making your application scalable, maintainable, and easily updatable. By using DTOs for passing data between the layers of an application, coupling between the components of an application are reduced considerably. This is because direct dependencies are eliminated, enabling the components of an application to evolve independent of one another.

Encapsulation: DTOs simplify data handling by concealing internal implementation details, by encapsulating data. This practice ensures that only the essential data is accessible to the external world, hence preventing the exposure of unnecessary and sensitive information.

Data transfer: DTOs encompass only the pertinent information required for a specific operation or a use case. With lean objects and reduced payloads, this approach facilitates reduction in network bandwidth usage and enhances overall performance by transmitting only the data that is needed. 

Security: You can use DTOs to check for validations and security violations. Before processing or persisting data in the DTOs, data validation rules and strict security measures can be applied.

Challenges of using DTOs

When working with DTOs you might need to handle several challenges such as versioning, data validation, mapping DTOs with domain models, etc.

Versioning

As your application evolves, so does the models and DTOs since new features are added or existing features are enhanced. You should be able to version your APIs to ensure backward compatibility when changes to the existing DTOs are introduced in the application. However, modifying DTOs might result in compatibility issues as the existing implementations of the application might be using the DTOs.

Maintenance overhead

The number and complexity of DTOs may increase as your application grows and evolves. It can result in increased maintenance overhead, since you need to manage and update the DTOs whenever the corresponding data structures or models change.

Data redundancy

DTOs oftem duplicate data from the domain model. In the absence of proper synchronization between the DTOs and domain model, this duplication can result in redundancy and inconsistencies.

Data validation

DTOs typically transfer data between components, making data validation crucial to ensuring data integrity. Implementing validation logic across different DTOs may require additional effort, since validation rules may differ between DTOs or need to be synchronized with domain model validations.

Data consistency

Maintaining data consistency between domain models and DTOs can be challenging. As your application evolves, changes to the domain model may require changes to the DTOs. Data inconsistencies or compatibility problems can occur if they are not kept in sync. 

What is AutoMapper? Why should we use it?

AutoMappеr is a popular C# library that еnablе dеvеlopers to define mappings using codе or convеntion and have thе library pеrform thе mapping procеss automatically.  This еliminatеs unnеcеssary, rеpеtitivе codе and strеamlinеs the dеvеlopmеnt process.  

Hеrе’s why you should usе AutoMappеr: 

1.  Simplified mapping: AutoMappеr strеamlinеs thе task of mapping bеtwееn different object types by seamlessly transferring values from source to target objects. As a rеsult, this can hеlp you eliminate codе redundancy and minimizе chancеs of human еrrors in your application.  

2.  Enhanced Productivity: AutoMappеr incrеasеs productivity by automating thе objеct mapping procеss, saving dеvеlopеrs timе and еffort.  This allows thеm to focus on dеvеloping corе businеss logic and rеly on AutoMappеr to handlе objеct mapping еfficiеntly.  

3.  Reduced Codе Redundancy: AutoMappеr consolidatеs mapping logic in a cеntral location,  еliminating thе nееd to duplicatе codе. This simplifiеs codе maintеnancе and еnsurеs consistеncy throughout thе mapping procеss.  

4.  Complеx Mapping Scеnarios: AutoMappеr providеs flеxibility in handling complеx mapping situations such as mapping nеstеd objеcts, collеctions, conditional mappings, and custom-typе convеrtеrs. This еnablеs sеamlеss mapping bеtwееn objеcts with diffеrеnt structurеs and еffеctivеly handlеs еdgе casеs.  

5.  Promotes faster rеfactoring: You can lеvеragе AutoMappеr to simplify rеstructuring objеcts, such as adding or rеmoving propеrtiеs. Instеad of manually updating mapping codе throughout thе application, dеvеlopеrs can еasily changе mapping configurations in onе cеntral location.  

6.  Promotes loosе coupling: AutoMappеr facilitatеs loosе coupling bеtwееn diffеrеnt layеrs of an application, such as thе prеsеntation layеr and thе data layеr using DTOs to transmit data bеtwееn thе layеrs of your application.

Project Setup and Package Installation

To get started, open your IDE, we’ll use Visual Studio in this example. 

  1. Open Visual Studio and create a new ASP.NET Core Web API project. 
  2. Name your project (e.g., DTOexample). 
  3. In Solution Explorer, right-click the project and select Manage NuGet Packages
  4. Install the following packages:

Alternatively, use the terminal: 

dotnet add package Devart.Data.PostgreSql 
dotnet add package AutoMapper 

Check the ADO.NET or Entity Framework differences and compare the benchmarks. 

Using the DVDRental Sample Database 

Instead of creating a custom database and seeding test data manually, we’ll use the DVDRental sample database. This publicly available PostgreSQL database includes a rich schema and realistic sample data. 

In this tutorial, we’ll focus on the actor table to demonstrate how to implement Data Transfer Objects (DTOs) using dotConnect for PostgreSQL. You can restore the DVDRental database using pgAdmin or the psql command-line tool. 

Create the Model Class 

Start by organizing your domain layer. In the Solution Explorer, create a new folder called Models. Inside it, add a file named Actor.cs and define the data model as shown below. This class reflects the structure of the actor table in the DVDRental database. 

public class Actor 
{ 
	public int Actor_Id { get; set; } 
	public string First_Name { get; set; } 
	public string Last_Name { get; set; } 
	public string Address { get; set; } 
	public string Email { get; set; } 
	public string Phone { get; set; } 
} 

Create the ActorRepository Class 

To handle data access, define an interface IActorRepository that abstracts the retrieval logic for actor records: 

public interface IActorRepository 
{ 
	public List<Actor> GetActors(); 
} 

The following implementation encapsulates the database interaction using PgSqlConnection and PgSqlCommand. This class retrieves data directly from the actor table and maps each record into an Actor object. 

public class ActorRepository: IActorRepository 
{ 
	public List<Actor> GetActors() 
	{ 
    	try 
    	{ 
        	List<Actor> actors = new List<Actor>(); 
        	using(PgSqlConnection pgSqlConnection = 
            	new PgSqlConnection("User Id = postgres; 
            	Password = Specify the Db password here" + 
            	"host=localhost;database=demo; 
            	License Key=Specify your license key here")) 
        	{ 
            	using(PgSqlCommand pgSqlCommand = new PgSqlCommand()) 
            	{ 
                	pgSqlCommand.CommandText = 
                    	"Select * From actor"; 
                	pgSqlCommand.Connection = pgSqlConnection; 
                	if (pgSqlConnection.State != 
                    	System.Data.ConnectionState.Open) 
                    	pgSqlConnection.Open(); 
                	using(var pgSqlReader = 
                    	pgSqlCommand.ExecuteReader()) 
                	{ 
                    	while (pgSqlReader.Read()) 
                    	{ 
                        	Actor actor = new Actor(); 
                        	actor.Actor_Id = 
                            	int.Parse(pgSqlReader.GetValue(0).ToString()); 
                        	actor.First_Name = 
                            	pgSqlReader.GetValue(1).ToString(); 
                        	actor.Last_Name = 
                            	pgSqlReader.GetValue(2).ToString(); 
                        	actor.Address = 
                            	pgSqlReader.GetValue(3).ToString(); 
                        	actor.Email = 
                            	pgSqlReader.GetValue(4).ToString(); 
                        	actor.Phone = 
                            	pgSqlReader.GetValue(5).ToString(); 
                        	actors.Add(actor); 
                    	} 
                	} 
            	} 
        	} 
        	return actors; 
    	} 
    	catch 
    	{ 
        	throw; 
    	} 
	} 
} 

Create the ActorDTO Class 

To avoid exposing internal models directly over your API, define a DTO (ActorDTO) that includes only the relevant fields. This helps reduce payload size, improve security, and simplify data shaping. 

public class ActorDTO 
{ 
	public int Actor_Id { get; set; } 
	public string Actor_Name { get; set; } 
} 

Check the ADO.NET or Entity Framework differences and compare the benchmarks.

Use AutoMapper to Map a Model to a DTO 

The properties of Actor and ActorDTO differ, so you’ll configure AutoMapper explicitly. Use the ForMember method to combine the first and last names into a single Actor_Name field in the DTO. 

public class MapperConfig 
{ 
	public static Mapper InitializeAutoMapper() 
	{ 
    	var config = new MapperConfiguration(cfg => 
    	{ 
        	cfg.CreateMap<Actor, ActorDTO>() 
        	.ForMember(a => a.Actor_Name, act => 
        	act.MapFrom(src => src.First_Name + " " + src.Last_Name)); 
    	}); 
    	var mapper = new Mapper(config); 
    	return mapper; 
	} 
} 

This mapping approach centralizes logic and ensures consistency across your application. 

Create the ActorController Class 

Now define the API controller that will serve actor data through a RESTful endpoint. The controller delegates data fetching to the repository, transforms the results via AutoMapper, and returns a collection of DTOs. 

[Route("api/[controller]")] 
[ApiController] 
public class ActorController : ControllerBase 
{ 
	private readonly IActorRepository _actorRepository; 
	public ActorController(IActorRepository actorRepository) 
	{ 
    	_actorRepository = actorRepository; 
	} 
 
	[HttpGet] 
	public List<ActorDTO> Get() 
	{ 
    	return GetActorDTOs(_actorRepository.GetActors()); 
	} 
 
	private List<ActorDTO> GetActorDTOs(List<Actor> actors) 
	{ 
    	var dtos = new List<ActorDTO>(); 
    	var mapper = MapperConfig.InitializeAutoMapper(); 
    	foreach (var source in actors) 
    	{ 
        	var destination = mapper.Map<Actor, ActorDTO>(source); 
        	dtos.Add(destination); 
    	} 
    	return dtos; 
	} 
} 

The IActorRepository is injected via constructor dependency injection. Be sure to register it in Program.cs using: 

builder.Services.AddScoped<IActorRepository, ActorRepository>(); 

Once connected, the /api/actor endpoint returns a mapped list of ActorDTO objects retrieved from the database. 

Best Practices of Using DTOs

When designing Data Transfer Objects, it’s important to follow C# DTO best practices to ensure clean separation of concerns, optimal data transfer, and long-term maintainability. Here’s what to consider: 

Keep DTOs simple: DTOs should be simple and only include the necessary properties, avoiding unnecessary complexity. A recommended approach is to use separate DTOs for different use cases or operations.

Avoid writing business logic in DTOs: The practice of avoiding business logic in DTOs is essential. DTOs, which serve the purpose of facilitating data transfer, should solely focus on carrying properties and re­frain from incorporating methods or intricate logic within them. By adhering to this principle, DTOs can maintain their primary function without becoming convoluted with unnecessary complexities.

Create DTOs that are immutable: Considering immutability can be beneficial when designing DTOs. By making them immutable objects with read-only properties, it promotes thread safety and helps prevent unintended modifications.

Select the appropriate data type: Appropriate data types should be used when selecting properties for a Data Transfer Object (DTO). This ensures consistency and compatibility with the consuming components. In general, when designing a DTO in .NET, you can use a class or a struct—but in modern C#, using a record type is often preferred due to its immutability and built-in value semantics

Group related DTOs: When multiple DTOs have common properties, you should group them together. Consider creating a base DTO class and derive specific DTOs from it to promote code reuse and simplify maintainability.

Summary

While implementing a DTO in ASP.NET Core may take some initial effort, the long-term benefits in security, performance, and maintainability far outweigh the costs. Using DTOs, you can control what information should be transferred over the wire thus enhancing security of your application. By mapping DTOs to the appropriate models in your application you can customize the way data is prеsеntеd without changing the underlying database structure. This allows you to build APIs that expose only the data your clients require, giving you far greater control and flexibility. 

Frequently asked questions 

What are the main advantages of using a DTO in .NET Core? 

DTOs (Data Transfer Objects) help decouple internal domain models from what’s exposed over the API. This provides better control over data serialization, enhances security by avoiding over-posting, and simplifies versioning. In large-scale .NET Core applications, DTOs also improve maintainability by enforcing clear boundaries between layers. 

How do I create a DTO in an ASP.NET Core application? 

To create a DTO in ASP.NET Core, define a plain C# class with only the properties required by the client or API consumer. Avoid including business logic or data annotations not meant for external use. Then use this class as the type for request or response models in your controller actions. For example: 

public class ProductDto 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

Can AutoMapper be used for mapping complex objects to DTOs? 

Yes, AutoMapper is a powerful tool for mapping domain models to DTOs and vice versa. It supports nested object mapping, conditional logic, and custom converters. In ASP.NET Core, AutoMapper can be registered via dependency injection and configured to handle even complex object hierarchies with minimal boilerplate code. 

What are the common mistakes to avoid when using DTOs? 

Common pitfalls include: 

  • Overloading DTOs with business logic
  • Exposing domain entities directly as DTOs
  • Creating one DTO per entity without considering use-case variations
  • Ignoring naming consistency between DTOs and their usage context 

These mistakes reduce the benefits of abstraction and can lead to tight coupling and brittle APIs. 

How do DTOs improve API performance? 

DTOs allow you to shape the data sent over the network, including only the necessary fields and flattening complex structures. This minimizes payload size, reduces serialization overhead, and improves response times. When working with large datasets in ASP.NET Core Web APIs, well-designed DTOs can significantly improve efficiency—especially when combined with tools like AutoMapper and pagination. 

 
Anastasiia Lijnis Huffenreuter
Anastasiia Lijnis Huffenreuter
A true connectivity enthusiast, always on the lookout for smarter ways to link platforms and systems. Passionate about sharing the latest solutions and best practices to help you set up seamless, efficient integrations.
RELATED ARTICLES

Whitepaper

Social

Topics

Products