Skip to main content
C#

How to use Mapster with .NET 7 - A Quick Introduction

Learn how to use Mapster with C# and .NET in this short but explanatory tutorial.

β€” Christian Schou

Are you also facing a repetitive task, where you have to map all properties on a model to another over and over again? In today's software development, we often have to map objects across layers in our applications and that can be quite a big task when the application is growing.

Here is an example, and I am sure you know the struggle. πŸ˜…

public IActionResult CreateBook(BookDto bookDto)
{
    Book book = new()
    {
        Title = dto.Title,
        Author = dto.Author,
        ...
        ...
        ...
        Pages = dto.Pages,
        ISBN = dto.ISBN,
        ...
        ...
        Publisher = dto.Publisher
    };
 
    ...
    return Ok("Book was created");
}

I have helped out customers, etc... fixing this issue, but one thing that always comes to my mind – Why would someone implement this manual mapping in the first place? It's a boring task, and time-consuming and you have to do it all places where you are working with the data transfer objects (DTOs) and domain models.

Instead, we can use something like Mapster or AutoMapper. Both are mapping libraries we can use to make this mapping happen. They are fast and we only have to maintain the mapping configuration/profile in one place.

In this quick introduction to Mapster, I will show you the essentials and how you can use Mapster to map between objects in your application blazingly fast. πŸ”₯ By the end of this tutorial you will find a link to the full repository and you will be able to make your own custom mappings using Mapster.

Requirements

To follow along in this tutorial you should be familiar with C# and .NET. You do not have to be an advanced programmer, but I recommend that you have a basic understanding of objects, etc... Also you should have an IDE or code editor.

Step 1 - Create A New Web API With .NET

If you are implementing Mapster in a current project, you don't have to perform this step. I will start from scratch and create a new .NET Core Web API running on .NET 7.

Create ASP.NET Core Web API, visual studio
Create ASP.NET Core Web API

Give the project a name you prefer, and create it. You should now have a blank Web API project like mine below.

visual studio IDE
Fresh ASP.NET Core Web API Project

Awesome! Let's get to the fun part and write some code. ✌️ Before you start implementing anything, you can delete the WeatherForecast controller and model.

Step 2 - Create The Book Models

Before we create the DTO for our Book I would like to have the Book model in place. At the root of your project, right-click and create a new folder named Models.

Inside the Models folder, create a new class and name it Book. This class holds all the details for our book in the system. Below is the implementation including a override method for ToString() on the Book class.

using System.ComponentModel.DataAnnotations;

namespace TwcMapster.Models
{
    public class Book
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; } = string.Empty;

        public string Author { get; set; } = string.Empty;

        public int ReleaseYear { get; set; }

        public string Publisher { get; set; } = string.Empty;

        public double Price { get; set; }

        public string Currency { get; set; } = "USD";

        public int CategoryId { get; set; }
        public Category Category { get; set; }

        // Method to print the book details
        public override string ToString()
        {
            return "The details of the book are: " + Title + ", " + Author + ", " + ReleaseYear + ", " + Publisher + ", " + Price;
        }
    }
}

Now create a new class and name it Category and add the following code inside that class.

using System.ComponentModel.DataAnnotations;

namespace TwcMapster.Models
{
    public class Category
    {
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public List<Book> Books { get; set; }
    }
}

Perfect! Now we got the models in place. You can also see them at the link below if you need further details.

Twc-Mapster-Demo/Models at master Β· Christian-Schou/Twc-Mapster-Demo
Mapster Demonstration in .NET 7 Web API with generic mapper between models and DTOs - Christian-Schou/Twc-Mapster-Demo

In the next section, we will install Mapster in our Web API project.

Step 3 - Install Mapster In The Project

It's fairly straightforward to add/install a new package/NuGet in .NET/C# projects. Open your Package Manager Console and type the following. Hit ENTER when you are done.

PM> Install-Package Mapster
GitHub - MapsterMapper/Mapster: A fast, fun and stimulating object to object Mapper
A fast, fun and stimulating object to object Mapper - GitHub - MapsterMapper/Mapster: A fast, fun and stimulating object to object Mapper

A Quick Introduction to Mapster

If you would like to make from one object to a new one, this is how you do it. πŸ‘¨β€πŸ’»

var destinationObject = sourceObject.Adapt<Destination>();

If you would like to map from one object to an existing one, you can do the following.

sourceObject.Adapt(destinationObject);

If you would like to map entities from your database or simply a list of data, you can do so by calling the ProjectToType<TDestination>.ToList(). See my example below.

List<Destination> projectedItems = data.Books.ProjectToType<Destination>().ToList();

With that in place, let's move on and create a base DTO that will provide the methods we need when making custom mapping, etc... πŸ™Œ

Step 4 - Create A Base DTO

Create a new folder named DTOs at the root of your project, and add a new BaseDto class inside that folder. The BaseDto class has the following logic. I will explain below.

using Mapster;

namespace TwcMapster.DTOs
{
    /// <summary>
    /// Base Data Transfer Object for model mapping.
    /// </summary>
    /// <typeparam name="TDto">The type of the DTO.</typeparam>
    /// <typeparam name="TModel">The type of the Model.</typeparam>
    public abstract class BaseDto<TDto, TModel> : IRegister
        where TDto : class, new()
        where TModel : class, new()
    {
        /// <summary>
        /// Configuration of type adapter.
        /// </summary>
        private TypeAdapterConfig Config { get; set; }

        /// <summary>
        /// Adds custom mappings to the configuration.
        /// </summary>
        public virtual void AddCustomMappings() { }

        /// <summary>
        /// Sets custom mappings for dto to model.
        /// </summary>
        /// <returns>The type adapter setter.</returns>
        protected TypeAdapterSetter<TDto, TModel> SetCustomMappings() => Config.ForType<TDto, TModel>();

        /// <summary>
        /// Sets custom mappings for model to dto.
        /// </summary>
        /// <returns>The type adapter setter.</returns>
        protected TypeAdapterSetter<TModel, TDto> SetCustomMappingsReverse() => Config.ForType<TModel, TDto>();

        /// <summary>
        /// Registers the type adapter configuration and adds custom mappings.
        /// </summary>
        /// <param name="config">The configuration of the type adapter.</param>
        public void Register(TypeAdapterConfig config)
        {
            Config = config;
            AddCustomMappings();
        }

        /// <summary>
        /// Maps DTO to an existing Model.
        /// </summary>
        /// <returns>The Model.</returns>
        public TModel ToModel()
        {
            return this.Adapt<TModel>();
        }

        /// <summary>
        /// Maps DTO to an existing Model.
        /// </summary>
        /// <param name="model">The existing Model instance to be updated.</param>
        /// <returns>The updated Model instance.</returns>
        public TModel ToModel(TModel model)
        {
            return (this as TDto).Adapt(model);
        }

        /// <summary>
        /// Maps a Model to a DTO.
        /// </summary>
        /// <param name="model">The Model to be mapped.</param>
        /// <returns>The DTO.</returns>
        public static TDto FromModel(TModel model)
        {
            return model.Adapt<TDto>();
        }
    }
}

WhatΒ΄s happening in the code above? πŸ€”

Class Declaration

public abstract class BaseDto<TDto, TModel> : IRegister This declares an abstract class named BaseDto which takes two generic types: TDto and TModel. The class also implements the IRegister interface, which is a part of Mapster for configuration registration.

Type Constraints

  • where TDto : class, new() - This constraint ensures that the generic type TDto is a reference type (class) and has a parameterless constructor (new()).
  • where TModel : class, new() - Similarly, this constraint ensures that TModel is a reference type with a parameterless constructor.

Properties

private TypeAdapterConfig Config { get; set; } - This is a private property that holds the configuration for type mapping. This configuration is used by Mapster for mapping operations.

Methods

  • AddCustomMappings() - An overridable method for adding custom mappings. In derived classes, you can provide specific implementation to define how certain properties map between TDto and TModel.
  • SetCustomMappings() and SetCustomMappingsReverse() - These protected methods are used to set custom mappings for DTO-to-Model and Model-to-DTO respectively. They use the Config property to specify the source and destination types.
  • Register(TypeAdapterConfig config) - A method to register a type adapter configuration and add custom mappings. This method assigns the passed configuration to the Config property and then calls AddCustomMappings().
  • ToModel() - Converts the current DTO instance to a model of type TModel.
  • ToModel(TModel model) - Maps the current DTO instance to an existing model, effectively updating the model with values from the DTO.
  • FromModel(TModel model) - A static method that converts a given model of type TModel to its equivalent DTO representation of type TDto.

Awesome! Let's use the BaseDto on the BookDto we will create in the next section.

Step 5 - Create The Book DTO

Now it's time to create our BookDto. Do that in the same folder as the BaseDto and add the following code inside. I will explain it below.

namespace TwcMapster.DTOs
{
    /// <summary>
    /// Book DTO
    /// </summary>
    public class BookDto : BaseDto<BookDto, Book>
    {
        /// <summary>
        /// Book Title
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Book Author
        /// </summary>
        public string Author { get; set; }

        /// <summary>
        /// Book Release Year
        /// </summary>
        public int ReleaseYear { get; set; }

        /// <summary>
        /// Book Publisher
        /// </summary>
        public string Publisher { get; set; }

        /// <summary>
        /// Book Price. This is made up by the custom mapping.
        /// </summary>
        public string Price { get; set; }

        /// <summary>
        /// Category ID
        /// </summary>
        public int CategoryId { get; set; }

        /// <summary>
        /// Category Name
        /// </summary>
        public string CategoryName { get; set; }

        /// <summary>
        /// Add the custom mappings for the DTO.
        /// The AddMapster in Program.cs will look for this in order to add the mappings.
        /// </summary>
        public override void AddCustomMappings()
        {
            // Mapster can map properties with different names
            // Here we split the price into two properties for the model behind the DTO
            SetCustomMappings()
                .Map(dest => dest.Price,
                     src => Convert.ToDouble(src.Price.Split(' ', StringSplitOptions.None)[0]))
                .Map(dest => dest.Currency,
                     src => src.Price.Split(' ', StringSplitOptions.None)[1]);

            // Mapping from model to DTO
            SetCustomMappingsReverse()
                .Map(dest => dest.Title, src => src.Title)
                .Map(dest => dest.Author, src => src.Author)
                .Map(dest => dest.ReleaseYear, src => src.ReleaseYear)
                .Map(dest => dest.Publisher, src => src.Publisher)
                .Map(dest => dest.Price, src => $"{src.Price} {src.Currency}")
                .Map(dest => dest.CategoryName, src => src.Category.Name);
        }
    }
}

What happens in the code above? πŸ€”

Class Declaration

public class BookDto : BaseDto<BookDto, Book> - This declares a public class named BookDto which inherits from the BaseDto class we created before. The class specifies BookDto as the DTO type and Book as the model type.

Properties

  • Each of the properties (Title, Author, ReleaseYear, Publisher, Price, CategoryId, and CategoryName) corresponds to a piece of data about a book.
  • The Price property is especially notable. This one is constructed through custom mapping and will hold values like "20 USD" (with both the amount and the currency).

Method Override - AddCustomMappings

This method is overridden from the base class and is designed to provide custom mappings between the BookDto and Book model.