Skip to content

Instantly share code, notes, and snippets.

@abdullin
Created October 24, 2011 15:03
Show Gist options
  • Save abdullin/1309255 to your computer and use it in GitHub Desktop.
Save abdullin/1309255 to your computer and use it in GitHub Desktop.
RedirectToWhen
/// <summary>
/// Creates convention-based routing rules
/// </summary>
public sealed class RedirectToDynamicCommand
{
readonly IDictionary<Type, Wire> _dict = new Dictionary<Type, Wire>();
sealed class Wire
{
public MethodInfo Method;
public object Instance;
}
static readonly MethodInfo InternalPreserveStackTraceMethod =
typeof (Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
public void AddWhenMethodsFrom(object o, bool overrideThis = false)
{
var infos = o.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(m => m.Name == "When")
.Where(m => m.GetParameters().Length == 1);
foreach (var methodInfo in infos)
{
var parameterType = methodInfo.GetParameters().First().ParameterType;
var wire = new Wire
{
Instance = o,
Method = methodInfo
};
if (overrideThis)
{
_dict[parameterType] = wire;
}
else
{
_dict.Add(parameterType, wire);
}
}
}
[DebuggerNonUserCode]
public void InvokeCommand(IFunctionalShelfCommand command)
{
Wire info;
var type = command.GetType();
if (!_dict.TryGetValue(type, out info))
{
var s = string.Format("Failed to locate When({0})", type.Name);
throw new InvalidOperationException(s);
}
try
{
info.Method.Invoke(info.Instance, new[] {command});
}
catch (TargetInvocationException ex)
{
if (null != InternalPreserveStackTraceMethod)
InternalPreserveStackTraceMethod.Invoke(ex.InnerException, new object[0]);
throw ex.InnerException;
}
}
}
/// <summary>
/// Creates convention-based routing rules
/// </summary>
public sealed class RedirectToDynamicEvent
{
public readonly IDictionary<Type, List<Wire>> Dict = new Dictionary<Type, List<Wire>>();
public sealed class Wire
{
public Action<object> Call;
}
static readonly MethodInfo InternalPreserveStackTraceMethod =
typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
public void WireToWhen(object o)
{
var infos = o.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(m => m.Name == "When")
.Where(m => m.GetParameters().Length == 1);
foreach (var methodInfo in infos)
{
if (null == methodInfo)
throw new InvalidOperationException();
var wires = new HashSet<Type>();
var parameterType = methodInfo.GetParameters().First().ParameterType;
wires.Add(parameterType);
// if this is an interface, then we wire up to all inheritors in loaded assemblies
// TODO: make this explicit
if (parameterType.IsInterface)
{
var inheritors = typeof (CreateCustomer).Assembly.GetExportedTypes().Where(parameterType.IsAssignableFrom);
foreach (var inheritor in inheritors)
{
wires.Add(inheritor);
}
}
foreach (var type in wires)
{
List<Wire> list;
if (!Dict.TryGetValue(type, out list))
{
list = new List<Wire>();
Dict.Add(type, list);
}
var wire = BuildWire(o, type, methodInfo);
list.Add(wire);
}
}
}
static Wire BuildWire(object o, Type type, MethodInfo methodInfo)
{
var info = methodInfo;
var dm = new DynamicMethod("MethodWrapper", null, new[] {typeof (object), typeof (object)});
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, o.GetType());
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Castclass, type);
il.EmitCall(OpCodes.Call, info, null);
il.Emit(OpCodes.Ret);
var call = (Action<object, object>) dm.CreateDelegate(typeof (Action<object, object>));
var wire = new Wire
{
Call = o1 => call(o, o1)
};
return wire;
}
public void WireTo<TMessage>(Action<TMessage> msg)
{
var type = typeof (TMessage);
List<Wire> list;
if (!Dict.TryGetValue(type, out list))
{
list = new List<Wire>();
Dict.Add(type, list);
}
list.Add(new Wire
{
Call = o => msg((TMessage)o)
});
}
[DebuggerNonUserCode]
public void InvokeEvent(object @event)
{
var type = @event.GetType();
List<Wire> info;
if (!Dict.TryGetValue(type, out info))
{
return;
}
try
{
foreach (var wire in info)
{
wire.Call(@event);
}
}
catch (TargetInvocationException ex)
{
if (null != InternalPreserveStackTraceMethod)
InternalPreserveStackTraceMethod.Invoke(ex.InnerException, new object[0]);
throw ex.InnerException;
}
}
}
/// <summary>
/// Class that statically caches information about methods named
/// <em>When</em> based on the parameter type. Then, given an instance
/// and an argument (command of event) we can pass this argument
/// directly to the corresponding method.
/// </summary>
/// <remarks>For updates visit: https://gist.github.com/1309255 and also
/// http://bliki.abdullin.com/event-sourcing/ on the usage patterns
/// </remarks>
public static class RedirectToWhen
{
private static readonly MethodInfo InternalPreserveStackTraceMethod = typeof(Exception)
.GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
static class Cache<T>
{
public static readonly IDictionary<Type, MethodInfo> Dict = typeof(T)
.GetMethods(BindingFlags.Public |BindingFlags.NonPublic | BindingFlags.Instance)
.Where(m => m.Name == "When")
.Where(m => m.GetParameters().Length == 1)
.ToDictionary(m => m.GetParameters().First().ParameterType, m => m);
}
[DebuggerNonUserCode]
public static void InvokeEventOptional<T>(T instance, IShelfEvent command)
{
MethodInfo info;
var type = command.GetType();
if (!Cache<T>.Dict.TryGetValue(type, out info))
{
// we don't care if state does not consume events
// they are persisted anyway
return;
}
try
{
info.Invoke(instance, new[] {command});
}
catch (TargetInvocationException ex)
{
if (null != InternalPreserveStackTraceMethod)
InternalPreserveStackTraceMethod.Invoke(ex.InnerException, new object[0]);
throw ex.InnerException;
}
}
[DebuggerNonUserCode]
public static void InvokeCommand<T>(T instance, IShelfCommand command)
{
MethodInfo info;
var type = command.GetType();
if (!Cache<T>.Dict.TryGetValue(type, out info))
{
var s = string.Format("Failed to locate {0}.When({1})", typeof (T).Name, type.Name);
throw new InvalidOperationException(s);
}
try
{
info.Invoke(instance, new[] {command});
}
catch (TargetInvocationException ex)
{
if (null != InternalPreserveStackTraceMethod)
InternalPreserveStackTraceMethod.Invoke(ex.InnerException, new object[0]);
throw ex.InnerException;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment