Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API

Mapping Complex Type to Primitive Type using AutoMapper

In this article, I will discuss Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API Application with Examples. Please read our previous article discussing AutoMapper Complex Mapping in ASP.NET Core Web API with Examples. Now, we will understand how to use AutoMapper in an ASP.NET Core Web API project to map between a complex type (an object with its own properties) and primitive types (simple data types like strings, ints, booleans).

What Is a Complex Type?

A complex type is a class or object that contains multiple properties (which themselves can be primitive or complex). For example, an Address class with properties such as Street, City, State, and Zip Code is a complex type. It is complex because it encapsulates multiple pieces of related data.

What are Primitive Types?

Primitive types are the basic data types such as int, string, bool, double, etc. They hold a single value and do not have sub-properties. They are used to represent simple, singular data like numbers, text, or Boolean conditions.

Why Map Complex to Primitive (and vice versa)?

In many real-world applications, we might have Entity classes that include nested objects (complex types). However, the data you expose or consume via API (in your DTOs) might need to flatten out those complex objects into simpler, separate fields (i.e., multiple primitive properties). At the same time, when you receive user input that is in simpler, separate fields, you often want to transform it into a complex object before saving it to the database.

Example to Understand Complex to Primitive (and vice versa) using AutoMapper:

Let’s understand the mapping of Complex Type to Primitive Type and Primitive Type to Complex Type using AutoMapper with ASP.NET Core Web API and Entity Framework Core, using one real-time application. We will create a User Management System where each user has an address. One entity (User) holds a complex type (Address). We want to do two kinds of mappings:

  • Complex Type to Primitive Type: When reading user data, we extract the individual address properties (primitive) from the Address complex type into a Data Transfer Object (DTO) called UserDTO.
  • Primitive to Complex Type: When creating a new user, we receive a DTO (UserCreateDTO) that contains primitive properties for address information. Then, we map these primitives to create a complex Address object within the User entity.
Setting Up the Project and Installing Entity Framework Core

First, create a new ASP.NET Core Web API Application named AutomapperDemo. Once you create the project, please install the Entity Framework Core and Automapper Packages by executing the following command in the Package Manager Console.

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.Tools
  • Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
Defining Entities and DTOs:

First, create two folders named Models and DTOs in the project root directory, where we will create all our Entities and DTOs.

User Model:

Create a class file named User.cs within the Models folder and then copy and paste the following code. This model represents a user and contains a navigation property for the Address complex type.

using System.ComponentModel.DataAnnotations;
namespace AutomapperDemo.Models
{
    // The User entity contains basic user details and a complex Address property.
    public class User
    {
        public int UserId { get; set; }
        [Required]
        public string FirstName { get; set; }
        [Required]
        public string LastName { get; set; }
        [Required, EmailAddress]
        public string Email { get; set; }
        [Phone]
        public string PhoneNumber { get; set; }
        // Complex type: Address object contains detailed address information.
        public Address Address { get; set; }
    }
}
Address Model:

Create a class file named Address.cs within the Models folder and then copy and paste the following code. This model represents the address information.

using System.ComponentModel.DataAnnotations;
namespace AutomapperDemo.Models
{
    // The Address entity holds detailed address data and is a complex type.
    public class Address
    {
        public int AddressId { get; set; }

        [Required]
        public string Street { get; set; }
        [Required]
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
        public int UserId { get; set; } // Foreign Key
        public User User { get; set; }
    }
}
UserDTO (Data Transfer Object for mapping Complex to Primitive)

DTOs are used to shape the data sent to or from the client. Create a class file named UserDTO.cs within the DTOs folder and then copy and paste the following code. This DTO is used when retrieving data. It maps the complex Address type into primitive properties.

namespace AutomapperDemo.DTOs
{
    // DTO for sending User details along with individual address properties (primitive types).
    public class UserDTO
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set; }

        // Address properties are primitive types (string) even though they come from a complex Address object.
        public int AddressId { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
    }
}
UserCreateDTO (Data Transfer Object for mapping Primitive to Complex type)

Create a class file named UserCreateDTO.cs within the DTOs folder, and then copy and paste the following code. This DTO is used when creating a new user. It supplies primitive properties that will be mapped to the complex Address type.

using System.ComponentModel.DataAnnotations;

namespace AutomapperDemo.DTOs
{
    // DTO for creating a new User. The address details are provided as primitive types.
    public class UserCreateDTO
    {
        [Required]
        public string FirstName { get; set; }

        [Required]
        public string LastName { get; set; }

        [Required, EmailAddress]
        public string Email { get; set; }

        [Phone]
        public string PhoneNumber { get; set; }

        // These are primitive properties that will be mapped into a complex Address object.
        [Required]
        public string Street { get; set; }

