ایجاد «خواص الحاقی» با استفاده از امکانات TypeDescriptor و یک TypeDescriptionProvider سفارشی
نویسنده: سالار ربال
تاریخ: ۱۳۹۸/۱۲/۱۸ ۲۱:۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
برای ایجاد «خواص الحاقی» قبلا در سایت مطلب ایجاد «خواص الحاقی» تهیه شدهاست. در این مطلب قصد داریم راه حل ارائه شدهی در مطلب مذکور را با یک TypeDescriptionProvider سفارشی ترکیب کرده تا به صورت یکدست، از طریق TypeDescriptor بتوان به آن خواص نیز دسترسی داشته باشیم.
فرض کنید در یک سیستم Modular Monolith، نیاز جدیدی به دست شما رسیده است که به شرح زیر میباشد:
نیاز داریم در گریدی از صفحهی X مربوط به «مؤلفه 1»، ستونی جدید را اضافه کنید و دیتای مربوط به این ستون، توسط «مؤلفه 2» مهیا خواهد شد.
در این حالت «مؤلفه 1» بدون آگاهی از سایر مؤلفهها، همهی پیاده سازیهای IExtraColumnConenvtion را در زمان اجرا یافته و از آنها برای ایجاد ستونهای جدید، استفاده خواهد کرد.
واسط مذکور به شکل زیر میباشد:
public interface IConvention
{
}
public interface IExtraColumnConvention<T> : IConvention
{
string Name { get; }
string Title { get; }
void Populate(IEnumerable<T> list);
} البته این واسط میتواند جزئیات بیشتری را هم شامل شود.
در .NET به دو طریق میتوان به متادیتای یک Type دسترسی داشت:
به طور کلی هدف از این کلاس در دات نت، ارائه اطلاعاتی در خصوص یک وهله از جمله: Attributeها، Propertyها، Eventهای آن و غیره، میباشد. هنگام استفاده از Reflection، اطلاعات بدست آمده از Type، به دلیل اینکه بعد از کامپایل نمیتوانند تغییر کنند، لذا قابلیت توسعه پذیری را هم ندارند. در مقابل، با استفاده از کلاس TypeDescriptor این توسعه پذیری را برای وهلههای مختلف میتوانید داشته باشید.
برای مهیا کردن متادیتای سفارشی (در اینجا اطلاعات مرتبط با خصوصیات الحاقی) برای TypeDescriptor، نیاز است یک TypeDescriptionProvider سفارشی را طراحی کنیم.
/// <summary>
/// Use this provider when you need access ExtraProperties with TypeDescriptor.GetProperties(instance)
/// </summary>
public class ExtraPropertyTypeDescriptionProvider<T> : TypeDescriptionProvider where T : class
{
private static readonly TypeDescriptionProvider Default =
TypeDescriptor.GetProvider(typeof(T));
public ExtraPropertyTypeDescriptionProvider() : base(Default)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type instanceType, object instance)
{
var descriptor = base.GetTypeDescriptor(instanceType, instance);
return instance == null ? descriptor : new ExtraPropertyCustomTypeDescriptor(descriptor, instance);
}
private sealed class ExtraPropertyCustomTypeDescriptor : CustomTypeDescriptor
{
//...
}
} private sealed class ExtraPropertyCustomTypeDescriptor : CustomTypeDescriptor
{
private readonly IEnumerable<ExtraPropertyDescriptor<T>> _instanceExtraProperties;
public ExtraPropertyCustomTypeDescriptor(ICustomTypeDescriptor defaultDescriptor, object instance)
: base(defaultDescriptor)
{
_instanceExtraProperties = instance.ExtraPropertyList<T>();
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor property in base.GetProperties(attributes))
{
properties.Add(property);
}
foreach (var property in _instanceExtraProperties)
{
properties.Add(property);
}
return properties;
}
public override PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
} public static class ExtraProperties
{
//...
public static IEnumerable<ExtraPropertyDescriptor<T>> ExtraPropertyList<T>(this object instance) where T : class
{
if (!PropertyCache.TryGetValue(instance, out var properties))
throw new KeyNotFoundException($"key: {instance.GetType().Name} was not found in dictionary");
return properties.Select(p =>
new ExtraPropertyDescriptor<T>(p.PropertyName, p.PropertyValueFunc, p.SetPropertyValueFunc,
p.PropertyType,
p.Attributes));
}
} public sealed class ExtraPropertyDescriptor<T> : PropertyDescriptor where T : class
{
private readonly Func<object, object> _propertyValueFunc;
private readonly Action<object, object> _setPropertyValueFunc;
private readonly Type _propertyType;
public ExtraPropertyDescriptor(
string propertyName,
Func<object, object> propertyValueFunc,
Action<object, object> setPropertyValueFunc,
Type propertyType,
Attribute[] attributes) : base(propertyName, attributes)
{
_propertyValueFunc = propertyValueFunc;
_setPropertyValueFunc = setPropertyValueFunc;
_propertyType = propertyType;
}
public override void ResetValue(object component)
{
}
public override bool CanResetValue(object component) => true;
public override object GetValue(object component) => _propertyValueFunc(component);
public override void SetValue(object component, object value) => _setPropertyValueFunc(component, value);
public override bool ShouldSerializeValue(object component) => true;
public override Type ComponentType => typeof(T);
public override bool IsReadOnly => _setPropertyValueFunc == null;
public override Type PropertyType => _propertyType;
} [TypeDescriptionProvider(typeof(ExtraPropertyTypeDescriptionProvider<Person>))]
private class Person
{
public string Name { get; set; }
public string Family { get; set; }
} [Test]
public void Should_TypeDescriptor_GetProperties_Returns_ExtraProperties_And_PredefinedProperties()
{
//Arrange
var rabbal = new Person {Name = "GholamReza", Family = "Rabbal"};
const string propertyName = "Title";
const string propertyValue = "Software Engineer";
//Act
rabbal.ExtraProperty(propertyName, propertyValue);
var title = TypeDescriptor.GetProperties(rabbal).Find(propertyName, true);
//Assert
rabbal.ExtraProperty<string>(propertyName).ShouldBe(propertyValue);
title.ShouldNotBeNull();
title.GetValue(rabbal).ShouldBe(propertyValue);
} public class Column4Convention : IExtraColumnConvention<Product>
{
public string Name => "Column4";
public string Title => "Column 4"
public void Populate(IEnumerable<Product> list)
{
//TODO: forEach on list and set ExtraProperty
// item.ExtraProperty(Name,value)
// item.ExtraProperty(Name,(obj)=> value)
// item.ExtraProperty(Name,(obj)=> value, (obj,value)=>)
}
}
public class Column2Convention : IExtraColumnConvention<Product>
{
public string Name => "Column2";
public string Title => "Column 2"
public void Populate(IEnumerable<Product> list)
{
//TODO: forEach on list and set ExtraProperty
}
}
public class Column3Convention : IExtraColumnConvention<Product>
{
public string Name => "Column3";
public string Title => "Column 3"
public void Populate(IEnumerable<Product> list)
{
//TODO: forEach on list and set ExtraProperty
}
} var products = _productService.PagedList(page:1, pageSize:10);
var columns = _provider.GetServices<IExtraColumnConvention<Product>>();
foreach(var column in columns)
{
column.Populate(products);
}