使用 Unity IoC 容器的更智能的 DTO

Smarter DTO with Unity IoC container

我有一个 MVVM 模型,其中我的视图模型包含一个 observableCollection<RfidData>。 每当其中的数据更新时,我想在属性上调用 RaisePropertyChanged()

我在某个地方看到它可以用 Unity IoC 容器非常巧妙地完成,但我找不到了。

这是我的 class:

public class RfidData : INotifyPropertyChanged
{
    private bool _checkedIn = false;
    private Guid _id;
    private int _collectorId;
    private DateTime _checkInTime;
    private string _name = "Test Name";

    public bool CheckedIn
    {
        get { return _checkedIn; }
        set { _checkedIn = value; RaisePropertyChanged(); }
    }

    public Guid Id
    {
        get {return _id;}
        set { _id = value; RaisePropertyChanged(); }
    }

    public int CollectorId
    {
        set { _collectorId = value; RaisePropertyChanged(); }
        get { return _collectorId; }
    }

    public DateTime CheckInTime
    {
        set { _checkInTime = value; RaisePropertyChanged(); }
        get { return _checkInTime; }
    }

    public string Name
    {
        set { _name = value; RaisePropertyChanged(); }
        get { return _name; }
    }

    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

有什么建议吗?

我想你在找拦截器。这是一个如何使用它的例子 Unity Interceptor

适合我的解决方案:

使用 virtual 属性定义 class:

public class RfidData : BaseViewModel
{
    public RfidData(Guid id)
    {
        Id = id;
    }
    public virtual int CollectorId { get; set; }
    public virtual DateTime CheckInTime { get; set; }
    public virtual string Name { get; set; }
    public virtual bool CheckedIn { get; set; }
    public virtual DateTime TimeStamp { get; set; }
    public virtual Guid Id { get; }
}

BaseViewModel 将执行 RaidPropertyChange() 方法:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

几乎抄袭@bilpor's link. 由于调用了 RaisePropertyChanged 而不是旧的 PropertyChanged,因此调用的方法不同。 我确实相信使用像 [CallerMemberName] 这样的新方法会更好一些,但我还没有研究它。

using System;
using System.ComponentModel;
using System.Reflection;
using Microsoft.Practices.Unity.InterceptionExtension;

public class PropertyChangedCallHandler : ICallHandler
{
    public int Order { get; set; }
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        //Only if not same value as current, and it's a 'set' called
        var shouldRaise = ShouldRaiseEvent(input);
        var res = getNext()(input, getNext);

        if (res.Exception == null && shouldRaise)
            RaiseEvent(input);

        return res;
    }

    private bool ShouldRaiseEvent(IMethodInvocation input)
    {
        var methodBase = input.MethodBase;

        //Is the method a property setter?
        if (!methodBase.IsSpecialName || !methodBase.Name.StartsWith("set_"))
        {
            return false;
        }

        //Get the name of the property out so we can use it to raise a 
        //property changed event
        string propertyName = methodBase.Name.Substring(4);

        //Retrieve the property getter
        PropertyInfo property = methodBase.ReflectedType.GetProperty(propertyName);
        MethodInfo getMethod = property.GetGetMethod();

        //IF the property has no get method, we don't care
        if (getMethod == null)
        {
            return false;
        }

        //Get the current value out
        object oldValue = getMethod.Invoke(input.Target, null);

        //Get the updated value
        object value = input.Arguments[0];

        //Is the new value null?
        if (value != null)
        {
            //Is the new value different from the old value?
            if (value.Equals(oldValue) == false)
            {
                return true;
            }
        }
        else
        {
            //Is the new value (null) different from the 
            //old value (non-null)?
            if (value != oldValue)
            {
                return true;
            }
        }

        return false;
    }

    private void RaiseEvent(IMethodInvocation input)
    {
        FieldInfo field = null;

        //Get a reference to the PropertyChanged event out of the current 
        //type or one of the base types
        var type = input.MethodBase.ReflectedType;
        while (field == null && type != null)
        {
            //This differs from the original
            field = type.GetField("RaisePropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
            type = type.BaseType;
        }

        //If we found the PropertyChanged event
        if (field != null)
        {
            //Get the event handler if there is one
            var evt = field.GetValue(input.Target) as MulticastDelegate;
            if (evt != null)
            {
                //Get the property name out
                string propertyName = input.MethodBase.Name.Substring(4);
                //Invoke the property changed event handlers
                evt.DynamicInvoke(input.Target, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

最后,用Unity拦截器注册class RfidData class:

var notificationPolicy = _container.AddNewExtension<Interception>()
    .RegisterType<BaseViewModel, RfidData>()
    .Configure<Interception>()
    .SetDefaultInterceptorFor(userControlType, new VirtualMethodInterceptor())
    .AddPolicy("NotificationPolicy");

notificationPolicy.AddMatchingRule(new PropertyMatchingRule("*", PropertyMatchingOption.Set));
notificationPolicy.AddCallHandler<PropertyChangedCallHandler>();

但奇怪的是: 当您实例化它时,通常是通过 contain.Resolve<RfidData>(),但我通过 NewtonSoft.JsonConvert.DeserializeObject() 方法获取所有数据。 但它接受正确的类型。