AutoMapper地图长吗?串起来
AutoMapper map long? to string
我有两个 类,一个 ViewModel 和一个 Dto,它们基本上相同,除了 Dto 有一个字段 'readonly long? Phone;' 而 ViewModel 有一个 属性 'string Phone { get; set; }'.
我发现让 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 needs to have a constructor with 0 args or only optional args.',并且:
.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => 5))
在尝试配置 AutoMapper 时给出 'System.ArgumentException: 'Expression must be writeable'。
有什么方法可以让 AutoMapper 明白它可以完全忽略 PhoneNumberString(或者,更好的是,我可以通过某种方法让它映射到长字符串,这样我就不需要支持 属性) 而不必使用 dto 的构造函数?
Is there any special reason that requires your DTO to not have a default constructor?
我将所有字段设为只读,这样我就可以包含一个构造函数来修改(例如 'Description = description?.Trim();')和验证(例如 'if (Phone.HasValue && Phone.ToString().Length != 10) throw ...')参数。这样我可以确保作为值对象的 Dto 始终处于有效状态。
1) 映射到只读字段
所以你有一个 Dto
class:
public class Dto
{
public readonly long? PhoneNumber;
}
然后您试图强制 AutoMapper 执行此操作:
var dto = new Dto();
dto.PhoneNumber = 123; // <== ERROR! A readonly field cannot be assigned to.
AutoMapper 无法写入只读字段或属性。事实上你也不是。使用 protected
或 private
setter:
将您的字段变成 属性
public class Dto
{
public long? PhoneNumber { get; private set; }
}
或通过删除 readonly
关键字使其成为常规字段:
public class Dto
{
public long? PhoneNumber;
}
2) 自定义值解析
ASP.NETMVC
使用 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
中进行服务注入是anti-pattern。 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 核心
如果您要在完全支持 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 包。
嗯,我找到问题了。它与我认为的完全无关。映射映射长?字符串 开箱即用。
我遇到的问题是完全不同的 属性。
我有以下结构:
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; }
...
}
我有两个 类,一个 ViewModel 和一个 Dto,它们基本上相同,除了 Dto 有一个字段 'readonly long? Phone;' 而 ViewModel 有一个 属性 'string Phone { get; set; }'.
我发现让 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 needs to have a constructor with 0 args or only optional args.',并且:
.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => 5))
在尝试配置 AutoMapper 时给出 'System.ArgumentException: 'Expression must be writeable'。
有什么方法可以让 AutoMapper 明白它可以完全忽略 PhoneNumberString(或者,更好的是,我可以通过某种方法让它映射到长字符串,这样我就不需要支持 属性) 而不必使用 dto 的构造函数?
Is there any special reason that requires your DTO to not have a default constructor?
我将所有字段设为只读,这样我就可以包含一个构造函数来修改(例如 'Description = description?.Trim();')和验证(例如 'if (Phone.HasValue && Phone.ToString().Length != 10) throw ...')参数。这样我可以确保作为值对象的 Dto 始终处于有效状态。
1) 映射到只读字段
所以你有一个 Dto
class:
public class Dto
{
public readonly long? PhoneNumber;
}
然后您试图强制 AutoMapper 执行此操作:
var dto = new Dto();
dto.PhoneNumber = 123; // <== ERROR! A readonly field cannot be assigned to.
AutoMapper 无法写入只读字段或属性。事实上你也不是。使用 protected
或 private
setter:
public class Dto
{
public long? PhoneNumber { get; private set; }
}
或通过删除 readonly
关键字使其成为常规字段:
public class Dto
{
public long? PhoneNumber;
}
2) 自定义值解析
ASP.NETMVC
使用 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
中进行服务注入是anti-pattern。 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 核心
如果您要在完全支持 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 包。
嗯,我找到问题了。它与我认为的完全无关。映射映射长?字符串 开箱即用。
我遇到的问题是完全不同的 属性。
我有以下结构:
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; }
...
}