[C#] DTO/Mapper Class Example

DTO คือ อะไร?

DTO คือ Data Transfer Object พูดง่าย ๆ ก็คือเป็น object ที่ใช้ในการส่ง data กันในระบบครับ

  • ถ้าระบบเราออกแบบแบบ Layer Architecture ตัว DTO เป็น Class ที่ส่ง Data ไประหว่างชั้น Presentation / Controller / Service / Repository
  • สำหรับฝั่ง Java จะมีชื่อเรียกอีกแบบ POJO

นอกจากนี้แล้ว การใช้ DTO ยังเป็นการ Limit ข้อมูล หรือมองว่าเป็นการกำหนด Pattern ให้กับ Request / Response กับระบบ

  • DTO ทั้งหมด
public class CustomerDTO
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
}
  • DTO สำหรับ Create
public class CreateCustomerDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
}

และยังป้องกัน Over-posting Attack (คนร้ายเดา Field ได้ถูก แล้วลองส่ง Request เข้ามา) ถ้าไม่มี DTO ดักไว้ จะมีข้อมูลเกินเข้าไปมาได้ครับ จากตัวอย่าง CustomerDTO ของเราจะมี 6 Field แต่ตัว CreateCustomerDTO เรา Limit ไว้ 3 Field ถ้าแบบเดิมตอน Create เราไม่มี DTO มากัน Attacker สามารถเดา Field และส่งมาได้ครับ

Mapper Class Example

สำหรับผม Pattern ที่ใช้ DTO และในชั้น Repository ทำตัว Mapper Class ระหว่าง DTO กับ Model (Model Class ที่เป็นตัวแทนของ Table ใน Database ครับ

ตัวอย่าง Class ที่ Map ระหว่าง DTO และ Data Mapper ใน pattern of enterprise application architecture ของลุงมาร์ตินครับ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using WindowsFormsModel.BusinessObjects;
using WindowsFormsModel.ActionServiceReference;

namespace WindowsFormsModel.DataTransferObjectMapper
{
    /// <summary>
    /// Static class that maps data transfer objects to model objects and vice versa.
    /// </summary>
    internal static class Mapper
    {
        /// <summary>
        /// Maps array of customer data transfer objects to customer model objects.
        /// </summary>
        /// <param name="customers">Array of customer data transfer objects.</param>
        /// <returns>List of customer models.</returns>
        internal static IList<CustomerModel> FromDataTransferObjects(Customer[] customers)
        {
            if (customers == null)
                return null;

            return customers.Select(c => FromDataTransferObject(c)).ToList();
        }

        /// <summary>
        /// Maps single customer data transfer object to customer model.
        /// </summary>
        /// <param name="customer">Customer data transfer object.</param>
        /// <returns>Customer model object.</returns>
        internal static CustomerModel FromDataTransferObject(CustomerDTO customer)
        {
            return new CustomerModel
            {
                Company = customer.Company,
                City = customer.City,
                Country = customer.Country,
                CustomerId = customer.CustomerId,
                Orders = FromDataTransferObjects(customer.Orders),
                Version = customer.Version
            };
        }

        /// <summary>
        /// Maps array of customer data transfer objects to customer model objects.
        /// </summary>
        /// <param name="orders">Array of order data transfer objects.</param>
        /// <returns>List of order model objects.</returns>
        internal static IList<OrderModel> FromDataTransferObjects(OrderDTO[] orders)
        {
            if (orders == null)
                return null;

            return orders.Select(o => FromDataTransferObject(o)).ToList();
        }

        /// <summary>
        /// Maps single order data transfer object to order model.
        /// </summary>
        /// <param name="order">Order data transfer object.</param>
        /// <returns>Order model object.</returns>
        internal static OrderModel FromDataTransferObject(OrderDTO order)
        {
            return new OrderModel
            {
                OrderId = order.OrderId,
                Freight = order.Freight,
                OrderDate = order.OrderDate,
                RequiredDate = order.RequiredDate,
                OrderDetails = FromDataTransferObjects(order.OrderDetails),
                Version = order.Version
            };
        }

        /// <summary>
        /// Maps arrary of order detail data transfer objects to list of order details models.
        /// </summary>
        /// <param name="orderDetails">Array of order detail data transfer objects.</param>
        /// <returns>List of order detail models.</returns>
        internal static IList<OrderDetailModel> FromDataTransferObjects(OrderDetailDTO[] orderDetails)
        {
            if (orderDetails == null)
                return null;

            return orderDetails.Select(o => FromDataTransferObject(o)).ToList();
        }

        /// <summary>
        /// Maps order detail data transfer object to order model object.
        /// </summary>
        /// <param name="orderDetail">Order detail data transfer object.</param>
        /// <returns>Orderdetail model object.</returns>
        internal static OrderDetailModel FromDataTransferObject(OrderDetailDTO orderDetail)
        {
            return new OrderDetailModel
            {
                ProductName = orderDetail.ProductName,
                Discount = orderDetail.Discount,
                Quantity = orderDetail.Quantity,
                UnitPrice = orderDetail.UnitPrice,
                Version = orderDetail.Version
            };
        }

        /// <summary>
        /// Maps customer model object to customer data transfer object.
        /// </summary>
        /// <param name="customer">Customer model object.</param>
        /// <returns>Customer data transfer object.</returns>
        internal static CustomerDTO ToDataTransferObject(CustomerModel customer)
        {
            return new Customer
            {
                CustomerId = customer.CustomerId,
                Company = customer.Company,
                City = customer.City,
                Country = customer.Country,
                Version = customer.Version
            };
        }
    }
}

นอกจากนี้ เรายังสามารถใชัตัว AutoMapper มาช่วยได้ครับ ใน Sample ของ .NET Core นะครับ

public class AppMapperProfile : Profile
{
    public AppMapperProfile()
    {
        //ถ้าชื่อเดียวกัน Map ชนกันได้เลย
        CreateMap<CustomerModel, CustomerDTO>().ReverseMap();
    }
}

ใน main ตัว program.cs

builder.Services.AddAutoMapper(typeof(Program));

Reference


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.