Skip to content

Instantly share code, notes, and snippets.

@JakeGinnivan
Created April 16, 2015 11:46
Show Gist options
  • Save JakeGinnivan/f345d34b7b8e9f04dc6d to your computer and use it in GitHub Desktop.
Save JakeGinnivan/f345d34b7b8e9f04dc6d to your computer and use it in GitHub Desktop.
Async Automapper
void Main()
{
var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var mapper = new MappingEngine(configurationStore);
var server = new Server();
configurationStore
.CreateMap<From, To>()
.ForMember(d => d.ServerThing, m => m.ResolveUsing(new FromServer1Resolver(server)).FromMember(s => s.ServerThingId))
.ForMember(d => d.AnotherServerThing, m => m.ResolveUsing(new FromServer2Resolver(server)).FromMember(s => s.AnotherServerThingId));
var fromValue = new From { ServerThingId = "Id1", AnotherServerThingId = "Id2" }.Dump();
mapper.AsyncMap<To>(fromValue).Result.Dump();
}
public static class AsyncExtensions
{
public static Task<TTo> AsyncMap<TTo>(this IMappingEngine mapper, object source)
{
var asyncMapContext = new AsyncContext();
// TODO the exception should be inside the task
var result = mapper.Map<TTo>(source, o => o.Items.Add("AsyncContext", asyncMapContext));
return asyncMapContext.MappingTask.ContinueWith(t => result);
}
}
public class Server
{
public Task<FromServer> GetThing(string id)
{
return Task.Run(() => new FromServer { Id = id, OtherData = Guid.NewGuid().ToString() });
}
public Task<FromServer2> GetAnotherThing(string id)
{
return Task.Run(() => new FromServer2 { Id = id, OtherData = Guid.NewGuid().ToString() });
}
}
public class FromServer1Resolver : AsyncValueResolver<string, FromServer>
{
Server server;
public FromServer1Resolver(Server server)
{
this.server = server;
}
protected override Task<FromServer> GetValue(string fromValue)
{
return server.GetThing(fromValue);
}
}
public class FromServer2Resolver : AsyncValueResolver<string, FromServer2>
{
Server server;
public FromServer2Resolver(Server server)
{
this.server = server;
}
protected override Task<FromServer2> GetValue(string fromValue)
{
return server.GetAnotherThing(fromValue);
}
}
public abstract class AsyncValueResolver<TFrom, TTo> : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
var contextItems = source.Context.Options.Items;
if (!contextItems.ContainsKey("AsyncContext"))
throw new InvalidOperationException("You must use mapper.AsyncMap when using async value resolvers");
var asyncContext = (AsyncContext)contextItems["AsyncContext"];
asyncContext.StartAsyncOperation();
var parentSourceValue = source.Context.SourceValue;
var sourceValue = source.Context.SourceType.GetProperty(source.Context.PropertyMap.SourceMember.Name).GetValue(source.Context.SourceValue);
GetValue((TFrom)source.Value)
.ContinueWith(r =>
{
source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, r.Result);
asyncContext.OperationFinished();
});
return source.Ignore();
}
protected abstract Task<TTo> GetValue(TFrom fromValue);
}
public class AsyncContext
{
object locker = new object();
TaskCompletionSource<object> taskSource = new TaskCompletionSource<object>();
int activeCalls = 0;
public void StartAsyncOperation()
{
lock (locker) { activeCalls++; };
}
public void OperationFinished()
{
var isComplete = false;
lock (locker)
{
activeCalls--;
if (activeCalls == 0)
{
isComplete = true;
}
};
if (isComplete)
taskSource.SetResult(null);
}
public Task MappingTask
{
get { return taskSource.Task; }
}
}
public class To
{
public FromServer ServerThing { get; set; }
public FromServer2 AnotherServerThing { get; set; }
}
public class From
{
public string ServerThingId {get;set;}
public string AnotherServerThingId {get;set;}
}
public class FromServer
{
public string Id { get; set; }
public string OtherData { get; set; }
}
public class FromServer2
{
public string Id { get; set; }
public string OtherData { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment