Chuyển đến nội dung chính

AutoMapper → Mapperly trong ASP.NET MVC

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 project
File > 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

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.