问题
I have a DTO I want to map to an entity. The entity has some properties decorated with the MaxLength
attribute.
I would like AutoMapper to truncate all the strings coming from the DTO when mapping to my entity according to the MaxLength
for each property, so that I don't get validation errors when saving the entity.
So, if entity is defined like this:
public class Entity
{
[MaxLength(10)]
string Name { get; set; }
}
I would like that doing this:
var myDto = new MyDto() { Name = "1231321312312312312312" };
var entity = Mapper.Map<Entity>(myDto);
The resulting entity
should have its Name
limited to a maximum of 10 characters.
回答1:
I'm not sure that it's a good place to put that logic, but here is an example that should work in your case (AutoMapper 4.x): Custom Mapping with AutoMapper
In this example, I'm reading a custom MapTo
property on my entity, you could do the same with MaxLength
.
Here a full example with the current version of AutoMapper (6.x)
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(configuration =>
configuration.CreateMap<Dto, Entity>()
.ForMember(x => x.Name, e => e.ResolveUsing((dto, entity, value, context) =>
{
var result = entity.GetType().GetProperty(nameof(Entity.Name)).GetCustomAttribute<MaxLengthAttribute>();
return dto.MyName.Substring(0, result.Length);
})));
var myDto = new Dto { MyName = "asadasdfasfdaasfasdfaasfasfd12" };
var myEntity = Mapper.Map<Dto, Entity>(myDto);
}
}
public class Entity
{
[MaxLength(10)]
public string Name { get; set; }
}
public class Dto
{
public string MyName { get; set; }
}
回答2:
For AutoMapper 8.0, and building on @SuperJMN's answer:
Create a file AutoMapperExtensions.cs in your project:
using AutoMapper;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
namespace YourNamespaceHere
{
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination> LimitStrings<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.Configuration.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType);
var propertyMaps = existingMaps.PropertyMaps.Where(map => !map.Ignored && ((PropertyInfo)map.SourceMember).PropertyType == typeof(string));
foreach (var propertyMap in propertyMaps)
{
var attr = propertyMap.DestinationMember.GetCustomAttribute<MaxLengthAttribute>();
if (attr != null)
{
expression.ForMember(propertyMap.DestinationMember.Name,
opt => opt.ConvertUsing(new StringLimiter(attr.Length), propertyMap.SourceMember.Name));
}
}
return expression;
}
}
public class StringLimiter : IValueConverter<string, string>
{
private readonly int length;
private readonly PropertyInfo propertyMapSourceMember;
public StringLimiter(int length)
{
this.length = length;
propertyMapSourceMember = propertyMapSourceMember ?? throw new ArgumentNullException(nameof(propertyMapSourceMember));
}
public string Convert(string sourceMember, ResolutionContext context)
{
var sourceValue = (string)propertyMapSourceMember.GetValue(sourceMember);
return new string(sourceValue.Take(length).ToArray());
}
}
}
... and add the following to the end of your CreateMap (e.g.):
.ForMember(
dest => dest.ShortField,
opts => opts.MapFrom(src => src.LongField))
.LimitStrings();
回答3:
For Automapper 4.x I got something with this code:
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(configuration =>
configuration.CreateMap<Dto, Entity>()
.ForMember(x => x.Name, e => e.MapFrom(d => d.MyName))
.ForMember(x => x.Free, e => e.MapFrom(d => d.Free))
.ForMember(x => x.AnotherName, e => e.MapFrom(d => d.Another))
.LimitStrings());
var dto = new Dto() { MyName = "asadasdfasfdaasfasdfaasfasfd12", Free = "ASFÑLASJDFÑALSKDJFÑALSKDJFAMLSDFASDFASFDASFD", Another = "blalbalblalblablalblablalblablalblablabb"};
var entity = Mapper.Map<Entity>(dto);
}
}
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> LimitStrings<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType);
var propertyMaps = existingMaps.GetPropertyMaps().Where(map => !map.IsIgnored() && ((PropertyInfo)map.SourceMember).PropertyType == typeof(string));
foreach (var propertyMap in propertyMaps)
{
var attr = propertyMap.DestinationProperty.MemberInfo.GetCustomAttribute<MaxLengthAttribute>();
if (attr != null)
{
expression.ForMember(propertyMap.DestinationProperty.Name,
opt => opt.ResolveUsing(new StringLimiter(attr.Length, (PropertyInfo) propertyMap.SourceMember)));
}
}
return expression;
}
}
public class StringLimiter : IValueResolver
{
private readonly int length;
private readonly PropertyInfo propertyMapSourceMember;
public StringLimiter(int length, PropertyInfo propertyMapSourceMember)
{
this.length = length;
this.propertyMapSourceMember = propertyMapSourceMember ?? throw new ArgumentNullException(nameof(propertyMapSourceMember));
}
public ResolutionResult Resolve(ResolutionResult source)
{
var sourceValue = (string)propertyMapSourceMember.GetValue(source.Context.SourceValue);
var result = new string(sourceValue.Take(length).ToArray());
return source.New(result);
}
}
Please, tell me if it has sense or has some bugs!
Thanks to @bidou for the tip. Here is the post where I took the inspiration here
来源:https://stackoverflow.com/questions/49799389/how-to-make-automapper-truncate-strings-according-to-maxlength-attribute