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.

Revisions

  1. JakeGinnivan created this gist Apr 16, 2015.
    154 changes: 154 additions & 0 deletions AsyncAutomapper.linq.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    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; }
    }