Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Mapster - Your Next Level Object to Object Mapping Tool

0.00/5 (No votes)
21 Jun 2018 1  
Mapster a fast, fun and stimulating object to object mapper

I was working on a NFR (Non-Functional Requirement) for my project. And I found that my developers have not correctly implemented object to object mapping across the layers of the application. And I had a question, what is the best way I can map one object to another in the most maintainable manner so that in the project we can leverage it thereby improving the performance. And I started exploring on the mapping frameworks which are available in the Web. I found many interesting ones like AutoMapper, SharpMap, ExpressMapper, etc. I found most of the people were using and recommending AutoMapper mapping framework.

But the one which really caught my attention was Mapster. Mapster is light-weight, fast, fun and stimulating object to object mapper, simple to use in the application and performs better than other popular frameworks like AutoMapper and ExpressMapper.

So let’s drive into how we can use the Mapster. As you might be aware, most mappers can do a task in a number of different and most convenient ways for us so does the Mapster.

1st Usage: Source object directly mapped to a destination object:

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

2nd Usage: Using Dependency Injection:

IAdapter adapter = new Adapter();

var destinationObject = adapter.Adapt<TDestination>(sourceObject);

3rd Usage: Using static method:

var destinationObject = TypeAdapter.Adapt<TDestination>;

The real advantage of using Mapster is its performance. It is 2.5 times faster than AutoMapper. And twice as fast to the other known mappers frameworks.

Ok, I know that the above theory is bit boring, let’s dive into coding. I have used VS 2017 with ASP.NET Core 2.0 Web Application (Web API) with Mapster version 3.1.8. It’s not necessary that you must use VS 2017 or .NET core application. As I said, I am working on a project and that project is into .NET core so I have used it. You can have any IDE which supports .NET Framework 4.0 and above. Mapster requires .NET Framework 4.0.

And also, I am alternatively creating a .NET Core Web API application which gives details steps involved in having Angular 5 with it and I want to make this article as a base for it 😊. At a high-level, this application used by the unit HR to track associate bench period and what activities which they have done while in the bench.

Further adding to the above, this article is only to give details on usage of Mapster. I have knowingly ignored other details like .NET Core, Runtime for .NET Core, etc., as they will be dealt with in my next article which I have mentioned above.

Let’s start creating.

Step 1

Create Visual Studio .NET Core Web API Application and name it Bechnoics. After creating your project:

Click OK. Build the application and Click F5 just to ensure that the application is up and running as expected. If everything is OK, you will see the below screen. By default, when you create a Web API, VS will provide us with Values controller.

Step 2

Set up the necessary classes. Before that, add Mapster Nuget package.

Step 2a

Create a folder called β€œModels” and class name Employee.cs. It should be like below:

using System;
namespace BechnoicsAPI.Models
{
    /// <summary>
    /// Employee Class
    /// </summary>
    public class Employee
    {
        /// <summary>
        /// Gets or sets the identifier.
        /// </summary>
        /// <value>
        /// The identifier.
        /// </value>

        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        /// <value>
        /// The first name.
        /// </value>

        public string FirstName { get; set; }
        /// <summary>
        /// Gets or sets the name of the family.
        /// </summary>
        /// <value>
        /// The name of the family.
        /// </value>

        public string FamilyName { get; set; }
        /// <summary>
        /// Gets or sets the start date at bench.
        /// </summary>
        /// <value>
        /// The start date at bench.
        /// </value>

        public DateTime StartDateAtBench { get; set; }
        /// <summary>
        /// Gets or sets the end date at bench.
        /// </summary>
        /// <value>
        /// The end date at bench.
        /// </value>

        public DateTime? EndDateAtBench { get; set; }
        /// <summary>
        /// Gets or sets the skillset.
        /// </summary>
        /// <value>
        /// The skillset.
        /// </value>
        public string Skillset { get; set; }
    }
}

Step 2b

Create a folder called β€œServices” and interface named β€œIEmployeeService” and class, EmployeeService which implements the interface. Below are the implementations:

using BechnoicsAPI.Models;
using System.Collections.Generic;
namespace BechnoicsAPI.Services
{
    /// <summary>
    /// Employee Service - API's
    /// </summary>
    public interface IEmployeeService
    {
        /// <summary>
        /// Gets all.
        /// </summary>
        /// <returns></returns>
        IEnumerable<Employee> GetAll();
        /// <summary>
        /// Gets the by identifier.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        Employee GetById(int id);
        /// <summary>
        /// Adds the specified model.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        bool Add(Employee model);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BechnoicsAPI.Models;
namespace BechnoicsAPI.Services
{
    /// <summary>
    /// Employee Service In-Memory Class
    /// </summary>
    /// <seealso cref="BechnoicsAPI.Services.IEmployeeService" />
    public class EmployeeMemoryService : IEmployeeService
    {
        /// <summary>
        /// The employees
        /// </summary>

        private readonly List<Employee> employees = new List<Employee>();
        /// <summary>
        /// Initializes a new instance of the <see cref="EmployeeMemoryService"/> class.
        /// </summary>

        public EmployeeMemoryService()
        {
            employees.Add(
                new Employee {
                    Id = 1,
                    FirstName = "Srinivasa Dinesh",
                    FamilyName = "Parupalli",
                    StartDateAtBench = new DateTime(2018, 05, 01),
                    EndDateAtBench = new DateTime(2018, 06,30),
                    Skillset = ".NET Tech Arch"});
        employees.Add(
                new Employee
                {
                    Id = 2,
                    FirstName = "Sridhar",
                    FamilyName = "Vemula",
                    StartDateAtBench = new DateTime(2018, 04, 17),
                    EndDateAtBench = new DateTime(2018, 06, 30),
                    Skillset = "Java"
                });
            employees.Add(
                new Employee
                {
                    Id = 3,
                    FirstName = "Chandra Sekhar",
                    FamilyName = "Laghuvarapu",
                    StartDateAtBench = new DateTime(2018, 05, 20),
                    EndDateAtBench = new DateTime(2018, 05, 30),
                    Skillset = "Agile"
                });
        }
        /// <summary>
        /// Adds the specified model.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        public bool Add(Employee model)
        {
            model.Id = employees.Max(c => c.Id) + 1;
            employees.Add(model);
            return true;
        }
        /// <summary>
        /// Gets all.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Employee> GetAll()
        {
            return employees.AsEnumerable();
        }
        /// <summary>
        /// Gets the by identifier.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        public Employee GetById(int id)
        {
            return employees.FirstOrDefault(c => c.Id == id);
        }
    }
}

Step 2c

Create a folded named β€œViewModels”. Classes in this folder would be used by the Controller. Create two classes named β€œEmployeeViewModel”, this class would be used for GET operations and β€œEmployeeCreateViewModel”, this class would be used for POST operation. Below are the implementations:

namespace BechnoicsAPI.ViewModels
{
    /// <summary>
    /// Employee View Model for Get
    /// </summary>
    public class EmployeeViewModel
    {
        /// <summary>
        /// Gets or sets the identifier.
        /// </summary>
        /// <value>
        /// The identifier.
        /// </value>

        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>

        public string Name { get; set; }
        /// <summary>
        /// Gets or sets the bench period in days.
        /// </summary>
        /// <value>
        /// The bench period in days.
        /// </value>

        public int BenchPeriodInDays { get; set; }
        /// <summary>
        /// Gets or sets the skillset.
        /// </summary>
        /// <value>
        /// The skillset.
        /// </value>

        public string Skillset { get; set; }
    }
}
using System;
namespace BechnoicsAPI.ViewModels
{
    /// <summary>
    /// Employee View Model for Creating Employees
    /// </summary>

    public class EmployeeCreateViewModel
    {
        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        /// <value>
        /// The first name.
        /// </value>

        public string FirstName { get; set; }
        /// <summary>
        /// Gets or sets the name of the family.
        /// </summary>
        /// <value>
        /// The name of the family.
        /// </value>

        public string FamilyName { get; set; }
        /// <summary>
        /// Gets or sets the start date at bench.
        /// </summary>
        /// <value>
        /// The start date at bench.
        /// </value>

        public DateTime StartDateAtBench { get; set; }
        /// <summary>
        /// Gets or sets the skillset.
        /// </summary>
        /// <value>
        /// The skillset.
        /// </value>

        public string Skillset { get; set; }
    }
}

Step 2d

The startup.cs needs to be modified at 3 places:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BechnoicsAPI.Models;
using BechnoicsAPI.Services;
using BechnoicsAPI.ViewModels;
using Mapster;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

We need to inject the employee service implementation which we have created. This is done at the ConfigureService(). Below are the implementation details:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSingleton<IEmployeeService, EmployeeMemoryService>();
        }

When we add our Employee Service, we are leveraging the .NET Core of built-in Dependency Injection feature. There are 3 ways of injecting:

  • AddSingleton: Object is created only once for the application life time
  • AddScoped: Object is lived until the request is completely handled
  • AddTransient: Object is created everytime the request is completely handled

Next is at the Configure(). At this method, we are defining any custom mapping which we need for our class while performing the object-to-object mapping. If you have noticed, definition for the Employee.cs and EmployeeViewModel.cs & EmployeeCreateViewModel.cs are different. This is intentional as I believe, Outer Facing models should not be equal to Business models and Business models should not be equal to Entity models.

We have made the custom mapping of the Employee.cs with EmployeeViewModel.cs.

Below are the implementation details:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            TypeAdapterConfig<Employee, EmployeeViewModel>.NewConfig()
                            .Map(dest => dest.Name, src => src.FirstName + " " + src.FamilyName);
            app.UseMvc();
        }

Step 2e

Remove ValueController. We no longer need it. Create a new controller class by name β€œEmployeeController”.

Below are the implementation details:

using BechnoicsAPI.Models;
using BechnoicsAPI.Services;
using BechnoicsAPI.ViewModels;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using System;
namespace BechnoicsAPI.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        /// <summary>
        /// The service
        /// </summary>

        private readonly IEmployeeService service;
        /// <summary>
        /// Initializes a new instance of the <see cref="EmployeeController"/> class.
        /// </summary>
        /// <param name="service">The service.</param>

        public EmployeeController(IEmployeeService service)
        {
            this.service = service;
        }
        /// <summary>
        /// Gets all employees.
        /// </summary>
        /// <returns></returns>
        [HttpGet()]

        public IActionResult GetAllEmployees()
        {
            var employeesFromRepo = this.service.GetAll();
            var employees = employeesFromRepo.Adapt<EmployeeViewModel[]>();
            return Ok(employees);
        }
        /// <summary>
        /// Gets the employee details.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        [HttpGet("{id}", Name = "GetEmployeeDetails")]
        public IActionResult GetEmployeeDetails(int id)
        {
            var employeeFromRepo = this.service.GetById(id);
            if (employeeFromRepo == null)
            {
                return NotFound(new { Error = String.Format("Employee with Id : 
                                      {0} has not been found", id) });
            }
            var employee = employeeFromRepo.Adapt<EmployeeViewModel>();
            return Ok(employee);
        }
        /// <summary>
        /// Adds the employee.
        /// </summary>
        /// <param name="employeeCreate">The employee create.</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        [HttpPost()]
        public IActionResult AddEmployee([FromBody] EmployeeCreateViewModel employeeCreate)
        {
            if (employeeCreate == null)
            {
                return BadRequest();
            }
            var employeeEntity = employeeCreate.Adapt<Employee>();
            if (!this.service.Add(employeeEntity))
            {
                throw new Exception($"Creation of employee failed!!!");
            }
            var employeeToReturn = employeeEntity.Adapt<EmployeeViewModel>();
            return CreatedAtRoute("GetEmployeeDetails", 
                      new { id = employeeToReturn.Id }, employeeToReturn);
        }
    }
}

At the constructor of the controller, we have injected the IEmployeeService. If you remember, we have Configured Service(ConfigureService method of startup.cs). And we have created property which would be used in the rest of the action methods in the controller.

At the GetAllEmployees(), we have mapped the Employee.cs with EmployeeViewModel.cs using the Adpat() of the Mapster. Same is the case with GetEmployeeDetails().

If you are good at the REST architectural style, then you would know that when we create a resource, after creating it, we need to mention where it has been created. Which is as per Richardson Maturity Model for REST APIs.

After creating all, VS solution should look like below:

Conclusion

In this article, I wanted all to know the basic usage of Mapster framework. For more, you can visit https://github.com/MapsterMapper/Mapster.

And also, this article is just a base for my future article on Angular 5 with ASP.NET Core 2.0 Web API series. In that, I would be using more of Mapster with EntityFramework Core 2.0 with a complete set of CRUD operations and lot more REST API features using Richardson Maturity Model.

I have kept the source code at https://github.com/iampsdinesh/BechnoicsAPI.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here