Click here to Skip to main content
Click here to Skip to main content

Extending Castle DynamicProxy

, 8 May 2010
Rate this:
Please Sign up or sign in to vote.
Shows how to extend the proxy generated by this framework by using Reflection.Emit.

Contents

Introduction

In my last article, I showed how one could "extend" a framework using custom type descriptors. Unfortunately, the modifications you make using this mechanism are only visible through TypeDescriptor's inspection since they are not real. In this article, I'm going to show how one could really extend a framework with Castle MicroKernel/DynamicProxy for Inversion of Control.

Castle DynamicProxy was created with a very specific task: Aspect Oriented Programming through method interception. Its developers didn't create it to allow third party extensions. However, Castle Project libraries are so well designed and loosely coupled, that after downloading and examining its source codes, it was not hard for me to find some "unintended" extension points that would allow me to not only modify the proxies generated, but to integrate this new functionality with Castle Microkernel/Windsor.

The following topics will be covered in this article:

  • Extending Castle DynamicProxy to allow one to plug in contributors (ITypeContributor objects) that make modifications to the dynamic type generated.
  • Integrating this functionality with Microkernel/Windsor, allowing consumers to specify their own ITypeContributors to the ComponentModel.
  • Building a Microkernel 'facility', a class whose primary purpose is to extend or integrate Microkernel with another framework. For this, I'm going to show a facility that integrates the PresentationModel framework with Microkernel.
  • Show how these modifications affected the demo application in my last article.

Target audience

This article is for people who are interested in Inversion of Control, or in extending an existing framework at runtime. I will also show a more advanced usage of Microkernel/Windsor, so it might be interesting to users of this container. It is expected that the reader has some knowledge with creating proxies, either with Castle's libraries, or with any other framework. Here is a great tutorial for understanding what proxies are and learning how to use Castle DynamicProxy.

Extending the dynamic proxy

To manually create a proxy with Castle's DynamicProxy, one does have to instantiate the ProxyGenerator class, then call one of its many methods, optionally passing options or interceptors:

ProxyGenerator generator = new ProxyGenerator();
var proxy = generator.CreateClassProxy<SomeType<(interceptor1, interceptor2);

The ProxyGenerator class delegates the job to an internal service, the IProxyBuilder, and that's why it has an overload to the constructor:

ProxyGenerator generator = new ProxyGenerator(someIProxyBuilderObject);

If no IProxyBuilder is passed, it will use a default implementation, the DefaultProxyBuilder. The DefaultProxyBuilder itself will only choose an appropriate class for building a specific type of proxy (there are interface/class proxies, with or without target). After looking into each generator class code, I noticed that at some point, it passes the dynamic type being emitted to 'ITypeContributor' objects for processing. Here is the contract:

public interface ITypeContributor
{
    void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model);
    void Generate(ClassEmitter @class, ProxyGenerationOptions options);
}

The ClassEmitter exposes the System.Reflection.Emit.TypeBuilder in the TypeBuilder property. So each ITypeContributor can make any modifications to the dynamic type. The problem is that there is no built-in way to insert a custom ITypeContributor for processing; the contributor's collection is created inside a method that each of the generator class has. Here is the signature for the method inside the ClassProxyGenerator class:

protected virtual IEnumerable<Type> GetTypeImplementerMapping(Type[] interfaces, 
   out IEnumerable<ITypeContributor> contributors, INamingScope namingScope);

It returns the contributors as out parameters. Luckily, it's a virtual method, and so it's possible to insert more contributors if this class is extended. Since each of the specialized proxy generating classes has its own version of the method, all of them have to be extended:

protected override IEnumerable<Type> GetTypeImplementerMapping(Type[] interfaces, 
        out IEnumerable<ITypeContributor> contributors, INamingScope namingScope)
{
    IEnumerable<ITypeContributor> contr;
    var ret = base.GetTypeImplementerMapping(interfaces, out contr, namingScope);
    var list = contr.ToList();
    list.AddRange(_proxyBuilder.Contributors);
    contributors = list;
    return ret;
}

