I'm using Spring.Net 1.3.1 alongside MVVM Foundation to apply cross-cutting to my viewmodels. I've noticed that if I assign a property changed handler before the object is converted to a proxy for cross-cutting that the proxy engine does not apply the property changed handler to the proxy. Does anyone know if this is expected behavior and if so, if there is a workaround?
My factory looks like this
public static class AopProxyFactory {
public static object GetProxy(object target) {
var factory = new ProxyFactory(target);
factory.AddAdvisor(new Spring.Aop.Support.DefaultPointcutAdvisor(
new AttributeMatchMethodPointcut(typeof(AttributeStoringMethod)),
new UnitValidationBeforeAdvice())
);
factory.AddAdvice(new NotifyPropertyChangedAdvice());
factory.ProxyTargetType = true;
return factory.GetProxy();
}
}
The advices look like this
public class UnitValidationBeforeAdvice : IMethodBeforeAdvice {
public UnitValidationBeforeAdvice() {
}
public void Before(MethodInfo method, object[] args, object target) {
if (args.Length != 1) {
throw new ArgumentException("The args collection is not valid!");
}
var canConvertTo = true;
if (!canConvertTo) {
throw new ArgumentException("The '{0}' cannot be converted.");
}
}
}
public class NotifyPropertyChangedAdvice : IAfterReturningAdvice, INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public void AfterReturning(object ReturnValue, MethodInfo Method, object[] Args, object Target) {
if (Method.Name.StartsWith("set_")) {
RaisePropertyChanged(Target, Method.Name.Substring("set_".Length));
}
}
private void RaisePropertyChanged(Object Target, String PropertyName) {
if (PropertyChanged != null)
PropertyChanged(Target, new PropertyChangedEventArgs(PropertyName));
}
}
The object I'm proxying look like this
public class ProxyTypeObject : ObservableObject {
private string whoCaresItsBroke;
public string WhoCaresItsBroke {
get { return whoCaresItsBroke; }
set {
whoCaresItsBroke = value;
RaisePropertyChanged("WhoCaresItsBroke");
}
}
}
And the calling code
var pto = new ProxyTypeObject();
pto.WhoCaresItsBroke = "BooHoo";
pto.PropertyChanged += (object sender, System.ComponentModel.PropertyChangedEventArgs e) => {
return;
};
var proxy = AopProxyFactory.GetProxy(pto);
(proxy as ProxyTypeObject).WhoCaresItsBroke = "BooHoo2";
You will notice that when I set the "WhoCaresItsBroke" property the property changed handler I previously hooked up is never hit. (I tried using the NotifyPropertyChangedAdvice as provided in the spring.net forums but that does not appear to work.)
It seems that the Spring examples Spring.AopQuickStart\src\Spring.AopQuickStart.Step6 does almost the samething you are trying to do (intercepting the [autogenerated] setter of a Property).
You might want to have a look at the source of the example.
You should declare the WhoCaresItsBroke
property as virtual, otherwise it will not be overridden by your proxy object. Making it virtual will cause your handler on pto
to be called again, because the proxy will delegate the property call to its target.
You do not need the NotifyPropertyChangedAdvice
, you can remove it. The desired behavior is already implemented by the ObservableObject
class you're using.
If you want the PropertyChanged
event to be fired on the proxy when the target PropertyChanged
event is fired, you should implement this manually, as suggested in the following hack.
The hack or workaround to fire PropertyChanged
on proxy and the target
A proxyfactory does not wire target events to similar event on a proxy, but you could do this manually. I'm not sure if I would advice you to do so, but you could use the following hack.
Rewrite your proxy factory and ProxyTypeObject
:
public class ProxyTypeObject : ObservableObject
{
private string whoCaresItsBroke;
// step 1:
// make the property virtual, otherwise it will not be overridden by the proxy
public virtual string WhoCaresItsBroke
{
// original implementation
}
public void PublicRaisePropertyChanged(string name)
{
RaisePropertyChanged(name);
}
}
public static class AopProxyFactory
{
public static object GetProxy(object target)
{
ProxyFactory factory = GetFactory(target);
object proxy = factory.GetProxy();
if(target is ProxyTypeObject)
{
// step 2:
// hack: hook handlers ...
var targetAsProxyTypeObject = (ProxyTypeObject)target;
var proxyAsProxyTypeObject = (ProxyTypeObject)proxy;
HookHandlers(targetAsProxyTypeObject, proxyAsProxyTypeObject);
}
return proxy;
}
private static void HookHandlers(ProxyTypeObject target, ProxyTypeObject proxy)
{
target.PropertyChanged += (sender, e) =>
{
proxy.PublicRaisePropertyChanged(e.PropertyName);
};
}
private static ProxyFactory GetFactory(object target)
{
var factory = new ProxyFactory(target);
// I simply add the advice here, but you could useyour original
// factory.AddAdvisor( ... )
factory.AddAdvice(new UnitValidationBeforeAdvice());
// You don't need this:
// factory.AddAdvice(new NotifyPropertyChangedAdvice());
factory.ProxyTargetType = true;
return factory;
}
}
This requires ProxyTypeObject
to have a publicly visible method to raise a PropertyChangedEvent
; you probably should do this differently, but that's besides the point.
How it works
The factory returns a proxy of type ProxyTypeObject
, because you have set factory.ProxyTargetType = true;
. It is still a composition based proxy though: after proxying you will have the original object (the target) and the new proxy object. Both proxy and target are of type ProxyTypeObject
and can raise a PropertyChanged
event.
At this stage, when setting WhoCaresItsBroke
on the proxy, the PropertyChanged
event will fire on your proxy, but not on the target. The target property is not changed.
step 1: declare property as virtual
Because we've made the property WhoCaresItsBroke
virtual, it can be overridden in the proxy. In the overridden property, the proxy object delegates all WhoCaresItsBroke
calls to the WhoCaresItsBroke
property to the target.
After this step, you'll see that the original handler you attached to your pto
instance is called. However, the PropertyChanged
event on the proxy is not raised.
step 2: hook target event to a handler on proxy
Simply hook the target PropertyChanged
event to a handler on the proxy that raises its own PropertyChanged
event. We can use the same name because in the proxy we can assume we're of the same type.