The Automapper is a great utility that will easily facilitate the conversion of models to other models without needing to define that logic inside the models themselves. It's a simple Nuget package that allows you to create "Profiles" which more or less define rules on how to convert one Type of object to another, so for example if you had an HourlyEmployee class and you wanted to convert it to a SalaryEmployee Class.
More often than not the auto mapper is actually used to generate Data Transfer Objects (DTOs) for APIs, DTOs are generally a manipulation of a model either to provide consumers of the API a subset and/or a manipulation of a model.
To demonstrate the auto mapper we are going to build a very simple Console Application.
To Get started let's set up our Console application
- mkdir pav.automapper.cnl create a directory for your project
- cd pav.automapper.cnl navigate into that directory
- dotnet new console --use-program-main insatiate a console application
- code . open the folder in MS code
You're MS Code should open with your current working directory open, if you are prompted to trust yourself, go ahead and do that.
with your project trusted, before we get started ensure that you have the C# extension from Microsoft installed.
On the left panel click the Extensions button and make sure that you have the C# extension added, once you are sure that you have it, click on the explorer button and open you project file, it's the one that ends in .csproj
with that open we are now going to add a nuget package for automapper
https://www.nuget.org/packages/automapper/
if you are looking to add this to an API then rather than install just automapper, install the Microsoft dependency injection variant of it
for now let's add the basic automapper nuget with
dotnet add package automapper
with our package added notice that our project file now makes a reference to it
next let's run our our project with "dotnet run"
exactly what we would expect to see, simply "Hello World!" written out to our console. let's open up our program.cs file and see what we are starting with.
not much there, and to be honest if we didn't include our "-use-program-main" flag when we created our application it would be even less, it would look something like:
which to me is just confusing, but that's probably cause I'm old now, by software standards I should have died of a stroke years ago.
To get start we are going to need some models, were going to keep it pretty simple and create a
simple Employee class and a corresponding salaryEmployee and hourlyEmployee class definitions which inherit from an Abstract Employee, then after we are going to use automapper to convert between the two.
so first lets get started by setting up our models folder, with our three classes defined
Now whenever I make an abstract class I like to prefix it with the word Base, just to give me a visual que that it's an abstract class and that any other class with the word employee in it will derive from that base class, it's just a personal preference, you can name them whatever you like.
lets start by defining our Base Employee class, one thing to remember is that all of our classes that we will be converting to, require a parameterless constructor, so that auto mapper can instantiate them.
using System;
namespace pav.automapper.cnl.Models{
public abstract class BaseEmployee{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime Birthdate { get; set; }
public BaseEmployee(){}
public BaseEmployee(string firstName, string lastName, DateTime birtdate){
this.FirstName = firstName;
this.LastName = lastName;
this.Birthdate = birtdate;
}
public abstract float payMoney();
public int getAge()
{
var today = DateTime.Today;
var age = today.Year - Birthdate.Year;
if (Birthdate.Date > today.AddYears(-age))
return age-1;
return age;
}
public override string ToString()
{
var fn = this.FirstName;
var ln = this.LastName;
var age = this.getAge();
return $"{fn} {ln} is {age} years old";
}
}
}
Pretty simple class definition we overrode the ToString() method to make it easier for us to later output to our console and we create an abstract payMoney function which will be used to calculate our employees wages.
Next let's define the hourlyEmployee class.
using System;
namespace pav.automapper.cnl.Models{
public class HourlyEmployee : BaseEmployee{
public float Wage { get; set; }
public float Hours { get; set; }
public HourlyEmployee(){}
public HourlyEmployee(string firstName, string lastName, DateTime birthdate, float wage, float hours)
:base(firstName, lastName, birthdate){
this.Wage = wage;
this.Hours = hours;
}
public override float payMoney(){
return Hours * Wage;
}
public override string ToString()
{
var baseString = base.ToString();
return $"{baseString} and will be paid ${payMoney()}";
}
}
}
again no rocket science here, we implemented to payMoney function as well as overrode the ToString method.
One more Model to go and that's the SaleryEmployee one.
using System;
namespace pav.automapper.cnl.Models{
public class SalaryEmployee : BaseEmployee{
public float Salary { get; set; }
public SalaryEmployee(){}
public SalaryEmployee(string firstName, string lastName, DateTime birthdate, float salary)
:base(firstName, lastName, birthdate){
this.Salary = salary;
}
public override float payMoney(){
return Salary;
}
public override string ToString()
{
var baseString = base.ToString();
return $"{baseString} and has a salary of ${payMoney()}";
}
}
}
More or less the same as before if not simpler since we removed the hour and wage and just provide a base salary, we also updated the two String method to make it easier to distinguish between the two types of employees.
let's go to our Program.cs class and test our our models.
using pav.automapper.cnl.Models;
using System;
namespace pav.automapper.cnl
{
class Program
{
static void Main(string[] args)
{
var john = new HourlyEmployee("John", "Doe", new DateTime(1984, 1, 31), 10, 40);
Console.WriteLine(john);
var bob = new SalaryEmployee("Bob", "Smith", new DateTime(1984, 1, 31), 600);
Console.WriteLine(bob);
}
}
}
we instantiated two different employees john and bob then we write them out to the console,
exactly what we would expect to see.
We can finally bring in automapper, let's say that we wanted to convert an hourly employee to a salaried one, well we could manually do it, but automapper will let us define mapping rules and do it for us.
let's get started by creating a Profiles folder and adding an EmployeeProfile class.
We included a base implementation for the EmployeeProfile class and inherited from the Profile class which is included in the AutoMapper nuget.
Let's now define some mapping rules to convert an hourly employee to a salaried one
CreateMap<HourlyEmployee, SalaryEmployee>()
.ForMember(dest => dest.Salary, opt => opt.MapFrom(src => src.Hours * src.Wage));
it's that simple, we map our destination property to what we want it to be from our source class. the finished EmployeeProfile class should look something like
using AutoMapper;
using pav.automapper.cnl.Models;
namespace pav.automapper.cnl.Profiles
{
public class EmployeeProfile : Profile
{
public EmployeeProfile()
{
CreateMap<HourlyEmployee, SalaryEmployee>()
.ForMember(dest => dest.Salary, opt => opt.MapFrom(src => src.Hours * src.Wage));
}
}
}
Obviously we have to somehow call this, but the conversion logic is done.
let's go back to our Program.cs class and implement our mapper. Let's tart by configuring our automapper.
var config = new MapperConfiguration(cfg => cfg.AddProfiles(new[] { new EmployeeProfile() }));
var mapper = new Mapper(config);
with that done we can now easily convert hourly employees to salaried ones
using AutoMapper;
using pav.automapper.cnl.Models;
using pav.automapper.cnl.Profiles;
using System;
namespace pav.automapper.cnl
{
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.AddProfiles(new[] { new EmployeeProfile() }));
var mapper = new Mapper(config);
var john = new HourlyEmployee("John", "Doe", new DateTime(1984, 1, 31), 10, 40);
Console.WriteLine(john);
var bob = new SalaryEmployee("Bob", "Smith", new DateTime(1984, 1, 31), 600);
Console.WriteLine(bob);
var John = mapper.Map<SalaryEmployee>(john);
Console.WriteLine(John);
}
}
}
it's just that easy now, we simply run john through our mapper and specify the output class and the rules we defined in our profile will take care of the conversion, any properties that have the same name will be automatically mapped.
Obviously there is a lot more to this, but at it's core that's all the automapper is for, to convert one class and abstract away the mappings for this conversion from the actual classes being converted.