多个类共享1种转换器

Multiple classes sharing 1 type converter

我有多个需要类型转换器的 DTO class。以下是其中一种实现方式。如您所见,我只需要 ConvertFrom。

public class EmployeeFilterTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (typeof(string) == sourceType)
            return true;
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        var strVal = value as String;
        if (string.IsNullOrEmpty(strVal))
            return new EmployeeFilter();
        EmployeeFilter employeeFilter = new EmployeeFilter();
        string[] filters = strVal.Split(';');

        foreach (var filter in filters)
        {
            var filterSplit = filter.Split(':');
            if (filterSplit.Length == 2)
            {
                var key = filterSplit[0];
                var val = filterSplit[1];
                SetPropertyValue(employeeFilter, key, val);
            }
        }
        return employeeFilter;
    }

    private void SetPropertyValue(EmployeeFilter employeeFilter, string key, string val)
    {
        var t = typeof(EmployeeFilter);
        PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
        if (prop != null)
            prop.SetValue(employeeFilter, val);
    }
}

我想让多个 DTO 共享同一个转换器,希望减少代码重复和测试,经过一些研究,我手头有 2 个问题

  1. 在ConvertFrom方法中获取我要转换的类型
  2. 使用类型 class 初始化新对象

对于第一个,我不知道如何从 ITypeDescriptorContext 中获取。

第二个,我就照着这个用下面的post

Type employeeType = typeof(EmployeeFilter);
object objtype = Activator.CreateInstance(employeeType);

那么,如何获取我要转换成的类型?

测试用例

public class testConverter
{
    [Theory]
    [InlineData(typeof(string), true)]
    [InlineData(typeof(int), false)]
    public void testCanConvertFrom(Type sourceType, bool expected)
    {
        //Arrange
        Type randomType = typeof(Book);
        Type typeGenericConverter = typeof(EmployeeFilterTypeConverter<>);
        Type typeActualConverter = typeGenericConverter.MakeGenericType(randomType);
        /*
            1. The way of creating EmployeeFilterTypeConverter<thattype>
                
         */
        dynamic testConverter = Activator.CreateInstance(typeActualConverter);
        Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
        //Act
        bool actual = testConverter.CanConvertFrom(mockDescContext.Object, sourceType);
        //Assert
        Assert.Equal(expected, actual);
    }
    [Theory, ClassData(typeof(TestConvertFromType1))]
    /*
        1. All these classdata, propertydata stuff just for passing complex objects to test
            
     */
    public void testConverFromType1(object value, EmployeeFilter expected)
    {
        //api/employee?filter=firstName:Nikhil;lastName:Doomra
        //Arrange
        EmployeeFilterTypeConverter<EmployeeFilter> testConverter = new EmployeeFilterTypeConverter<EmployeeFilter>();
        Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
        //Act
        EmployeeFilter actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as EmployeeFilter;
        //Assert
        //public static void Equal<T>(T expected, T actual);
        Assert.Equal(expected, actual);
    }
    [Theory, ClassData(typeof(TestConvertFromType2))]
    public void testConverFromType2(object value, GeoPoint expected)
    {
        //api/employee?filter=firstName:Nikhil;lastName:Doomra
        //Arrange
        EmployeeFilterTypeConverter<GeoPoint> testConverter = new EmployeeFilterTypeConverter<GeoPoint>();
        Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
        //Act
        GeoPoint actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as GeoPoint;
        //Assert
        //public static void Equal<T>(T expected, T actual);
        Assert.Equal(expected, actual);
    }
}

测试数据模型

public class TestConvertFromType1: IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "firstName:Nikhil;lastName:Doomra",
            new EmployeeFilter {
            FirstName = "Nikhil", LastName = "Doomra"
            }},
        new object[] { "firstName:Nikhil",
            new EmployeeFilter {
            FirstName = "Nikhil"
            }}
    };
    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}
public class TestConvertFromType2 : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "Latitude:12.345;Longitude:342.12",
            new GeoPoint {
            Latitude = 12.345, Longitude = 342.12
            }},
        new object[] { "Latitude:11.234;Longitude:345.12",
            new GeoPoint {
            Latitude = 11.234, Longitude = 345.12
            }}
    };
    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}

通用转换器

public class EmployeeFilterTypeConverter<T> : TypeConverter where T: new()
    /*
        1. You can't declare T type = new T() without this constraint
            Evidently it is because compiler can't say what is the type!
            
     */
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (typeof(string) == sourceType)
            return true;
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        var strVal = value as String;
        if (string.IsNullOrEmpty(strVal))
            return new EmployeeFilter();

        T converTo = new T();

        string[] filters = strVal.Split(';');

        foreach (var filter in filters)
        {
            string[] filterSplit = filter.Split(':');
            if (filterSplit.Length == 2)
            {
                string key = filterSplit[0];
                string val = filterSplit[1];
                SetPropertyValue(converTo, key, val);
            }
        }
        return converTo;
    }

    private void SetPropertyValue(T converTo, string key, string val)
    {
        Type t = typeof(T);
        PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
        if (prop is null) return;
        prop.SetValue(converTo, TypeDescriptor.GetConverter(prop.PropertyType).ConvertFrom(val));
        /*
            1. Problem: val is a string and if your target property is non-string there is
                a contradiction.
                The following link offers a solution
                
         */
    }
}

员工过滤器

[TypeConverter(typeof(EmployeeFilterTypeConverter<EmployeeFilter>))]
public class EmployeeFilter: IEquatable<EmployeeFilter>
{
    /*
        1. As you can see, the DTO omitted the
            a. ID
            b. DOB
     */
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public DateTime? DOJ { get; set; }

    public bool Equals(EmployeeFilter other)
    {
        /*
            1. You need to put parenthesis around (this.FirstName == other.FirstName)
            2. https://docs.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-5.0
         */
        return (this.FirstName == other.FirstName) &&
            (this.LastName == other.LastName) && 
            (this.Street == other.Street) &&
            (this.City == other.City) &&
            (this.State == other.State) &&
            (this.ZipCode == other.ZipCode) &&
            (this.DOJ == other.DOJ);
    }
}

地理点

public class GeoPoint: IEquatable<GeoPoint>
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public static bool TryParse(string s, out GeoPoint result)
    {
        result = null;

        var parts = s.Split(',');
        if (parts.Length != 2)
        {
            return false;
        }

        double latitude, longitude;
        if (double.TryParse(parts[0], out latitude) &&
            double.TryParse(parts[1], out longitude))
        {
            result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
            return true;
        }
        return false;
    }

    public bool Equals(GeoPoint other)
    {
        return (this.Latitude == other.Latitude) && (this.Longitude == other.Longitude);
    }

编辑:添加模型 类