Skip to content

Instantly share code, notes, and snippets.

@jpolvora
Created May 17, 2012 03:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpolvora/2716022 to your computer and use it in GitHub Desktop.
Save jpolvora/2716022 to your computer and use it in GitHub Desktop.
My custom implementation of DynamicDecorator from Gary H Guo
// Created by por Jone Polvora
// Twitter: @jpolvora
// WebSite: http://silverlightrush.blogspot.com
// Based on Dynamic Decorator / CBO Extender Project (http://www.codeproject.com/Articles/275292/Component-Based-Object-Extender)
// Depends on Impromptu-Interface (https://code.google.com/p/impromptu-interface/_
// Last Update: 21/05/2012 10:40AM GMT -04:00
// Brazil
/* USAGE EXAMPLE
static void DecorateContext(DbContext ctx)
{
/********************/
//fluent interface
/********************/
var proxyCtx = ObjectProxyFactory
.Configure<IUnitOfWork>(ctx) //will return an IUnitOfWork object
.WithPreAspect(pre =>
LogEntering(pre.CallCtx.MethodName)) //pre_decoration
.WithPostAspect(pos =>
LogExiting(pos.CallCtx.MethodName)) //post_decoration
.FilterMethods(m1 =>
m1.SaveChanges(),
m2 => m2.ToString(),
m3 => m3.Dispose())
.Build(); //create and return
//execute a method
proxyCtx.SaveChanges(); //will be intercepted
/********************/
//using buider
/********************/
var builder = ObjectProxyFactory.Configure<IUnitOfWork>(ctx);
builder.WithPostAspect(p => LogExiting(p.CallCtx.MethodName));
var proxyResult = builder.Build();
proxyResult.SaveChanges();
}
static void LogEntering(string methodName)
{
System.Console.WriteLine(string.Format("Entering {0}", methodName));
}
static void LogExiting(string methodName)
{
System.Console.WriteLine(string.Format("Exiting {0}", methodName));
}
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using ImpromptuInterface;
namespace DynamicProxy.AllInOneFile
{
public class AspectContext<TTarget>
{
public TTarget Target { get; private set; }
public IMethodCallMessage CallCtx { get; private set; }
public dynamic Parameters { get; set; }
public bool Abort { get; set; }
public AspectContext(TTarget target, IMethodCallMessage callCtx, dynamic parameters)
{
Target = target;
CallCtx = callCtx;
Parameters = parameters;
}
}
public interface IAspectBehavior<TTarget>
{
string[] MethodsToIntercept { get; }
void PreAspect(AspectContext<TTarget> ctx);
void PostAspect(AspectContext<TTarget> ctx);
}
public static class Helpers
{
#region public helper methods to get methodnames
public static string[] GetMethodNames<T>(params Expression<Action<T>>[] expressions)
{
if (expressions == null)
return Enumerable.Empty<string>().ToArray();
return expressions.Select(expression => expression.ExtractMethod()).ToArray();
}
private static string ExtractMethod<T>(this Expression<Action<T>> expression)
{
var methodCall = expression.Body as MethodCallExpression;
if (methodCall == null)
{
throw new ArgumentException("expression");
}
var method = methodCall.Method;
return method.Name;
}
public static string[] GetMethodNamesFromPropertyInfo(bool getters = true, bool setters = true, params PropertyInfo[] propertyInfos)
{
var methodsProperties = new List<string>();
foreach (var propertyName in propertyInfos.Select(propertyInfo => propertyInfo.Name))
{
if (getters) methodsProperties.Add("get_" + propertyName);
if (setters) methodsProperties.Add("set_" + propertyName);
}
return methodsProperties.ToArray();
}
public static string[] GetMethodNamesFromLambdaProperty<T>(bool getters = true, bool setters = true, params Expression<Func<T, object>>[] expressions) where T : class
{
var methodsProperties = new List<string>();
foreach (var propertyName in expressions.Select(expression => expression.ExtractPropertyName()))
{
if (getters) methodsProperties.Add("get_" + propertyName);
if (setters) methodsProperties.Add("set_" + propertyName);
}
return methodsProperties.ToArray();
}
internal static string ExtractPropertyName<T>(this Expression<Func<T, object>> expression)
{
var memberExpression = expression.ToMemberExpression();
var member = memberExpression.Member;
return member.Name;
}
private static MemberExpression ToMemberExpression(this LambdaExpression expression)
{
MemberExpression memberExpression;
if (expression.Body is UnaryExpression)
{
var unary = (UnaryExpression)expression.Body;
memberExpression = (MemberExpression)unary.Operand;
}
else memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("expression");
}
return memberExpression;
}
#endregion
#region helper methods
public static bool Implements<TType>(this Type derived)
{
return Implements(typeof(TType), derived);
}
public static bool Implements(this Type derived, Type baseType)
{
return derived.IsAssignableFrom(baseType);
}
#endregion
public static PropertyInfo[] GetAllProperties(this Type type)
{
List<Type> typeList = new List<Type> { type };
if (type.IsInterface)
{
typeList.AddRange(type.GetInterfaces());
}
return typeList.SelectMany(interfaceType => interfaceType.GetProperties()).ToArray();
}
}
internal class ObjectProxy<TInterface> : RealProxy, IRemotingTypeInfo
{
private readonly Action<AspectContext<TInterface>> _preAspect = delegate { };
private readonly Action<AspectContext<TInterface>> _postAspect = delegate { };
private readonly IAspectBehavior<TInterface> _behavior;
private readonly bool _supressErrors;
private readonly String[] _arrMethods;
private readonly dynamic _parameters;
private readonly TInterface _target;
public TInterface Proxy { get; private set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="target">The object that will be proxied</param>
/// <param name="preAspect">Optional action that will be fired before intercepted method execution</param>
/// <param name="postAspect">Optional action that will be fired after the execution of the intercepted method</param>
/// <param name="behavior">A class that implements IAspectBehavior. If this parameter is not null, the preAspect and postAspect are ignored</param>
/// <param name="parameters">dynamic parameters that will be available during the execution context</param>
/// <param name="supressErrors">True to supress exceptions, otherwise, false to throw exceptions</param>
/// <param name="arrMethods">List of methods that will be intercepted. If null or empty, all methods of the TInterface will be intercepted</param>
protected internal ObjectProxy(
TInterface target,
Action<AspectContext<TInterface>> preAspect,
Action<AspectContext<TInterface>> postAspect,
IAspectBehavior<TInterface> behavior,
dynamic parameters = null,
bool supressErrors = true,
params string[] arrMethods)
: base(typeof(TInterface)) //: base(typeof(MarshallByRefObject))
{
_target = target;
_preAspect = preAspect;
_postAspect = postAspect;
_behavior = behavior;
_parameters = parameters;
_supressErrors = supressErrors;
_arrMethods = _behavior != null ? _behavior.MethodsToIntercept : arrMethods;
Proxy = (TInterface)GetTransparentProxy();
TypeName = string.Format("{0}_{1}", Proxy.GetType().Name, _target.GetType().Name);
}
public override sealed object GetTransparentProxy()
{
return base.GetTransparentProxy();
}
public override IMessage Invoke(IMessage message)
{
var methodMessage = (IMethodCallMessage)message;
var method = methodMessage.MethodBase;
if (!HasMethod(method.Name))
return CreateReturnMessage(InvokeOriginalMethod(methodMessage, false), methodMessage);
var context = new AspectContext<TInterface>(_target, methodMessage, _parameters);
// Perform the preprocessing
var returnMessage = ExecuteAspect(_behavior != null ? _behavior.PreAspect : _preAspect, context);
if (returnMessage != null)
return returnMessage;
// Perform the call
var returnValue = InvokeOriginalMethod(methodMessage, context.Abort);
// Perform the postprocessing
if (!context.Abort)
returnMessage = ExecuteAspect(_behavior != null ? _behavior.PostAspect : _postAspect, context);
if (returnMessage != null)
return returnMessage;
// Create the return message (ReturnMessage)
returnMessage = CreateReturnMessage(returnValue, methodMessage);
return returnMessage;
}
/// <summary>
/// Executes the pre or post aspect, returning null if sucess
/// </summary>
private ReturnMessage ExecuteAspect(Action<AspectContext<TInterface>> aspect, AspectContext<TInterface> context)
{
try
{
if (aspect != null)
aspect.Invoke(context);
}
catch (Exception e)
{
if (_supressErrors)
{
return new ReturnMessage(e, context.CallCtx);
}
throw;
}
return null;
}
/// <summary>
/// Call the current method being intercepted
/// </summary>
private object InvokeOriginalMethod(IMethodMessage methodMessage, bool abort)
{
try
{
if (abort)
return null;
return methodMessage.MethodBase.Name == "GetType"
? typeof(TInterface)
: methodMessage.MethodBase.Invoke(_target, methodMessage.Args);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
}
/// <summary>
/// Creates the return IMessage to the Invoke method
/// </summary>
private static ReturnMessage CreateReturnMessage(object returnValue, IMethodCallMessage methodMessage)
{
return new ReturnMessage(returnValue,
methodMessage.Args,
methodMessage.ArgCount,
methodMessage.LogicalCallContext,
methodMessage);
}
/// <summary>
/// Will check if a method is going to be intercepted. Will return true if empty or null or contains the name.
/// </summary>
private bool HasMethod(string mtd)
{
return _arrMethods == null || _arrMethods.Count() == 0 || _arrMethods.Any(s => s.Equals(mtd));
}
public override ObjRef CreateObjRef(Type type)
{
throw new NotSupportedException("ObjRef for DynamicProxy isn't supported");
}
public bool CanCastTo(Type fromType, object realproxy) // refers to current TransparentProxy object
{
var result = fromType == typeof(TInterface) || fromType == _target.GetType() ||
typeof(TInterface).IsAssignableFrom(fromType) || _target.GetType().Implements(fromType);
return result;
}
public string TypeName { get; set; }
}
public class FluentBuilder<TInterface> where TInterface : class
{
private readonly TInterface _target;
private Action<AspectContext<TInterface>> _preAspect;
private Action<AspectContext<TInterface>> _postAspect;
private dynamic _parameters;
private string[] _methodsFilter;
private IAspectBehavior<TInterface> _behavior;
private Expression<Action<TInterface>>[] _lambdas;
private readonly bool _supressErrors;
internal FluentBuilder(TInterface target, bool supressErrors = true)
{
_target = target;
_supressErrors = supressErrors;
}
public FluentBuilder<TInterface> SetPreDecoration(Action<AspectContext<TInterface>> preAspect)
{
_behavior = null;
_preAspect = preAspect;
return this;
}
public FluentBuilder<TInterface> SetPostDecoration(Action<AspectContext<TInterface>> postAspect)
{
_behavior = null;
_postAspect = postAspect;
return this;
}
public FluentBuilder<TInterface> SetParameters(dynamic parameters)
{
_parameters = parameters;
return this;
}
public FluentBuilder<TInterface> FilterMethods(params string[] methods)
{
_lambdas = null;
_methodsFilter = methods;
return this;
}
public FluentBuilder<TInterface> FilterMethods(params Expression<Action<TInterface>>[] methods)
{
_methodsFilter = null;
_lambdas = methods;
return this;
}
public FluentBuilder<TInterface> ApplyBehavior(IAspectBehavior<TInterface> behavior)
{
_preAspect = null;
_postAspect = null;
_behavior = behavior;
return this;
}
public TInterface Build()
{
if (_lambdas != null)
{
_methodsFilter = Helpers.GetMethodNames(_lambdas);
}
var objectProxy = new ObjectProxy<TInterface>(
_target,
_preAspect,
_postAspect,
_behavior,
_parameters,
_supressErrors,
_methodsFilter);
return objectProxy.Proxy; //GetTransparentProxy()
}
}
public static class ObjectProxyFactory
{
public static FluentBuilder<TInterface> Configure<TInterface>(object target, bool supressErrors = true)
where TInterface : class
{
if (!typeof(TInterface).IsInterface)
throw new ArgumentException("TInterface");
/* automatic duck typing if needed */
TInterface typedTarget = target.GetType().Implements(typeof(TInterface))
? (TInterface)target
: target.ActLike<TInterface>();
return new FluentBuilder<TInterface>(typedTarget, supressErrors);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment