NHibernate Interceptor for Dynamic Proxy Generation
NHibernate comes along with a nice code generation API that is used, for example, for lazy-loading proxy generation. It can, however, be used for other purposes, such as adding interfaces to the loaded entities, for example, INotifyPropertyChanged coming to my mind. Ayende and José have talked about this in the past, but I decided to publish my own updated version.
It has two parts: a custom NHibernate interceptor class that can be added to Configuration or session creation and a custom Castle DynamicProxy interceptor that is created from the former for each new entity loaded from the DB.
Here's the skeleton. You just have to fill in the blanks to add your own behavior.
public class CustomInterceptor : NHibernate.EmptyInterceptor { class _CustomInterceptor : Castle.DynamicProxy.IInterceptor { private Boolean finishedLoading = false; void Castle.DynamicProxy.IInterceptor.Intercept(IInvocation invocation) { Object target = (invocation.InvocationTarget == null) ? invocation.Proxy : invocation.InvocationTarget; //check if the entity has finished loading if ((invocation.InvocationTarget == null) && (this.finishedLoading == false)) { this.finishedLoading = true; } //TODO: do something before base method call if (invocation.Method.Name.StartsWith("get_") == true) { //getter invocation } else if (invocation.Method.Name.StartsWith("set_") == true) { //setter invocation } else { //method invocation } if (invocation.InvocationTarget != null) { //proceed with base implementation (base getter, setter or method call) invocation.Proceed(); } //TODO: do something after base method call if (invocation.Method.Name.StartsWith("get_") == true) { //getter invocation } else if (invocation.Method.Name.StartsWith("set_") == true) { //setter invocation } else { //method invocation } } } private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator(); private ISession session = null; public static Object CreateProxy(Type type) { List<Type> interfaces = new List<Type>(); //TODO: add interfaces to list Object instance = null; if ((interfaces.Count != 0) && (type.IsSealed == false)) { //TODO: pass any custom parameters to the _CustomInterceptor class instance = proxyGenerator.CreateClassProxy(type, interfaces.ToArray(), new _CustomInterceptor()); } else { instance = Activator.CreateInstance(type); } return(instance); } public static T CreateProxy<T>() where T: class, new() { Type type = typeof(T); return(CreateProxy(type) as T); } public override String GetEntityName(Object entity) { if (entity.GetType().Assembly.FullName.StartsWith("DynamicProxyGenAssembly2") == true) { return (entity.GetType().BaseType.FullName); } else { return (entity.GetType().FullName); } } public override void SetSession(ISession session) { this.session = session; base.SetSession(session); } public override Object Instantiate(String clazz, EntityMode entityMode, Object id) { if (entityMode == EntityMode.Poco) { Type type = Type.GetType(clazz, false); if (type != null) { Object instance = CreateProxy(type); this.session.SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode); return (instance); } } return (base.Instantiate(clazz, entityMode, id)); } }
You can add the interceptor at two levels:
- The Configuration object: all sessions will inherit this interceptor
- The ISession object
Here's an example for each:
Configuration cfg = ...; cfg.SetInterceptor(new CustomInterceptor()); ISessionFactory factory = cfg.BuildSessionFactory(); ISession session = factory.OpenSession(new CustomInterceptor());
As you can see, the outer _CustomInterceptor class is the one that NHibernate must know. You should add any interfaces that the proxy should implement. The inner _CustomInterceptor class becomes part of the generated proxy, and the Intercept method will be called for each interceptable (virtual or new) property and method. If you want, you can add code before and after the base call.
I have my own implementation which adds some common interfaces used for data binding, editing and validation: example, INotifyPropertyChanged and example, INotifyPropertyChanging, example, IEditableObject and example, IDataErrorInfo (with the help of NHibernate Validator). Feel free to roll your own!