Skip to content

Instantly share code, notes, and snippets.

@jbogard
Forked from JakeGinnivan/AsyncAutomapper.linq.cs
Last active January 18, 2022 22:31
Show Gist options
  • Save jbogard/9c964dcdc4cd46a31730 to your computer and use it in GitHub Desktop.
Save jbogard/9c964dcdc4cd46a31730 to your computer and use it in GitHub Desktop.
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.MapFrom(s => s.ServerThingId))
.ForMember(d => d.AnotherServerThing, m => m.MapFrom(s => s.AnotherServerThingId));
configurationStore
.CreateMap<string, FromServer>()
.ConvertUsing(new FromServer1Converter(server));
configurationStore
.CreateMap<string, FromServer2>()
.ConvertUsing(new FromServer2Converter(server));
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 FromServer1Converter : AsyncValueConverter<string, FromServer>
{
Server server;
public FromServer1Resolver(Server server)
{
this.server = server;
}
protected override Task<FromServer> GetValue(string fromValue)
{
return server.GetThing(fromValue);
}
}
public class FromServer2Converter : AsyncTypeConverter<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 AsyncTypeConverter<TFrom, TTo> : ITypeConverter<TTo, TFrom>
{
public TDestination Convert(ResolutionContext context);
{
var contextItems = 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 sourceValue = context.SourceValue;
TDestination result;
GetValue((TFrom)sourceValue)
.ContinueWith(r =>
{
result = r.Result;
asyncContext.OperationFinished();
});
return result;
}
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