1. Bối cảnh
Trong nhiều năm, AutoMapper gần như là tiêu chuẩn mặc định khi làm mapping trong .NET. Viết ít code, config một lần, chạy mọi nơi.
Nhưng khi:
- AutoMapper chuyển dần sang hướng commercial
- Runtime mapping gây khó debug
- Performance không còn tối ưu cho hệ thống lớn
→ nhiều team bắt đầu chuyển sang hướng compile-time mapping.
Một trong những lựa chọn nổi bật hiện nay: Mapperly.
2. Bài toán thực tế (ASP.NET MVC)
Tạo projectFile > New Project > ASP.NET Web Application (.NET Framework)
Name : ProductCatalog.Web
Framework: .NET Framework 4.8
Template : MVC
Cấu trúc dự án:
ProductCatalog.Web/
├── App_Start/
│ ├── BundleConfig.cs
│ ├── FilterConfig.cs
│ └── RouteConfig.cs
├── Controllers/
│ └── HomeController.cs
├── Models/
├── Views/
├── Global.asax
└── Web.config
Cài đặt packages:
# Package Manager Console
Install-Package AutoMapper
Chúng ta sẽ tạo lần lượt các file như sau:
ProductCatalog.Web/
├── App_Start/
│ ├── RouteConfig.cs
│ ├── FilterConfig.cs
│ ├── BundleConfig.cs
│ └── AutoMapperConfig.cs ← tạo mới
│
├── Controllers/
│ ├── HomeController.cs
│ ├── ProductController.cs ← tạo mới
│ └── CategoryController.cs ← tạo mới
│
├── Models/ ← Domain Models (EF entities / POCO)
│ ├── Product.cs
│ └── Category.cs
│
├── ViewModels/ ← DTOs cho View (tạo folder mới)
│ ├── ProductViewModel.cs
│ ├── ProductCreateViewModel.cs
│ └── CategoryViewModel.cs
│
├── Mappings/ ← AutoMapper Profiles (tạo folder mới)
│ └── ProductMappingProfile.cs
│
├── Repositories/ ← Data access (tạo folder mới)
│ ├── IProductRepository.cs
│ ├── ProductRepository.cs
│ ├── ICategoryRepository.cs
│ └── CategoryRepository.cs
│
├── Services/ ← Business logic (tạo folder mới)
│ ├── IProductService.cs
│ └── ProductService.cs
│
├── Views/
│ ├── Product/
│ │ ├── Index.cshtml
│ └── Shared/
│ └── _Layout.cshtml
│
├── Global.asax
└── Web.config
Domain Models
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; } // ← nested object
}
ViewModels
public class CategoryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string PriceDisplay { get; set; } // "1.500.000 ₫" ← format from decimal
public string StockStatus { get; set; }
public string CategoryName { get; set; } // flatten from Category.Name
public bool IsActive { get; set; }
public string CreatedAtDisplay { get; set; } // "21/05/2025"
}
public class ProductCreateViewModel
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public decimal Price { get; set; }
public int Stock { get; set; }
public int CategoryId { get; set; }
public List<CategoryViewModel> Categories { get; set; } // dropdown
}
AutoMapper Profile
public class ProductMappingProfile : Profile
{
public ProductMappingProfile()
{
// Category → CategoryViewModel
CreateMap<Category, CategoryViewModel>();
// Product → ProductViewModel (có custom logic)
CreateMap<Product, ProductViewModel>()
.ForMember(dest => dest.CategoryName,
opt => opt.MapFrom(src => src.Category.Name))
.ForMember(dest => dest.PriceDisplay,
opt => opt.MapFrom(src =>
src.Price.ToString("N0") + " ₫"))
.ForMember(dest => dest.StockStatus,
opt => opt.MapFrom(src =>
src.Stock > 0 ? "In stock" : "Out of stock"))
.ForMember(dest => dest.CreatedAtDisplay,
opt => opt.MapFrom(src =>
src.CreatedAt.ToString("dd/MM/yyyy")));
// ProductCreateViewModel → Product (form submit)
CreateMap<ProductCreateViewModel, Product>()
.ForMember(dest => dest.Id,
opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt,
opt => opt.MapFrom(_ => DateTime.Now))
.ForMember(dest => dest.IsActive,
opt => opt.MapFrom(_ => true))
.ForMember(dest => dest.Category, opt => opt.Ignore());
}
}
Khởi tạo AutoMapper trong App_Start
public static class AutoMapperConfig
{
public static IMapper Mapper { get; private set; }
public static void Initialize()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ProductMappingProfile>();
}, NullLoggerFactory.Instance);
config.AssertConfigurationIsValid();
Mapper = config.CreateMapper();
}
}
File Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AutoMapperConfig.Initialize();
}
}
Đoạn code đầy đủ Auto Mapper tại đây: https://github.com/anbinhtrong/MapperlySample/releases/tag/v1.0.0
Nhận xét
Đăng nhận xét