by oclockvn at 04:14 PM 09/27/2016

sử dụng automapper trong asp.net mvc

không còn "copy" nữa...

Chắc hẳn bạn vẫn còn nhớ (hoặc đã từng - hoặc đang) sử dụng đoạn code dưới đây

public class Foo
{
    public string Id { get; set; }
    public string Name { get; set; }
    
    // copy constructor
    public Foo(Foo copy)
    {
        this.Id = copy.Id;
        this.Name = copy.Name;
    }
}

hoặc đại loại 1 đoạn code copy giá trị tương tự:

public int DoSomeThing(FooModel foo)
{
    // copy from FooModel to Foo
    Foo f = new Foo
    {
        Id = foo.Id,
        Name = foo.Name
    };

    // do something

    return 0;
}

Sở dĩ phải copy giá trị của các object bởi đơn giản object là kiểu tham chiếu (reference type) nếu bạn không muốn xảy ra chuyện ơ hay, Id của tao sao mày dám đổi :))

2 cách trên không có gì sai hay chuối cả, và hiện tại vẫn có rất nhiều người - người mà chưa biết đến automapper - vẫn đang sử dụng. Mình tạm gọi việc copy dữ liệu giữa 2 object là mapping.

Mapping: gán giá trị từ thuộc tính A trong đối tượng a cho thuộc tính tương ứng B trong đối tượng b:

b.B = a.A

...bởi vì đã có automapper

Automapper là 1 thư viện mapping object-object. Nó cho phép copy giá trị từ 2 object có các property (thuộc tính) giống tên nhau - không phân biệt hoa thường. Hơn thế nữa, nó còn có thể map các property giống tên nhưng khác kiểu dữ liệu(*) và cho phép bạn custom việc chuyển đổi giữa các property theo ý mình.

Automapper cho phép download miễn phí thông qua nuget, bạn có thể sử dụng lệnh sau trong Package Manager Console để cài đặt version mới nhất

Install-Package AutoMapper

sử dụng automapper

Giả sử bạn có 2 class như sau

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Bar
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Bạn có thể map Foo instance sang Bar instance bằng 2 bước đơn giản:

automapper simple

B1: setup để khai báo rằng object Bar sẽ được map từ object Foo (dòng 31)
B2: map từ Foo sang Bar (dòng 36)

Với cách setup như trên, bạn đang dùng instance api để khởi tạo đối tượng Mapper. Với các version cũ hơn của automapper, bạn có thể sử dụng static api như sau:

automapper static api

Việc đăng ký mapper chỉ xảy ra 1 lần trên 1 AppDomain, điều này dễ dàng thực hiện bằng cách đăng ký trong Application_Start

automapper boostrapper Application_Start

Đăng ký sử dụng static api cũng làm tương tự, nhưng bạn không cần phải giữ lại instance của mapper để sử dụng lại. Cách đăng ký sử dụng static api có vẻ tiện hơn, nhưng đã bị deprecated ở các version 5.x của automapper (mình cũng thích dùng static api hơn).

convert 2 property khác nhau

Giả sử trường hợp như sau:

public class Foo
{
    public string Id = "5";
}

public class Bar
{
    public int Id;
}

// setup map..

// convert
var foo = new Foo();
var bar = Mapper.Map<Foo, Bar>(); // => bar = 5;

Lợi hại chưa, automapper có thể map 2 property khác kiểu dữ liệu nếu trùng tên. Tuy nhiên, nếu convert fail thì exception sẽ văng ra

var foo = new Foo { Id = "5s" };
var bar = Mapper.Map<Foo, Bar>(foo); // exception

automapper convert fail

custom automapper convert

Có 2 cách để custom converter theo ý của mình: trong config và implement ITypeConverter

1. custom converter trong config

Bạn có thể custom convert trong config ở 2 vị trí khác nhau: khi đăng ký map và trong quá trình map.

1.1. Đăng ký map

Với 2 class như sau

automapper classes

Bạn có thể custom cho Fullname của Bar map với Name của Foo bằng cách sử dụng extension ForMember trong config:

custom map register

ForMember nhận vào 2 tham số

ForMember<TMember>(
    Expression<Func<TDestination, TMember>> destinationMember, 
    Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions)

Tham số đầu tiên dùng để chỉ định target property, tham số thứ 2 là 1 Action option dùng để chỉ định source property

1.2. Trong quá trình map

Bạn cũng có thể custom trong quá trình mapping như sau

automapper custom map

Và kết quả:

automapper custom result

Trên đây là cách custom cho những object có nhiều property giống nhau, chỉ chỉnh sửa 1 vài logic nhỏ trước/sau khi mapping. Đối với 2 object hoàn toàn khác nhau, cách đơn giản nhất là viết 1 customer converter.

2. custom map bằng cách implement ITypeConverter

Bằng cách kế thừa interface ITypeConverter, bạn có quyền hoàn toàn muốn làm gì thì làm :))

public class FooToBarConverter : ITypeConverter<Foo, Bar>
{
    public Bar Convert(
        Foo source, 
        Bar destination, 
        ResolutionContext context)
    {
        if (source == null)
            return null;

        if (destination == null)
            destination = new Bar();

        destination.Name = source.Name;
        destination.Fullname = "Mr." + source.Name;

        return destination;
    }
}

và đăng ký nó trong config bằng cách:

var mapperConfig = new MapperConfiguration(config => {
    config.CreateMap<Foo, Bar>()
        .ConvertUsing<FooToBarConverter>(); // use converter
});

Chú ý:

Trong các phiên bản 4.x trở xuống, implement ITypeConverter có đôi chút khác về tham số trong phương thức Convert

public class FooToBarConverter : ITypeConverter<Foo, Bar>
{
    public Bar Convert(ResolutionContext context)
    {
        if (context.IsSourceValueNull) return null;
        var p = context.SourceValue as Foo;

        return new Bar
        {
            // ...
        };
    }
}

automapper in action

Trong các project của mình, mình thích sử dụng cách viết class kế thừa hơn là config (cũng tùy trường hợp nên dùng cái nào)

Step 1: đăng ký 1 lần trong global.asax

Application_Start

Step 2: đăng ký mapping và conveter

mapping register

Step 3: Map

map

kết

Trong ứng dụng, việc chuyển đổi giá trị từ object này sang object kia rất hay gặp, và thanks god, automapper đã làm chuyện đó trở nên đơn giản và thuận tiện hơn :)

Hy vọng qua bài viết, các bạn có thể áp dụng tốt automapper vào các project của mình.

relate posts
by {{relate.UpdatedBy}} at 02/21/2018

{{relate.Title}}

  • {{relate.TotalComment}}
  • {{relate.View}}
  • {{relate.AveragePoint}}
[{{postCtrl.comments.length}}] comments
all comments {{ postCtrl.isHiddenComment ? 'show comments' : 'hide comments' }}
what do you want to say?

(*) markdown supported with html disabled