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);
}