The _proxyBuilder field is a reference to the custom ProxyBuilder I had to implement. Unfortunately, the DefaultProxyBuilder class does not offer any extension points, so I had to build one from the scratch. Basically, I copied the code from the DefaultProxyBuilder and got to this:

public class ExtensibleProxyBuilder : IProxyBuilder
{
    private ILogger _logger = NullLogger.Instance;
    private readonly ModuleScope _scope;

    public ExtensibleProxyBuilder(params ITypeContributor[] contributors)
        : this(new ModuleScope(), contributors)
    {
       
    }

    public ExtensibleProxyBuilder(ModuleScope scope, 
           params ITypeContributor[] contributors)
    {
        this._scope = scope;
        foreach (var item in contributors)
        {
            _contributors.Add(item);
        }
    }   

    private List<ITypeContributor> _contributors = new List<ITypeContributor>();
    public List<ITypeContributor> Contributors
    {
        get { return _contributors; }
    }

    public Type CreateClassProxyType(Type classToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(classToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleClassProxyGenerator(_scope, 
                            classToProxy, this) { Logger = _logger };
        return generator.GenerateCode(additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, Type targetType, 
           ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleInterfaceProxyWithTargetGenerator(
                            _scope, interfaceToProxy, this) { Logger = _logger };
        return generator.GenerateCode(targetType, additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleInterfaceProxyWithTargetInterfaceGenerator(
                            _scope, interfaceToProxy, this) { Logger = _logger };
        return generator.GenerateCode(interfaceToProxy, additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new InterfaceProxyWithoutTargetGenerator(_scope, 
                              interfaceToProxy) { Logger = _logger };
        return generator.GenerateCode(typeof(object), additionalInterfacesToProxy, options);
    }
}

This class has other methods, but the principal are these. This is pretty much a copy from the DefaultProxyBuilder, except it has a list to insert a custom user defined ITypeContributor, and creates the specialized proxy generator classes that I extended. Now it is easy to make modifications to the proxy:

ExtensibleProxyBuilder builder = new ExtensibleProxyBuilder(tcontributor1, tcontributor2);
ProxyGenerator generator = new ProxyGenerator(builder);

Integrating with Microkernel

By default, Microkernel does not create proxies of its components, even if interceptors are added to the ComponentModel. There is a property of type IProxyFactory in Microkernel/Windsor that is responsible to create the proxies when needed; here is the contract:

public interface IProxyFactory
{
    void AddInterceptorSelector(IModelInterceptorsSelector selector);
    object Create(IKernel kernel, object instance, ComponentModel model, 
                  CreationContext context, params object[] constructorArguments);
    bool RequiresTargetInstance(IKernel kernel, ComponentModel model);
    bool ShouldCreateProxy(ComponentModel model);
}

The difference between Microkernel and Windsor is that Microkernel has a dummy implementation of this interface that always returns false in the ShouldCreateProxy method. To integrate this new ExtensibleProxyBuilder with Microkernel/Windsor, we need to create a new IProxyFactory. The DefaultProxyFactory has only one method that allows us to customize the proxy object, but since the type was already created, it will do no good. And since it does not expose the ShouldCreateProxy as virtual, the only option is to implement the IProxyFactory from scratch. Again, this was a copy and paste operation, with some minor but important modifications in the methods Create and ShouldCreateProxy:

public object Create(IKernel kernel, object target, ComponentModel model, 
       CreationContext context, params object[] constructorArguments)
{
    ExtensibleProxyBuilder proxyBuilder;
    if (model.ExtendedProperties.Contains(ExtensibleProxyConstants.ProxyTypeContributorsKey))
        proxyBuilder = new ExtensibleProxyBuilder(_moduleScope, 
            (model.ExtendedProperties[ExtensibleProxyConstants.ProxyTypeContributorsKey] 
             as List<ITypeContributor>).ToArray());
    else
        proxyBuilder = new ExtensibleProxyBuilder(_moduleScope);

    ProxyGenerator generator = new ProxyGenerator(proxyBuilder);
    //In the 'DefaultProxyFactory' the 'ProxyGenerator' is a field
    //but since I will pass diferent contributors based on the ComponentModel,
    //it must be created everytime(A little more costly, but not enough for 
    //the user to notice, and definitly worth it)
    //following this is the code from the original proxy factory.
}

public bool ShouldCreateProxy(ComponentModel model)
{
    if (model.ExtendedProperties.Contains(ExtensibleProxyConstants.ProxyTypeContributorsKey))
        return true;
        
    //Same here, only an additional check to see if a proxy should be 
    //created, the rest is just like the original
}

That's all that is needed to integrate this Extensible DynamicProxy with Microkernel. Now I'm going to show how to apply this on the sample from my last article.

Integrating the PresentationModel framework with WPF

On my last sample, I created a configuration assembly that contained all the code to register components in Windsor. Most of that code was related to integrating the PresentationModel framework (which is independent of any UI engine) to WPF. This time, I moved all that configuration to an assembly that contains a specialized facility (Microkernel extension). The difference is that in this facility, I use classes that implement IContributeComponentModelConstruction to modify the component model as it is registered. I also removed all custom type descriptors, and replaced them for implementations of the ITypeContributor interface. Here is a sample IContributeComponentModelConstruction with its correspondent ITypeContributor:

public class IsWorkingPropertyComponentContributor : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (typeof(IPresentationModel).IsAssignableFrom(model.Implementation))
        {
            var contributors = model.GetTypeContributors();
            contributors.Add(new NotificatingPropertyTypeContributor(
                             "IsWorking", typeof(bool)));
        }
    }
}

public class NotificatingPropertyTypeContributor : ITypeContributor
{
    public NotificatingPropertyTypeContributor(string propertyName, Type propertyType)
    {
        _propertyName = propertyName;
        _propertyType = propertyType;
    }

    string _propertyName;
    Type _propertyType;

    public void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
    {

    }

    public void Generate(ClassEmitter @class, ProxyGenerationOptions options)
    {
        GenerateSimpleProperty(@class.TypeBuilder);
    
    }

    protected virtual void GenerateSimpleProperty(TypeBuilder typeBuilder)
    {
        var fieldBuilder = typeBuilder.DefineField(
            string.Format("_{0}", _propertyName),
            _propertyType,
            FieldAttributes.Private);
        var propertyBuilder = typeBuilder.DefineProperty(
            _propertyName,
            PropertyAttributes.None,
            _propertyType,
            Type.EmptyTypes);
      
        var getterBuilder = typeBuilder.DefineMethod(
            string.Format("get_{0}", _propertyName),
            MethodAttributes.Public | MethodAttributes.HideBySig | 
            MethodAttributes.SpecialName,
            _propertyType,
            Type.EmptyTypes);
        var getterGenerator = getterBuilder.GetILGenerator();
        getterGenerator.Emit(OpCodes.Ldarg_0);
        getterGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
        getterGenerator.Emit(OpCodes.Ret);

        var setterBuilder = typeBuilder.DefineMethod(
            string.Format("set_{0}", _propertyName),
            MethodAttributes.Public | MethodAttributes.HideBySig | 
            MethodAttributes.SpecialName,
            null,
            new Type[] { _propertyType });
        var setterGenerator = setterBuilder.GetILGenerator();
        Label returnInstruction = setterGenerator.DefineLabel();
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
        setterGenerator.Emit(OpCodes.Ldarg_1);
        setterGenerator.Emit(OpCodes.Ceq);
        setterGenerator.Emit(OpCodes.Brtrue, returnInstruction);
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldarg_1);
        setterGenerator.Emit(OpCodes.Stfld, fieldBuilder);
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldstr, _propertyName);            
        setterGenerator.Emit(OpCodes.Callvirt, 
           typeBuilder.BaseType.GetMethod("OnPropertyChanged", 
           BindingFlags.NonPublic | BindingFlags.Instance));
        setterGenerator.MarkLabel(returnInstruction);
        setterGenerator.Emit(OpCodes.Ret);
    
        propertyBuilder.SetGetMethod(getterBuilder);
        propertyBuilder.SetSetMethod(setterBuilder);
    }
}

