Spring AOP + MVVM Foundation + PropertyChanged - PullRequest
1 голос
/ 29 апреля 2011

Я использую Spring.Net 1.3.1 вместе с MVVM Foundation, чтобы применить сквозную обработку к моим моделям представления.Я заметил, что если я назначаю обработчик измененного свойства до того, как объект будет преобразован в прокси для сквозной обработки, то прокси-движок не применяет обработчик измененного свойства к прокси.Кто-нибудь знает, является ли это ожидаемым поведением, и если да, то есть ли обходной путь?

Моя фабрика выглядит так

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();
    }
}

Советы выглядят так

    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));
    }
}

Объект, который я проксирую, выглядит следующим образом

    public class ProxyTypeObject : ObservableObject {
    private string whoCaresItsBroke;
    public string WhoCaresItsBroke {
        get { return whoCaresItsBroke; }
        set {
            whoCaresItsBroke = value;
            RaisePropertyChanged("WhoCaresItsBroke");
        }
    }
}

И вызывающий код

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";

Вы заметите, что когда я устанавливаю свойство "WhoCaresItsBroke", свойство изменяет обработчик Iранее подключенный никогда не попал.(Я пытался использовать NotifyPropertyChangedAdvice, как указано на форумах spring.net, но это не работает.)

Ответы [ 2 ]

0 голосов
/ 18 мая 2011

Вы должны объявить свойство WhoCaresItsBroke как виртуальное, иначе оно не будет переопределено вашим прокси-объектом. Если сделать его виртуальным, ваш обработчик на pto будет вызван снова, потому что прокси-сервер делегирует вызов свойства своей цели.

Вам не нужно NotifyPropertyChangedAdvice, вы можете удалить его. Требуемое поведение уже реализовано используемым классом ObservableObject.

Если вы хотите, чтобы событие PropertyChanged было запущено на прокси-сервере при возникновении целевого события PropertyChanged, вы должны реализовать это вручную, как предложено в следующем разделе.

Взлом или обходной путь к огню PropertyChanged на прокси и цель

Прокси-фабрика не связывает целевые события с аналогичными событиями на прокси, но вы можете сделать это вручную. Я не уверен, что я бы посоветовал вам сделать это, но вы могли бы использовать следующий хак.

Переписать свой прокси-завод и 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;
    }
}

Для этого требуется ProxyTypeObject, чтобы иметь публично видимый метод для поднятия PropertyChangedEvent; Вы, вероятно, должны сделать это по-другому, но это не главное.

Как это работает

Фабрика возвращает прокси типа ProxyTypeObject, потому что вы установили factory.ProxyTargetType = true;. Тем не менее, это все еще основанный на композиции прокси: после прокси у вас будет исходный объект (цель) и новый прокси-объект. И прокси, и цель имеют тип ProxyTypeObject и могут вызывать событие PropertyChanged.

На этом этапе при установке WhoCaresItsBroke на прокси событие PropertyChanged будет запускаться на вашем прокси, но не на цели. Целевое свойство не изменено.

шаг 1: объявить свойство виртуальным

Поскольку мы сделали свойство WhoCaresItsBroke виртуальным, оно может быть переопределено в прокси. В переопределенном свойстве прокси-объект передает все WhoCaresItsBroke вызовы свойству WhoCaresItsBroke целевому объекту.

После этого шага вы увидите, что вызывается оригинальный обработчик, подключенный к вашему экземпляру pto. Однако событие PropertyChanged на прокси не вызывается.

шаг 2: подключить целевое событие к обработчику на прокси

Просто подключите целевое событие PropertyChanged к обработчику на прокси, который вызывает свое собственное событие PropertyChanged. Мы можем использовать то же имя, потому что в прокси мы можем предположить, что мы одного типа.

0 голосов
/ 30 апреля 2011

Кажется, что Spring примеры Spring.AopQuickStart \ src \ Spring.AopQuickStart.Step6 делает почти то же самое, что вы пытаетесь сделать (перехватывая [autogenerated] setter свойства).Возможно, вы захотите взглянуть на источник примера .

...