        [Required]
        public string City { get; set; }

        public string State { get; set; }
        public string ZipCode { get; set; }
    }
}
Create the AutoMapper Mapping Profile

The mapping profile shows how to map from a User (with its complex Address) to a UserDTO (with primitive address properties) and vice versa. So, create a class file named UserMappingProfile.cs within the MappingProfiles folder and copy and paste the following code. The following code is self-explained, so please read the comment lines for a better understanding.

using AutomapperDemo.DTOs;
using AutomapperDemo.Models;
using AutoMapper;
namespace AutomapperDemo.MappingProfiles
{
    public class UserMappingProfile : Profile
    {
        public UserMappingProfile()
        {
            // Complex -> Primitive (User -> UserDTO)
            // Map the Address object to separate primitive fields
            // Mapping from User (with complex Address) to UserDTO (with primitive address properties)
            // Here, source is the User object and destination is the UserDTO object
            CreateMap<User, UserDTO>()
                // For each property in UserDTO that represents an Address field,
                // map it from the corresponding property in the Address complex type.
                .ForMember(dest => dest.Street, opt => opt.MapFrom(src => src.Address.Street))
                .ForMember(dest => dest.City, opt => opt.MapFrom(src => src.Address.City))
                .ForMember(dest => dest.State, opt => opt.MapFrom(src => src.Address.State))
                .ForMember(dest => dest.ZipCode, opt => opt.MapFrom(src => src.Address.ZipCode));

            // Primitive -> Complex (UserCreateDTO -> User)
            // Map separate primitive fields to the complex Address object
            // Mapping from UserCreateDTO (with primitive properties) to User (which contains a complex Address object)
            // Here, source is the UserCreateDTO object and destination is the User object
            CreateMap<UserCreateDTO, User>()
                // When creating a new User, construct the Address object from the primitive properties in UserCreateDTO.
                .ForMember(dest => dest.Address, opt => opt.MapFrom(src => new Address
                {
                    Street = src.Street,
                    City = src.City,
                    State = src.State,
                    ZipCode = src.ZipCode
                }));
        }
    }
}
Code Explanation:
  • CreateMap<User, UserDTO>: When retrieving user data from the database, the Address property on the User object is a complex type, but in UserDTO, we have primitive fields (Street, City, etc.). Hence, we map each field (src.Address.Street) to the corresponding property (dest.Street).
  • CreateMap<UserCreateDTO, User>: When creating a new user, the source (UserCreateDTO) only has primitive fields like Street or City. We create a new Address object and map those fields accordingly to fill the Address property on the User.
Create a DbContext Class

First, create a folder called Data, and then inside the Data folder, create a class file named UserDbContext.cs, then copy and paste the following code. The UserDbContext class sets up the Entity Framework Core connection and includes some seed data for testing.

using AutomapperDemo.Models;
using Microsoft.EntityFrameworkCore;

namespace AutomapperDemo.Data
{
    public class UserDbContext : DbContext
    {
        public UserDbContext(DbContextOptions<UserDbContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Seed a user for testing purposes.
            modelBuilder.Entity<User>().HasData(
                new User
                {
                    UserId = 1,
                    FirstName = "Pranaya",
                    LastName = "Rout",
                    Email = "pranaya.rout@example.com",
                    PhoneNumber = "123-456-7890"
                }
            );

            // Seed the address related to the above user.
            modelBuilder.Entity<Address>().HasData(
                new Address
                {
                    AddressId = 1,
                    Street = "123 Main St",
                    City = "BBSR",
                    State = "Odisha",
                    ZipCode = "755019",
                    UserId = 1
                }
            );
        }

        public DbSet<User> Users { get; set; }
        public DbSet<Address> Addresses { get; set; }
    }
}
Configure the Database Connection

Next, please update the appsettings.json with the database connection string as follows:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "UsersDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=UsersDB;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}
Set Up AutoMapper, Dependency Injection and Middleware:

Set up dependency injection for AutoMapper, Entity Framework Core, and configure middleware. So, please modify the Program class as follows.

using AutomapperDemo.Data;
using Microsoft.EntityFrameworkCore;

namespace AutomapperDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Register AutoMapper and the mapping profile
            builder.Services.AddAutoMapper(typeof(Program).Assembly);

            // Add services to the container.
            builder.Services.AddControllers()
                // Optionally, configure JSON options or other formatter settings
                .AddJsonOptions(options =>
                {
                    // Configure JSON serializer settings to keep the Original names in serialization and deserialization
                    options.JsonSerializerOptions.PropertyNamingPolicy = null;
                });

            // Register the ProductDbContext with dependency injection
            builder.Services.AddDbContext<UserDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("UsersDBConnection")));

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Creating and Applying Database Migration:

