Introduction
This article presents a discussion on AutoMapper, why it is useful, and then shows how we can use AutoMapper to eliminate the need of writing tedious boilerplate code. It illustrates how dotConnect for Oracle can be used to connect to an Oracle database, read data from the database and store it in an in-memory collection of objects.
Finally, AutoMapper is used to convert these models to data transfer objects and then the collection of data transfer objects is sent back to the client.
Pre-requisites
To be able to work with the code examples demonstrated in this article, you should have the following installed in your system:
- Visual Studio 2019 Community Edition (or later)
- .NET 5 (or later)
- Oracle database
- dotConnect for Oracle
You can download Visual Studio 2019 from here.
You can download Oracle Express Edition from here.
You can download a copy of dotConnect for Oracle from here.
Why do we need to Map Business Objects to DTOs?
When working on applications, you might typically want to map objects pertaining to similar or dissimilar types. Here’s why you often need this: The models (also called entities) in an application typically map with the database tables of the database in use. So, if you have a model class named Product, it will have properties that correspond to each of the fields of the Product table in the database. However, not all fields of the Product class might be needed in the presentation layer. You may just want to have three properties such as Id, Name, and Price in the product entity in the presentation layer of your application.
When you would like to send data to the presentation layer from the business layer of your application, you might need additional properties that don’t match with the models defined in the application. As an example, you might need a few additional properties for your product entity in the presentation layer such as ReorderLevel, ReorderValue, and NetPrice that are absent in the Product class.
So, there often exists a mismatch between the properties of the model classes and those of the data transfer object classes. This might create problems for you since you might need to write a lot of boilerplate code to convert instances of incompatible types. If you’re working on a large application such as an ERP, you might need many models and data transfer object classes. It would be cumbersome to write code to do the mapping manually. Here’s where tools like AutoMapper helps.
What is AutoMapper?
AutoMapper is a ubiquitous, simple, convention-based object-to-object mapping library compatible with.NET Core. It is adept at converting an input object of one kind into an output object of a different type. You can use it to map objects of incompatible types. You can take advantage of AutoMapper to save the time and effort needed to map the properties of incompatible types in your application manually.
You can use AutoMapper to map any set of classes, but the properties of those classes have identical names. If the property names don't match, you should manually write the necessary code to let AutoMapper know which properties of the source object it should map to in the destination object.
Create a new ASP.NET Core Web API Project
Assuming that the necessary software has been installed in your computer to be able to work with Entity Developer, follow the steps outlined below to create a new ASP.NET Core Web API project.
- First off, open the Visual Studio 2019 IDE
- Next, click "Create a new project" once the IDE has loaded
- Click "Create a new project"
- Next, select "ASP.NET Core Web Application"
- Click the "Next" button
- Specify the project name and location - where it should be stored in your system
- Optionally, click the "Place solution and project in the same directory" checkbox.
- Next, click the "Create" button
- In the "Create a new ASP.NET Core Web Application" dialog window that is shown next, select "API" as the project template.
- Select ASP.NET Core 3.1 or later as the version.
- You should disable the "Configure for HTTPS" and "Enable Docker Support" options by disabling the respective checkboxes.
- Since we'll not be using authentication in this example, specify authentication as "No Authentication".
- Finally, click on the "Create" button to finish the process.
We’ll use this project throughout this article to demonstrate how to work with AutoMapper.
Install NuGet Packages
To get started you should install AutoMapper, and dotConnect for Oracle package(s) in your project. You can install these packages either from the NuGet Package Manager tool inside Visual Studio or, from the NuGet Package Manager console using the following commands:
PM> Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
PM> Install-Package dotConnect.Express.for.Oracle
Note that once you install the AutoMapper.Extensions.Microsoft.DependencyInjection package, AutoMapper will be installed to your project automatically as well. If the installation is successful, you’re all set to get started.
Read Data from the Product Table
Create a new file named Product.cs and write the following code in there:
namespace AutoMapperDemo {
public class Product {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
public string Description {
get;
set;
}
public double Discount {
get;
set;
}
public double Price {
get;
set;
}
}
}
The following code snippet illustrates how you can take advantage of dotConnect for Oracle to read data from the Product database table and return a list of product records.
public List < Product > GetProducts() {
List < Product > products = new List < Product > ();
string connectionString = "Write your connection string here...";
using(OracleConnection oracleConnection = new OracleConnection(connectionString)) {
OracleCommand oracleCommand = new OracleCommand();
oracleCommand.CommandText = "SELECT * FROM product";
oracleCommand.Connection = oracleConnection;
using(OracleDataReader oracleDataReader = oracleCommand.ExecuteReader()) {
while (oracleDataReader.Read()) {
Product product = new Product();
product.Id = int.Parse(oracleDataReader.GetValue("id").ToString());
product.Name = oracleDataReader.GetValue("name").ToString();
product.Description = oracleDataReader.GetValue("description").ToString();
product.Discount = double.Parse(oracleDataReader.GetValue("discount").ToString());
product.Price = double.Parse(oracleDataReader.GetValue("price").ToString());
products.Add(product);
}
}
}
return products;
}
Convert a List of Models to a List of DTOs
Create a new file named ProductDTO.cs and write the following code in there:
namespace AutoMapperDemo {
public class ProductDTO {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
public string Description {
get;
set;
}
public double Discount {
get;
set;
}
public double Price {
get;
set;
}
}
}
You can write the following code to create a map between the Product and ProductDTO classes:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap < Product, ProductDTO > ();
});
Now that the mapping between the Product and ProductDTO types have been established, you can write the following code to create an instance of ProductDTO from an instance of the Product class.
IMapper iMapper = config.CreateMapper();
var sourceObject = new Product();
var destinationObject = iMapper.Map<Product, ProductDTO>(sourceObject);
And with the mapping configuration in place, you can also write the following code to convert a list of Products to a list of ProductDTOs.
var records = dbManager.GetAllProducts();
var data = iMapper.Map<List<Product>, List<ProductDTO>>(records);
Specify Mapping Configuration using Profiles
Another way to specify mapping configuration in AutoMapper is by using profiles. To do this, you need to create a class that extends the Profile class of the AutoMapper library.
using AutoMapper;
namespace AutomapperDemo {
public class ProductProfile: Profile {
public ProductProfile() {
CreateMap < Product, ProductDTO > ();
}
}
}
Next, you should specify the mapper configuration in the ConfigureServices method of the Startup class, create a mapper instance and add it as a singleton service:
var mapperConfig = new MapperConfiguration(mc => {
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mapperConfig.CreateMapper();
services.AddSingleton(mapper);
Using AutoMapper in the Controller Classes
You can then leverage dependency injection in your controller class to retrieve an instance of IMapper as shown in the code snippet given below:
private readonly IMapper _mapper;
public ProductController(IMapper mapper) {
_mapper = mapper;
}
Here's the complete source code of the ProductController for your reference:
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace AutomapperDemo.Controllers {
[ApiController]
[Route("[controller]")]
public class ProductController: ControllerBase {
private readonly IMapper _mapper;
public ProductController(IMapper mapper) {
_mapper = mapper;
}
[HttpGet]
public ActionResult < List < ProductDTO >> GetProducts() {
DbManager dbManager = new DbManager();
var records = dbManager.GetAllProducts();
return _mapper.Map < List < Product > , List < ProductDTO >> (records);
}
}
}
Summary
AutoMapper is a great tool to map your business objects to data transfer objects, eliminate the need of writing boilerplate code and hence reduce the clutter in your application’s code. You should use AutoMapper when you need a complex mapping of incompatible types in your application.