Я написал генератор моделей для создания интерфейса между клиентскими ViewModels и серверными моделями.
Некоторые классы моделей сериализуются с атрибутом [JsonObject(MemberSerialization.OptIn)]
, а их свойства помечаются [JsonProperty ...]
для сериализации, вКроме того, у меня есть некоторые пользовательские атрибуты, которые необходимо обработать в моем пользовательском DefaultContractResolver
.
. Вот как я создаю свой ModelView из любого типа источника
public Type CreateModelView(Type sourceType) {
...
foreach (var attribute in sourceType.CustomAttributes)
typeBuilder.SetCustomAttribute(
new CustomAttributeBuilder(
attribute.Constructor,
attribute.ConstructorArguments.Select(x => x.Value).ToArray()));
...
var fields = GetFields(sourceType);
foreach (var field in fields)
CreateProperty(typeBuilder, field.Name, field.PropertyType, field.GetCustomAttributes(true));
return typeBuilder.CreateTypeInfo().AsType();
}
private void CreateProperty(TypeBuilder typeBuilder, string name, Type type, object[] customAttributes)
{
var fieldBuilder = typeBuilder.DefineField(name, type, FieldAttributes.Public);
var propertyBuilder = typeBuilder.DefineProperty(name, PropertyAttributes.HasDefault, type, null);
var GetMethodBuilder = typeBuilder.DefineMethod(
"Get" + name,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig, type,
Type.EmptyTypes);
var iLGenerator = GetMethodBuilder.GetILGenerator();
iLGenerator.Emit(OpCodes.Ldarg_0);
iLGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
iLGenerator.Emit(OpCodes.Ret);
var SetMethodBuilder =
typeBuilder.DefineMethod("Set" + name,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null,
new[] { type });
ILGenerator setIl = SetMethodBuilder.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
foreach (var customAttribute in customAttributes)
AddCustomAttributeToProperty(customAttribute, propertyBuilder);
propertyBuilder.SetGetMethod(GetMethodBuilder);
propertyBuilder.SetSetMethod(SetMethodBuilder);
}
/// <summary>
/// Given a custom attribute and property builder, adds an instance of custom attribute
/// to the property builder
/// </summary>
void AddCustomAttributeToProperty(object customAttribute, PropertyBuilder propBuilder)
{
var customAttributeBuilder = BuildCustomAttribute(customAttribute);
if (customAttributeBuilder != null)
{
propBuilder.SetCustomAttribute(customAttributeBuilder);
}
}
static CustomAttributeBuilder BuildCustomAttribute(object customAttribute)
{
ConstructorInfo longestCtor = null;
// Get constructor with the largest number of parameters
foreach (var cInfo in customAttribute.GetType().GetConstructors().
Where(cInfo => longestCtor == null || longestCtor.GetParameters().Length < cInfo.GetParameters().Length))
longestCtor = cInfo;
if (longestCtor == null)
{
return null;
}
// For each constructor parameter, get corresponding (by name similarity) property and get its value
var args = new object[longestCtor.GetParameters().Length];
var position = 0;
foreach (var consParamInfo in longestCtor.GetParameters())
{
var attrPropInfo = customAttribute.GetType().GetProperty(consParamInfo.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (attrPropInfo != null)
{
args[position] = attrPropInfo.GetValue(customAttribute, null);
}
else
{
args[position] = null;
var attrFieldInfo = customAttribute.GetType().GetField(consParamInfo.Name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (attrFieldInfo == null)
{
if (consParamInfo.ParameterType.IsValueType)
{
args[position] = Activator.CreateInstance(consParamInfo.ParameterType);
}
}
else
{
args[position] = attrFieldInfo.GetValue(customAttribute);
}
}
++position;
}
var propList = new List<PropertyInfo>();
var propValueList = new List<object>();
foreach (var attrPropInfo in customAttribute.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!attrPropInfo.CanWrite)
{
continue;
}
object defaultValue = null;
var defaultAttributes = attrPropInfo.GetCustomAttributes(typeof(DefaultValueAttribute), true);
if (defaultAttributes.Length > 0)
{
defaultValue = ((DefaultValueAttribute)defaultAttributes[0]).Value;
}
var value = attrPropInfo.GetValue(customAttribute, null);
if (value == defaultValue)
{
continue;
}
propList.Add(attrPropInfo);
propValueList.Add(value);
}
return new CustomAttributeBuilder(longestCtor, args, propList.ToArray(), propValueList.ToArray());
}
Я также протестировал этот кодс field.CustomAttributes
и результат был таким же.
propertyBuilder.SetCustomAttribute(
new CustomAttributeBuilder(
attribute.Constructor,
attribute.ConstructorArguments.Select(x => x.Value).ToArray()));
и проблема здесь в ContractResolver
, когда результат attributes
пуст,
public class ContractResolver : DefaultContractResolver {
...
protected override IList<JsonProperty> CreateProperties(
Type type,
MemberSerialization memberSerialization)
{
var propertyList = base.CreateProperties(type, memberSerialization);
foreach (var jProperty in propertyList)
{
var attributes = jProperty.AttributeProvider.GetAttributes(true);
var isBindNever = attributes.OfType<BindNeverAttribute>().Any();
var isKey = attributes.OfType<KeyAttribute>().Any();
...
}
и [JsonProperty ...]
атрибуты также не работают.
PS Я могу получить пользовательские атрибуты из этого кода
var PropertyType = jProperty.DeclaringType.GetProperties()
.Where(property => property.Name.ToLower() == jProperty.PropertyName.ToLower())
.FirstOrDefault();
Проблема в том, почему jProperty.AttributeProvider.GetAttributes
не работает должным образом, почему [JsonProperty]
не работаеткак и ожидалось (помеченные свойства не будут сериализованы).