Open the Package Manager Console and Execute the Add-Migration and Update-Database commands as follows to generate the Migration file and then apply the Migration file to create the UsersDB database and required Users and Addresses tables:

Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API

Once you execute the above commands and verify the database, you should see the UsersDB database with the required Users and Addresses tables, as shown in the image below.

Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API

Create the Users API Controller

So, create an API Empty Controller named UsersController within the Controllers folder and copy and paste the following code. This controller exposes endpoints to retrieve users (mapping complex type to primitives) and create a new user (mapping primitives to a complex type).

using AutoMapper;
using AutomapperDemo.Data;
using AutomapperDemo.DTOs;
using AutomapperDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace AutomapperDemo.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UserController : ControllerBase
    {
        private readonly IMapper _mapper;
        private readonly UserDbContext _context;

        public UserController(IMapper mapper, UserDbContext context)
        {
            _mapper = mapper;
            _context = context;
        }

        // GET: api/user/GetUsers
        // Retrieves all users.
        // Maps from complex User (with Address) to UserDTO (with primitive properties).
        [HttpGet("GetUsers")]
        public async Task<ActionResult<List<UserDTO>>> GetUsers()
        {
            List<User> users = await _context.Users
                .AsNoTracking()
                .Include(u => u.Address)
                .ToListAsync();

            if (users == null || users.Count == 0)
            {
                return NotFound("No users found.");
            }

            var userDTOs = _mapper.Map<List<UserDTO>>(users);
            return Ok(userDTOs);
        }

        // GET: api/user/GetUserById/{id}
        // Retrieves a specific user by ID.
        [HttpGet("GetUserById/{id}")]
        public async Task<ActionResult<UserDTO>> GetUserById(int id)
        {
            var user = await _context.Users
                .AsNoTracking()
                .Include(u => u.Address)
                .FirstOrDefaultAsync(u => u.UserId == id);

            if (user == null)
            {
                return NotFound($"User with ID {id} not found.");
            }

            // Map User entity to UserDTO
            var userDTO = _mapper.Map<UserDTO>(user);
            return Ok(userDTO);
        }

        // POST: api/user/CreateUser
        // Creates a new user.
        // Maps from UserCreateDTO (primitive properties) to User (with a complex Address).
        [HttpPost("CreateUser")]
        public async Task<ActionResult<UserDTO>> CreateUser([FromBody] UserCreateDTO userCreateDTO)
        {
            // Map UserDTO to User entity
            User user = _mapper.Map<User>(userCreateDTO);
            _context.Users.Add(user);

            // Save the new user to the database
            await _context.SaveChangesAsync();

            // Map User entity to UserDTO
            var userDTO = _mapper.Map<UserDTO>(user);

            // Returns the created user
            return Ok(userDTO);
        }
    }
}
Testing the Endpoints

Now, run the application and test the functionality, and it should work as expected. First, run the application, and  then you can test the endpoints as follows:

Retrieve All Users

Method: GET
URL: https://localhost:{port}/api/user/GetUsers
Expected Response: This will return a list of users and their address details (each address property is now a primitive type in the JSON response).

Mapping Complex Types to Primitive Types using AutoMapper in ASP.NET Core Web API with Examples

Retrieve a Specific User by ID

Method: GET
URL: https://localhost:{port}/api/user/GetUserById/1
Expected Response: Replace 1 with the desired user ID. This returns the specific user’s details.

Example to Understand Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API

Create a New User

Method: POST
URL: https://localhost:{port}/api/user/CreateUser
Headers: Content-Type: application/json
Body (raw JSON):

{
    "FirstName": "John",
    "LastName": "Doe",
    "Email": "john.doe@example.com",
    "PhoneNumber": "555-1234",
    "Street": "456 Elm Street",
    "City": "Metropolis",
    "State": "NY",
    "ZipCode": "10001"
}

Expected Response: This creates a new user. The AutoMapper configuration will map the provided primitive address properties into a new Address object, which is then saved as part of the User entity. After a successful POST request, Postman will return a 200 Ok response and the newly created user details.

Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API

This example should give you a clearer understanding of converting between complex types and primitive types using AutoMapper in an ASP.NET Core Web API.

In the next article, I will discuss AutoMapper Reverse Mapping in ASP.NET Core Web API with Examples. In this article, I explain Mapping Complex Types to Primitive Types using AutoMapper in ASP.NET Core Web API with Examples. I hope you enjoy this article, “Mapping Complex Types to Primitive Types using AutoMapper in ASP.NET Core Web API.”

Leave a Reply

Your email address will not be published. Required fields are marked *