I am reading the coordinate values from a file using 3rd tool, and tool casts data to a Point
class collection.
public class Point
{
public string Lon {get;set;}
public string Lat {get;set;}
public string Elevation {get;set;}
}
And I want to map Point
class to PointEntity
using automapper.
public class PointEntity
{
public float Lon {get;set;}
public float Lat {get;set;}
public float Elevation {get;set;}
}
I created a Map using Automapper.
public class LocationProfile : Profile
{
public LocationProfile()
{
CreateMap<Point, PointEntity>();
}
}
And I have created a generic mapper extension to map List.
public static class MapperHelper
{
public static TDest MapTo<TDest>(this object src)
{
return (TDest)AutoMapper.Mapper.Map(src, src.GetType(), typeof(TDest));
}
}
IList<Point> data = readData<Point>("file");
var mapped = data.MapTo<PointEntity>();
But sometimes users may have wrong type entries in file like following.
Lon Lat Elevation
--- --- ---------
11.5 25.6 80.56
12ab 89.87 14.83
1.7 x.8 9.3
In this situation, the code throw exception.
So how can I find which row and value is wrong type? (For example row1 and Lon value is wrong)
Change your design thinking. Mapping is simply transferring one data to another data. And automapper is very good at that task. Keep it simple. So, keep that as simple as possible. I read that you want to perform a validation during mapping and provide information about it. That make mapping more complex. In my design I wouldn't let this happen during mapping. Validate this information before mapping. Iterate this before mapping by using an validation rule class. Or validates the data at the time of reading so that you can immediately inform a line and column position.
Example
Use the ConvertUsing (but not needed if validated before) Step to Implementation
public class LocationProfile : Profile
{
public LocationProfile()
{
CreateMap<Point, PointEntity>().ConvertUsing<PointEntityConverter>();
}
}
public class PointEntityConverter : ITypeConverter<Point, PointEntity>
{
public PointEntity Convert(Point source, PointEntity destination, ResolutionContext context)
{
if (destination == null)
destination = new PointEntity();
destination.Lon = Parse(source.Lon, nameof(source.Lon));
destination.Lat = Parse(source.Lat, nameof(source.Lat));
destination.Elevation = Parse(source.Elevation, nameof(source.Elevation));
return destination;
}
private float Parse(string s, string paramName)
{
if (float.TryParse(s, out float result))
return result;
throw new ArgumentException($"Invalide value ({s}) for parameter {paramName}", paramName);
}
Implementation
private readonly IMapper mapper;
private readonly IValidator<Point> validator;
public HowToSpecifyAutomapperExceptionCausedData(IMapper mapper)
{
this.mapper = mapper;
this.validator = new PointValidator();
}
public void Example()
{
var data = readData<Point>("file");
for (int i = 0; i < data.Count; i++)
validator.AssertValide(data[i], (m, p) => OnInvalidArgument(m, p, i));
var mapped = mapper.Map<IList<PointEntity>>(data);
}
private void OnInvalidArgument(string message, string paramName, int index)
{
throw new ArgumentException($"{message} on index {index}", paramName);
}
Validator
internal interface IValidator<T>
{
void AssertValide(T value, InvalideArgumentHandler handler);
}
internal delegate void InvalideArgumentHandler(string message, string paramName);
internal class PointValidator : IValidator<Point>
{
public void AssertValide(Point value, InvalideArgumentHandler handler)
{
AssertValideFloat(value.Lon, nameof(value.Lon), handler);
AssertValideFloat(value.Lat, nameof(value.Lat), handler);
AssertValideFloat(value.Elevation, nameof(value.Elevation), handler);
}
private void AssertValideFloat(string s, string paramName, InvalideArgumentHandler handler)
{
if (!float.TryParse(s, out float result))
handler($"Invalide value ({s}) for parameter {paramName}", paramName);
}
}
Why using a delegate? Other example by collecting all (also for each parameter [lon, lat, ...]) invalide parameters. Here see now the power using a handler for it.
public void Example()
{
var data = readData<Point>("file");
var errors = new List<string>();
for (int i = 0; i < data.Count; i++)
validator.AssertValide(data[i], (m, p) => errors.Add($"{m} on index {i}"));
if (errors.Any()) //using System.Linq;
throw new ApplicationException(string.Join(Environment.NewLine, errors));
var mapped = mapper.Map<IList<PointEntity>>(data);
}