The NotificatingPropertyTypeContributor class allows the user to add a simple property that raises change notifications on the PresentationModel. Some people may be uncomfortable in programming against a stack machine, but know that in .NET 4.0, you can write dynamic methods using Expression Trees. I did this in CIL to make this sample compatible with .NET 3.5. The IsWorking contributor creates a property with similar behavior to this:

private bool _isWorking;
public virtual bool IsWorking
{
    get { return _isWorking; }
    set
    {
        if (_isWorking == value)
            return;
        _isWorking = value;
        OnPropertyChanged("IsWorking");
    }
}

The CallPropertyTypeContributor adds this property to the PresentationModels:

private MethodCallCommand _call
public MethodCallCommand Call
{
    get
    {
        if (_call == null)
            _call = new MethodCallCommand(this);
        return _call;
    }
}

All interceptors and ITypeContributor implementations will be added to the ComponentModel by the IContributeComponentModelConstruction implementations, which in turn will be added to the Microkernel facility. This facility is reusable in any PresentationModel/WPF project:

public class PresentationModelWpfFacility : AbstractFacility
{
    protected override void Init()
    {
        if (!typeof(ExtensibleProxyFactory).IsAssignableFrom(
                    Kernel.ProxyFactory.GetType()))
            Kernel.ProxyFactory = new ExtensibleProxyFactory();

        HandleContributors();
        RegisterComponents();
    }

    protected void HandleContributors()
    {
        var propertyDIContributor = 
          Kernel.ComponentModelBuilder.Contributors.OfType<
          PropertiesDependenciesModelInspector>().Single();
        Kernel.ComponentModelBuilder.RemoveContributor(propertyDIContributor);
        Kernel.ComponentModelBuilder.AddContributor(
               new CallPropertyComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(new 
               EntityViewEncapsulationPropertiesComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(
               new IsWorkingPropertyComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(
               new AutomaticThreadingComponentContributor());
    }

    protected void RegisterComponents()
    {
        Kernel.AddComponentInstance("presentationmodel.proxyoptions", 
               new ProxyOptions() { Hook = new ThreadingProxyHook() });
        Kernel.Register
            (
            Component.For(typeof(ObservableCollection<>), 
              typeof(ICollection<>)).LifeStyle.Is(LifestyleType.Transient),
            Component.For<WpfDialogSystem, IDialogSystem>(),
            Component.For<CustomActionPresentationModel, 
              ICustomActionPresentationModel>().LifeStyle.Is(LifestyleType.Transient),
            Component.For<DispatchInterceptor>(),
            Component.For<BackgroundWorkInterceptor>()
            );
    }
}

I also did make changes to the application core and framework. Firstly, I removed the EntityViewPresentationModel<T> class. Now there is only the IEntityViewPresentationModel<T>> interface:

public interface IEntityViewPresentationModel<T> : ISelectableViewPresentationModel
       where T : class, new()
{
    T Entity { get; set; }
}

This interface has a simple implementation, and a PresentationModel can now implement this interface multiple times if it wishes to display the properties of more than one entity type. That's why I made a change to the EncapsulatesPropertyAttribute:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class EncapsulatesPropertyAttribute : Attribute
{
    public EncapsulatesPropertyAttribute(Type entityType, string propertyName)
    {
        _entityType = entityType;
        _propertyName = propertyName;
    }        

    private string _propertyName;
    public string PropertyName
    {
        get { return _propertyName; }
        set { _propertyName = value; }
    }      

    private Type _entityType;
    public Type EntityType
    {
        get { return _entityType; }
        set { _entityType = value; }
    }
}

This allows a PresentationModel to encapsulate properties of many entities at once. Here is the code for EntityViewEncapsulationPropertiesComponentContributor:

public class EntityViewEncapsulationPropertiesComponentContributor : 
       IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        var contractDefiniton = typeof(IEntityViewPresentationModel<>);
        var entityViews = model.Implementation.GetInterfaces()
             .Where(t => t.IsGenericType && 
                    t.GetGenericTypeDefinition() == contractDefiniton);
            
        if (entityViews.Count() > 0) 
        {
            foreach (var entityView in entityViews)
            {
                var entityType = entityView.GetGenericArguments()[0];
                var contributors = model.GetTypeContributors();                    
                var propertiesToBeEncapsulated =
                    model.Implementation.GetCustomAttributes(
                    typeof(EncapsulatesPropertyAttribute), true)
                    .OfType<EncapsulatesPropertyAttribute>()
                    .Where(attr => attr.EntityType == entityType);

                foreach (var encapsulatedProperty in propertiesToBeEncapsulated)
                {                      
                    //checking if the property exits in the type
                    PropertyInfo pi = 
                      entityType.GetProperty(encapsulatedProperty.PropertyName);
                    if (pi == null)
                        continue;
                    var typeContributor =
                        new EntityViewEncapsulationPropertyTypeContributor(
                            pi.Name,
                            entityType,
                            pi.PropertyType,
                            model.Implementation);
                    contributors.Add(typeContributor);
                }
            }          
        }
    }
}

In simple terms, this will look for each interface implemented by the PresentationModel that is a constructed generic interface of the IEntityViewPresentationModel<> generic interface definition. For each of these interfaces, it will construct properties that encapsulate the properties specified by the EncapsulatesPropertyAttribute that are tied with the type parameter of the generic interface. Now, here is the new configuration class:

public class ConfigurationManager
{
    internal static DefaultKernel _microkernel = new DefaultKernel();

