Implementing an Interceptor Using NHibernate’s Built In Dynamic Proxy Generator
- by Ricardo Peres
NHibernate 3.2 came with an included proxy generator, which means there is no longer the need – or the possibility, for that matter – to choose Castle DynamicProxy, LinFu or Spring. This is actually a good thing, because it means one less assembly to deploy. Apparently, this generator was based, at least partially, on LinFu. As there are not many tutorials out there demonstrating it’s usage, here’s one, for demonstrating one of the most requested features: implementing INotifyPropertyChanged. This interceptor, of course, will still feature all of NHibernate’s functionalities that you are used to, such as lazy loading, and such. We will start by implementing an NHibernate interceptor, by inheriting from the base class NHibernate.EmptyInterceptor. This class does not do anything by itself, but it allows us to plug in behavior by overriding some of its methods, in this case, Instantiate: 1: public class NotifyPropertyChangedInterceptor : EmptyInterceptor
2: {
3: private ISession session = null;
4:
5: private static readonly ProxyFactory factory = new ProxyFactory();
6:
7: public override void SetSession(ISession session)
8: {
9: this.session = session;
10: base.SetSession(session);
11: }
12:
13: public override Object Instantiate(String clazz, EntityMode entityMode, Object id)
14: {
15: Type entityType = Type.GetType(clazz);
16: IProxy proxy = factory.CreateProxy(entityType, new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as IProxy;
17:
18: _NotifyPropertyChangedInterceptor interceptor = proxy.Interceptor as _NotifyPropertyChangedInterceptor;
19: interceptor.Proxy = this.session.SessionFactory.GetClassMetadata(entityType).Instantiate(id, entityMode);
20:
21: this.session.SessionFactory.GetClassMetadata(entityType).SetIdentifier(proxy, id, entityMode);
22:
23: return (proxy);
24: }
25: }
Then we need a class that implements the NHibernate dynamic proxy behavior, let’s place it inside our interceptor, because it will only need to be used there:
1: class _NotifyPropertyChangedInterceptor : NHibernate.Proxy.DynamicProxy.IInterceptor
2: {
3: private PropertyChangedEventHandler changed = delegate { };
4:
5: public Object Proxy
6: {
7: get;
8: set;}
9:
10: #region IInterceptor Members
11:
12: public Object Intercept(InvocationInfo info)
13: {
14: Boolean isSetter = info.TargetMethod.Name.StartsWith("set_") == true;
15: Object result = null;
16:
17: if (info.TargetMethod.Name == "add_PropertyChanged")
18: {
19: PropertyChangedEventHandler propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
20: this.changed += propertyChangedEventHandler;
21: }
22: else if (info.TargetMethod.Name == "remove_PropertyChanged")
23: {
24: PropertyChangedEventHandler propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
25: this.changed -= propertyChangedEventHandler;
26: }
27: else
28: {
29: result = info.TargetMethod.Invoke(this.Proxy, info.Arguments);
30: }
31:
32: if (isSetter == true)
33: {
34: String propertyName = info.TargetMethod.Name.Substring("set_".Length);
35: this.changed(this.Proxy, new PropertyChangedEventArgs(propertyName));
36: }
37:
38: return (result);
39: }
40:
41: #endregion
42: }
What this does for every interceptable method (those who are either virtual or from the INotifyPropertyChanged) is:
For methods that came from the INotifyPropertyChanged interface, add_PropertyChanged and remove_PropertyChanged (yes, events are methods ), we add an implementation that adds or removes the event handlers to the delegate which we declared as changed;
For all the others, we direct them to the place where they are actually implemented, which is the Proxy field;
If the call is setting a property, it fires afterwards the PropertyChanged event.
In order to use this, we need to add the interceptor to the Configuration before building the ISessionFactory:
1: using (ISessionFactory factory = cfg.SetInterceptor(new NotifyPropertyChangedInterceptor()).BuildSessionFactory())
2: {
3: using (ISession session = factory.OpenSession())
4: using (ITransaction tx = session.BeginTransaction())
5: {
6: Customer customer = session.Get<Customer>(100); //some id
7: INotifyPropertyChanged inpc = customer as INotifyPropertyChanged;
8: inpc.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e)
9: {
10: //fired when a property changes
11: };
12: customer.Address = "some other address"; //will raise PropertyChanged
13: customer.RecentOrders.ToList(); //will trigger the lazy loading
14: }
15: }
Any problems, questions, do drop me a line!