首页 > 解决方案 > Newtonsoft.Json AttributeProvider 不提供运行时添加的自定义属性

问题描述

我确实编写了一个模型生成器来创建客户端 ViewModel 和服务器模型之间的接口。

一些模型类使用[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]不能按预期工作(标记的属性不会序列化)。

标签: c#jsonjson.netentity-framework-core

解决方案


这将解决问题,但效率不高

public class JsonResolver : DefaultContractResolver
{
    ...
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        var serializableMembers = base.GetSerializableMembers(objectType).Select(memberInfo => memberInfo.Name);
        return objectType.GetProperties().Where(memberInfo => serializableMembers.Contains(memberInfo.Name)).Cast<MemberInfo>().ToList();
    }

    ...
}

推荐阅读