    public static void Configure()
    {
        AppDomain.CurrentDomain.SetData("servicelocator", _microkernel);
        _microkernel.AddFacility<PresentationModelWpfFacility>();

        
        _microkernel.Register
            (
            Component.For<MainViewPresentationModel, 
              IMainViewPresentationModel>().
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For<ProductsViewPresentationModel, 
              IEntityCollectionViewPresentationModel<Product>>().
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For(typeof(DummyDao<>), typeof(IDao<>)).
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For(typeof(List<>), typeof(IList<>)).
              LifeStyle.Is(LifestyleType.Transient),
            Component.For<ProductEditViewPresentationModel, 
              IEntityViewPresentationModel<Product>>().
              LifeStyle.Is(LifestyleType.Transient)
            );
        InsertStubData();
    }
}

Much simpler, right? Notice that I replaced Windsor with Microkernel. Although Windsor has more features than Microkernel, the DefaultProxyFactory was the only Windsor-exclusive feature being used . Since I created a new IProxyFactory implementation that is independent of Windsor, I'd rather not create unnecessary dependencies.

Conclusion

As shown in this article, with dynamic proxies and Inversion of Control, it's possible to integrate frameworks that are unaware of each other. It is also possible to greatly reduce the amount of repetitive code by generating it dynamically. What I have shown here is not 1% of the possibilities a programmer can achieve by building types at runtime. It would, for example, be possible to generate specialized ICommand implementations as nested types on each PresentationModel proxy (these ICommands would be created based on the methods contained in the PresentationModel).

I hope you liked what I showed here; please leave your comments/suggestions so I can improve my future articles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Software developer specialized in the .NET framework

Comments and Discussions

 
GeneralMy vote of 4 Pinmemberdubiousadvocate16-May-11 9:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 8 May 2010
Article Copyright 2010 by Thiago de Arruda
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid