84074

Spring AOP + MVVM Foundation + PropertyChanged

Question:

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.)

Answer1:

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 <a href="https://fisheye.springsource.org/browse/spring-net/trunk/examples/Spring/Spring.AopQuickStart/src/Spring.AopQuickStart.Step6" rel="nofollow">the source of the example</a>.

Answer2:

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.

<em>The hack</em> or workaround to fire PropertyChanged on proxy <em>and</em> 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.

<em>How it works</em>

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) <em>and</em> 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.

Recommend

  • Cannot create an object of type 'System.Object' from its string representation
  • Grouping and counting in XSLT 1.0
  • ng-pattern that allows decimal ratios
  • Associate ID with Class in CSS
  • Client-side regex validation based on Regular Expression attribute fails
  • Jackson (JSON) throws JsonMappingException when Float is null
  • setImageData fails in iOS 8.3
  • unit test angular service not able to reach function inside service + jasmine
  • How to convert one data type to another
  • Linq: Merge the dictionaries
  • Parsing tokens with PLY
  • Warning: ldap_start_tls() [function.ldap-start-tls]: Unable to start TLS: Server is unavailable
  • FOSUserBundle force user to write a different password
  • jQuery - how to validate a date of birth using jQuery Validation plugin?
  • Converting RVML to SVG using XSLT in C#
  • How can I alert message in Servlet code and sendredirect to JSP page?
  • How to limit cursor movement in WPF based app?
  • Meteor oplog for Mongo 2.6
  • Zend Framework 2, Module Redirect
  • Why is it ambiguous to call overloaded ambig(long) and ambig(unsigned long) with an integer literal?
  • What is “PHP-standardized” version number strings?
  • VB.NET: How do I use coalesce with db column values and nullable types? Or is there a better solutio
  • API (curl)Command to Approve a promoted build Job in Jenkins
  • How do I listen to all Seam contextual events with parameterized names?
  • Updating and removing unique join relationships in CakePHP
  • c++ using primitive types as a base class
  • What is the reason that Policy.getPolicy() is considered as it will retain a static reference to the
  • Should I use composite primary keys in Grails?
  • Many to Many in Linq using Dapper
  • Accessing Rows In A LINQ Result Without A Foreach Loop?
  • Where these are stored?
  • Why must we declare a variable name when adding a method to a struct in Golang?
  • abstracting over a collection
  • How can I tell a form not to dispose a particular control when it closes?
  • Symfony 2. CSRF token is invalid
  • Unable to decode certificate at client new X509Certificate2()
  • Apache 2.4 and php-fpm does not trigger apache http basic auth for php pages
  • Release, debug version and Authorization Google?
  • Is there a mandatory requirement to switch app.yaml?
  • retrieve vertices with no linked edge in arangodb