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
{
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string FamilyName { get; set; }
public DateTime StartDateAtBench { get; set; }
public DateTime? EndDateAtBench { get; set; }
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
{
public interface IEmployeeService
{
IEnumerable<Employee> GetAll();
Employee GetById(int id);
bool Add(Employee model);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BechnoicsAPI.Models;
namespace BechnoicsAPI.Services
{
public class EmployeeMemoryService : IEmployeeService
{
private readonly List<Employee> employees = new List<Employee>();
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"
});
}
public bool Add(Employee model)
{
model.Id = employees.Max(c => c.Id) + 1;
employees.Add(model);
return true;
}
public IEnumerable<Employee> GetAll()
{
return employees.AsEnumerable();
}
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
{
public class EmployeeViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int BenchPeriodInDays { get; set; }
public string Skillset { get; set; }
}
}
using System;
namespace BechnoicsAPI.ViewModels
{
public class EmployeeCreateViewModel
{
public string FirstName { get; set; }
public string FamilyName { get; set; }
public DateTime StartDateAtBench { get; set; }
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
{
private readonly IEmployeeService service;
public EmployeeController(IEmployeeService service)
{
this.service = service;
}
[HttpGet()]
public IActionResult GetAllEmployees()
{
var employeesFromRepo = this.service.GetAll();
var employees = employeesFromRepo.Adapt<EmployeeViewModel[]>();
return Ok(employees);
}
[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);
}
[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.