user10268539
user10268539

Reputation: 159

How to manually mapping DTO WITHOUT using AutoMapper?

I'm learning C#.NET Core and trying to create DTO mapping without using AutoMapper as I'm working on a small project alone and want to understand fundamental before using extra packages, surpringly I could not easily find answer at stackoverflow.com or I may use wrong keyword searching.

BTW, below is my code which I successfully map to EmployeeForShortDto under GetEmployee method. Unfortunately, I don't how to map it under GetAllEmployee just because the return data is a collection, not a single record. Please advice.

EmployeeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Repository;
using NetCoreWebApplication1.Other;

namespace NetCoreWebApplication1.Controller
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IMasterRepository _repo;

        public EmployeeController(IMasterRepository repo)
        {
            _repo = repo;
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetEmployee(int id)
        {
            var data = await _repo.GetEmployee(id);
            if (data == null) return NotFound();
            var dataDto = new EmployeeForShortDto()
            {
                Id = data.Id,
                EmpCode = data.EmpCode,
                Fname = data.Fname,
                Lname = data.Lname,
                Age = NetCoreWebApplication1.Other.Extension.CalcAge(data.DateBirth)
            };

            return Ok(dataDto);
        }

        [HttpGet]
        public async Task<IActionResult> GetAllEmployee()
        {
            var data = await _repo.GetAllEmployee();
            return Ok(data);
        }

    }
}

MasterRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Repository
{
    public class MasterRepository : IMasterRepository
    {
        private readonly PrDbContext _context;

        public MasterRepository(PrDbContext context)
        {
            _context = context;
        }


        // Employee
        public async Task<List<Employee>> GetAllEmployee()
        {
            var data = await _context.Employee.ToListAsync();
            return data;
        }

        public async Task<Employee> GetEmployee(int id)
        {
            var data = await _context.Employee.FirstOrDefaultAsync(x => x.Id == id);
            return data;
        }

        // Generic methods
        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            _context.Remove(entity);
        }

        public async Task<bool> SaveAll()
        {
            return await _context.SaveChangesAsync() > 0;
        }
    }
}

Upvotes: 12

Views: 30481

Answers (4)

user10268539
user10268539

Reputation: 159

thank you for all responses, all are very useful to me. Finally, I end up with solution from @Brad. I also learned how to make a reverse mapping from DTO to a class before adding a record to the database.

I put my code below in case someone want to see. Any comments/suggestions are more than welcome. Thank you.

Extension.cs

using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NetCoreWebApplication1.Other
{
    public static class Extension
    {
        public static EmployeeForShortDto MapToEmployeeForShortDto(this Employee emp)
        {
            if (emp != null)
            {
                return new EmployeeForShortDto
                {
                    Id = emp.Id,
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    Age = emp.DateBirth.CalcAge()
                };
            }

            return null;
        }

        public static EmployeeForListDto MapToEmployeeForListDto(this Employee emp)
        {
            if (emp != null)
            {
                return new EmployeeForListDto
                {
                    Id = emp.Id,
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    Age = emp.DateBirth.CalcAge(),
                    EntityCode = emp.EntityCode,
                    IsActive = emp.IsActive
                };
            }

            return null;
        }

        public static Employee MapFromEmployeeForAddDto(this EmployeeForAddDto emp)
        {
            if (emp != null)
            {
                return new Employee
                {
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    IdCard = emp.IdCard,
                    IsActive = 1
                };
            }

            return null;
        }

        public static int CalcAge(this DateTime? dateBirth)
        {
            if (dateBirth.HasValue)
            {
                var age = DateTime.Today.Year - dateBirth.Value.Year;
                if (dateBirth.Value.AddYears(age) > DateTime.Today) age--;
                return age;
            }
            else
            {
                return 0;
            }
        }
    }
}

MasterRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Repository
{
    public class MasterRepository : IMasterRepository
    {
        private readonly PrDbContext _context;

        public MasterRepository(PrDbContext context)
        {
            _context = context;
        }


        // Employee
        public async Task<List<Employee>> GetAllEmployee()
        {
            var data = await _context.Employee.ToListAsync();
            return data;
        }

        public async Task<Employee> GetEmployee(int id)
        {
            var data = await _context.Employee.FirstOrDefaultAsync(x => x.Id == id);
            return data;
        }

        public async Task<Employee> AddEmployee(Employee data)
        {
            await _context.Employee.AddAsync(data);
            await _context.SaveChangesAsync();
            return data;
        }

        public async Task<bool> EmployeeExists(string entityCode, string empCode)
        {
            if (await _context.Employee.AnyAsync(x =>
                x.EntityCode == entityCode &&
                x.EmpCode == empCode))
                return true;

            return false;
        }

        // Generic methods
        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            _context.Remove(entity);
        }

        public async Task<bool> SaveAll()
        {
            return await _context.SaveChangesAsync() > 0;
        }
    }
}

EmployeeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Repository;
using NetCoreWebApplication1.Other;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Controller
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IMasterRepository _repo;

        public EmployeeController(IMasterRepository repo)
        {
            _repo = repo;
        }

        [HttpPost("add")]
        public async Task<IActionResult> AddEmployee(EmployeeForAddDto emp)
        {
            if (await _repo.EmployeeExists(emp.EntityCode, emp.EmpCode))
                ModelState.AddModelError("Employee", "Employee is duplicate (EntityCode + EmpCode)");

            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            Employee employeeToAdd = emp.MapFromEmployeeForAddDto();

            await _repo.AddEmployee(employeeToAdd);

            return StatusCode(201);
        }


        [HttpGet("{id}")]
        public async Task<IActionResult> GetEmployee(int id)
        {
            var data = await _repo.GetEmployee(id);

            if (data == null) return NotFound();

            return Ok(data.MapToEmployeeForShortDto());
        }

        [HttpGet]
        public async Task<IActionResult> GetAllEmployee()
        {
            var data = await _repo.GetAllEmployee();

            //var dataDto = data.Select(x => x.MapToEmployeeForShortDto());
            var dataDto = data.Select(x => x.MapToEmployeeForListDto());

            return Ok(dataDto);
        }

    }
}

Upvotes: 3

Brad
Brad

Reputation: 4553

You can use an extension method to map from your entity type to your DTO type.

public static EmployeeForShortDto ToDto(this Employee employee)
{
    if (employee != null)
    {
        return new EmployeeForShortDto
        {
            Id = employee.Id,
            EmpCode = employee.EmpCode,
            Fname = employee.Fname,
            Lname = employee.Lname,
            Age = NetCoreWebApplication1.Other.Extension.CalcAge(employee.DateBirth)
        };
    }

    return null;
}

And then use where needed.

[HttpGet("{id}")]
public async Task<IActionResult> GetEmployee(int id)
{
    var data = await _repo.GetEmployee(id);

    if (data == null) 
    {
        return NotFound();
    }

    return Ok(data.ToDto());
}

[HttpGet]
public async Task<IActionResult> GetAllEmployee()
{
    var data = await _repo.GetAllEmployee();

    return Ok(data.Select(x => x.ToDto()));
}

Upvotes: 19

Code Name Jack
Code Name Jack

Reputation: 3293

For your problem, extract your implementation in a new method.

EmployeeForShortDto ConvertToDto(Employee data)
{
 var dataDto = new EmployeeForShortDto()
        {
            Id = data.Id,
            EmpCode = data.EmpCode,
            Fname = data.Fname,
            Lname = data.Lname,
            Age = NetCoreWebApplication1.Other.Extension.CalcAge(data.DateBirth)
        };
}

Then finally call it in loop,

 foreach(Employee e in EmployeeList)
    { 
       dtoList.Add(ConvertToDto(e));
    }

For generic implementation, Generate a list of properties of Model and Dto via reflection. and then match their types.

class AdapterHelper<T1, T2>
{
    public T1 Adapt(T2 source)
    {
        T1 targetItem = Activator.CreateInstance<T1>();
        var props = typeof(T1).GetProperties();
        var targetProps = typeof(T2).GetProperties();
        foreach (var prop in props)
        {
            foreach (var targetProp in targetProps)
            {
                if (prop.Name == targetProp.Name)
                {
                    targetProp.SetValue(targetItem, prop.GetValue(source));
                    //assign

                }
            }
        }
        return targetItem;
    }
}

This is the link to my original answer.

Upvotes: 4

Morten Bork
Morten Bork

Reputation: 1632

Okay, the direct answer to your question is "do it pr returned values";

List<EmployeeForShortDto> result = new List<EmployeeForShortDto>();
foreach(Employee dbEmployee in data )
{
 result.add(new EmployeeForShortDto()
            {
                Id = dbEmployee .Id,
                EmpCode = dbEmployee .EmpCode,
                Fname = dbEmployee .Fname,
                Lname = dbEmployee .Lname,
                Age = NetCoreWebApplication1.Other.Extension.CalcAge(dbEmployee .DateBirth)
            });
}

This however, is type specific for your item. Why not make a generic method that uses reflection to map the object either by attributes attached, or by property name directly? If you get it done, you will be able to transfer any object to a DTO, as long as you adhere to the internal rules of property names or setup up the mappings via the attributes.

Upvotes: 2

Related Questions