AutoMapper地图长吗?串

时间:2019-06-18 19:41:58

标签: c# automapper

我有两个类,一个ViewModel和一个Dto,它们基本相同,只是Dto的字段“只读长?电话;'而ViewModel具有“字符串Phone {get;组; }'。

我发现使AutoMapper正常工作的唯一方法是将ViewModel属性更改为支持属性:

    public long? Phone { get; set; }

    public string PhoneNumberString
    {
        get
        {
            var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
            return srv.GetFormattedPhoneNumber(Phone);
        }
        set
        {
            var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
            Phone = srv.GetLongPhoneNumber(value);
        }
    }

然后在AutoMapper中,有一条巨大的线来调用构造函数:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<MyViewModel, MyDto>()
        .ConstructUsing(src => new MyDto(
            src.Phone
    /* ...Some ~30 other parameters here... */))
        .ReverseMap();
});

...必须有更好的方法来做到这一点?我已经尝试过这些:

.ForSourceMember(x => x.PhoneNumberString, opt => opt.DoNotValidate())

.ForMember(x => x.PhoneNumberString, opt => opt.Ignore())

.ForMember(viewModel => viewModel.Phone, options => options.MapFrom<PhoneNumberResolver>());//PhoneNumberResolver implements IValueResolver<ProspectMaintenanceViewModel, ProspectMaintenanceDto, long?>

全部给出“ Core.DTO.MyDto需要具有0个args或仅可选args的构造函数”。尝试绘制地图时:

.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => 5))

尝试配置AutoMapper时会给出“ System.ArgumentException:表达必须是可写的”。

有没有办法让AutoMapper理解它可以完全忽略PhoneNumberString(或者更好的是,我可以使它长映射到字符串,所以我不需要backing属性),而不必使用dto的构造函数?

  

是否有任何特殊的原因要求您的DTO没有默认的构造函数?

我将所有字段都设为只读,以便可以包含一个可修改(例如'Description = description?.Trim();')和验证(例如'if(Phone.HasValue && Phone.ToString()。长度!= 10)抛出...')参数。这样,我可以确保Dto作为值对象始终处于有效状态。

2 个答案:

答案 0 :(得分:2)

1)映射到只读字段

因此,您有一个Dto类:

public class Dto
{
    public readonly long? PhoneNumber;
}

然后您要强制AutoMapper执行此操作:

var dto = new Dto();
dto.PhoneNumber = 123; // <== ERROR! A readonly field cannot be assigned to.

AutoMapper无法写入只读字段或属性。事实上,您都不是。使用protectedprivate设置器将您的字段变成属性:

public class Dto
{
    public long? PhoneNumber { get; private set; }
}

或通过删除readonly关键字使其成为常规字段:

public class Dto
{
    public long? PhoneNumber;
}

2)自定义值解析

ASP.NET MVC

使用ValueResolver

public class StringPhoneNumberResolver : IValueResolver<Dto, ViewModel, string>
{
    private readonly IPhoneNumberService _phoneNumberService;

    public StringPhoneNumberResolver()
    {
        _phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
    }

    public string Resolve(Dto source, ViewModel destination, string destMember, ResolutionContext context)
    {
        return _phoneNumberService.GetFormattedPhoneNumber(source.PhoneNumber);
    }
}

您应该知道,通常在DTO或IValueResolver中进行服务注入是一种反模式。 AutoMapper应该是愚蠢的,各种注入等等都应该在其他地方处理。就是说,这是AutoMapper配置:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Dto, ViewModel>()
        .ForMember(viewModel => viewModel.PhoneNumber, options =>
            options.MapFrom<StringPhoneNumberResolver>());
});

如果要将long ==> string转换为string ==> long的过程,只需添加另一个值解析器:

public class LongPhoneNumberResolver : IValueResolver<ViewModel, Dto, long?>
{
    private readonly IPhoneNumberService _phoneNumberService;

    public LongPhoneNumberResolver()
    {
        _phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
    }

    public long? Resolve(ViewModel source, Dto destination, long? destMember, ResolutionContext context)
    {
        return _phoneNumberService.GetLongPhoneNumber(source.PhoneNumber);
    }
}

.NET Core

如果要在完全支持IServiceCollection集成的.NET Core环境中运行,则应添加以下AutoMapper配置:

serviceCollection.AddAutoMapper(config =>
{
    config.CreateMap<Dto, ViewModel>()
        .ForMember(viewModel => viewModel.PhoneNumber, options =>
            options.MapFrom<StringPhoneNumberResolver>());
}, typeof(Startup));

然后将IPhoneNumberServce自动注入值解析器:

public StringPhoneNumberResolver(IPhoneNumberService phoneNumberService)
{
    _phoneNumberService = phoneNumberService;
}

对于依赖项注入,我使用了automapper.extensions.microsoft.dependencyinjection包。

答案 1 :(得分:0)

好吧,我发现了问题。它与我所认为的完全无关。映射图长吗?来开箱即用

我遇到的问题是拥有完全不同的财产。

我具有以下结构:

public class MyDto
{
    public readonly AddressDto BillingAddress;
    public readonly AddressDto ShippingAddress;
    public readonly long? Phone;
    ...
}
public class AddressDto
{
    public readonly string Country;
    public readonly string SubnationalEntity;
    ...
}
public class MyViewModel
{
    public string BillingAddressCountry { get; set; }
    public string BillingAddressSubnationalEntity { get; set; }
    public string ShippingAddressCountry { get; set; }
    public string ShippingAddressSubnationalEntity { get; set; }
    public string Phone { get; set; }
    ...
}

将其更改为以下内容后,它就会起作用:

public class MyDto
{
    public readonly AddressDto BillingAddress;
    public readonly AddressDto ShippingAddress;
    public readonly long? Phone;
    ...
}
public class AddressDto
{
    public readonly string Country;
    public readonly string SubnationalEntity;
    ...
}
public class MyViewModel
{
    public string AddressViewModel BillingAddress { get; set; }
    public string AddressViewModel ShippingAddress { get; set; }
    public string Phone { get; set; }
    ...
}
public class AddressViewModel
{
    public string Country { get; set; }
    public string SubnationalEntity { get; set; }
    ...
}