All Code Dies and Burns in Time

After having conversation on twitter with Jonas Folleso about an automatic INotifyPropertyChanged implementation, I couldn’t resist digging in and seeing what I can do. He also has a blog post that is definitely worth reading on this subject. He solved the same problem through the IProvider functionality in Ninject. The code is based on his initial implementation. I had originally planned to do this as a demo of how to extend Ninject.Extensions.Interception, but I decided to roll it into the trunk of the project instead. There is a set of goals I wanted to accomplish with the IANPC implementation

Create a property [NotifyOfChanges] that would indicate that a particular property should be intercepted and participate in IANPC

Be able to use that same property on a class to indicate that you would like to proxy all available properties for IANPC

Have additional dependency property notifications so multiple events will be triggered.

Create a property [DoNotNotifyOfChanges] that would indicate that a particular property should not notify property changes, but will still be available for other interception schemes. This is only applicable if [NotifyOfChanges] was applied at the class level.

We first need to start with a contract that the ViewModel will need to adhere to in order to participate in our party. We could add more options to suppress messages dynamically, but let’s keep it clean for now.

Now that we have our contract and attributes we need to integrate them into the planning pipeline for the interception extension. We want to hook into the planning pipeline by implementing IPlanningStrategy::Execute(IPlan). When Ninject resolves a service it builds an activation plan. Prior to creating the instance, Ninject loops through all of the registered planning strategies executing the plan, configuring directives, and getting things ready for the activation strategies. Here we will find candidate properties and register the interceptors associated with them.

publicclassAutoNotifyInterceptorRegistrationStrategy:InterceptorRegistrationStrategy{publicAutoNotifyInterceptorRegistrationStrategy(IAdviceFactoryadviceFactory,IAdviceRegistryadviceRegistry):base(adviceFactory,adviceRegistry){}publicoverridevoidExecute(IPlanplan){if(!typeof(IAutoNotifyPropertyChanged).IsAssignableFrom(plan.Type)){return;}IEnumerable<MethodInfo>candidates=GetCandidateMethods(plan.Type);RegisterClassInterceptors(plan.Type,plan,candidates);foreach(MethodInfomethodincandidates){PropertyInfoproperty=method.GetPropertyFromMethod(method.DeclaringType);NotifyOfChangesAttribute[]attributes=property.GetAllAttributes<NotifyOfChangesAttribute>();if(attributes.Length==0){continue;}RegisterMethodInterceptors(plan.Type,method,attributes);// Indicate that instances of the type should be proxied.if(!plan.Has<ProxyDirective>()){plan.Add(newProxyDirective());}}}protectedoverridevoidRegisterClassInterceptors(Typetype,IPlanplan,IEnumerable<MethodInfo>candidates){NotifyOfChangesAttribute[]attributes=type.GetAllAttributes<NotifyOfChangesAttribute>();if(attributes.Length==0){return;}foreach(MethodInfomethodincandidates){PropertyInfoproperty=method.GetPropertyFromMethod(method.DeclaringType);if(!property.HasAttribute<DoNotNotifyOfChangesAttribute>()){RegisterMethodInterceptors(type,method,attributes);}}// Indicate that instances of the type should be proxied.if(!plan.Has<ProxyDirective>()){plan.Add(newProxyDirective());}}protectedoverrideboolShouldIntercept(MethodInfomethodInfo){if(!IsPropertySetter(methodInfo)){returnfalse;}if(IsDecoratedWithDoNotNotifyChangesAttribute(methodInfo)){returnfalse;}returnbase.ShouldIntercept(methodInfo);}privatestaticboolIsPropertySetter(MethodBasemethodInfo){returnmethodInfo.IsSpecialName&&methodInfo.Name.StartsWith("set_");}privatestaticboolIsDecoratedWithDoNotNotifyChangesAttribute(MethodInfomethodInfo){PropertyInfopropertyInfo=methodInfo.GetPropertyFromMethod(methodInfo.DeclaringType);return(propertyInfo!=null&&propertyInfo.GetOneAttribute<DoNotNotifyOfChangesAttribute>()!=null);}}

That wasn’t too bad, right? We looked at the plan and registered interceptors when applicable that will be attached during the instance activation. Now we need to create the attributes for the planning strategy to process.

We need to create a simple attribute for [DoNotNotifyOfChanges] – we could have just used [DoNotIntercept], but I still want to allow other interception schemes to be able to proxy these properties. Even though [DoNotIntercept] is its base class, we do not look at inheritance of attributes when processing the activation plans.

The NotifyOfChangesAttribute is a little more complex, but still pretty easy. We want to inherit from InterceptAttribute, but we need to change the targets to classes and properties only. The base attribute was able to attach to methods which doesn’t make sense for us. We use the proxy request to construct an interceptor based on the target and notification settings. The tricky thing here is that the default interceptor we are using is an open generic, so we have to close the type before creating the interceptor instance.

In order to attach the AutoNotifyInterceptorRegistrationStrategy to the planning pipeline, we simply add it to the kernel components associated with IPlanningStrategy during the InterceptionModule’s Bind() method.

That’s it! We now have natively integrated, automatic INotifyPropertyChanged proxy generation. We can do more work to actually compare the existing and new values for changes, but we can enhance the interface later or move to support more flexible interceptor generation for IANPC.