We are createing a Web.Api application of a angularjs application. The Web.Api returns a json result.
Step one was getting the data:
public List<DataItem>> GetData()
{
return Mapper.Map<List<DataItem>>(dataRepository.GetData());
}
That worked like a charm. Then we made the data repo async and we changed to code to work with it.
public List<DataItem>> GetData()
{
return Mapper.Map<List<DataItem>>(dataRepository.GetDataAsync().Result);
}
Stil no problems. Now we wanted to make my Web.Api full async awayt so we changed it to:
public async Task<List<DataItem>> GetData()
{
return await Mapper.Map<Task<List<DataItem>>>(dataRepository.GetDataAsync());
}
At this moment Automapper gets confused. First we had the following mapper rule: Mapper.CreateMap();
This worked until the web api method became full async. The exception said it was missing a map from
Task<ICollection<Data>> to Task<ICollection<DataItem>>.
The mapper was changed to
Mapper.CreateMap<Task<List<Data>>, Task<List<DataItem>>>();
When validating the configuration it complains that Result cannot be mapped. How should we configure the mappings?
AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.
AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.
Where do I configure AutoMapper? ¶ Configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.
You need to move the async data fetch out of the Map call:
var data = await dataRepository.GetDataAsync();
return Mapper.Map<List<DataItem>>(data);
Alternatively, you can use AutoMapper LINQ projections:
var data = await dbContext.Data.ProjectTo<DataItem>().ToListAsync();
I don't know if your repository exposes IQueryable directly (we don't use repositories). Our apps use the second version pretty much exclusively these days.
Jose's solution proved quite useful to me. I did, however, re-target the extension method to extend IMapper which allowed me to remove the singleton Mapper reference.
public static class MapperExtensions
{
public static Task<TResult> MapAsync<TSource, TResult>(this IMapper mapper, Task<TSource> task)
{
if (task == null)
{
throw new ArgumentNullException(nameof(task));
}
var tcs = new TaskCompletionSource<TResult>();
task
.ContinueWith(t => tcs.TrySetCanceled(), TaskContinuationOptions.OnlyOnCanceled);
task
.ContinueWith
(
t =>
{
tcs.TrySetResult(mapper.Map<TSource, TResult>(t.Result));
},
TaskContinuationOptions.OnlyOnRanToCompletion
);
task
.ContinueWith
(
t => tcs.TrySetException(t.Exception),
TaskContinuationOptions.OnlyOnFaulted
);
return tcs.Task;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With