Newtonsoft.Json AttributeProvider does not provide runtime added custom attributes

c# entity-framework-core json json.net

Question

I did write a model generator to create an interface between client ViewModels and server Models.

Some model classes are serialize with [JsonObject(MemberSerialization.OptIn)] attribute and their properties are marked with [JsonProperty ...] to be serialized, in addition i have some custom attributes which need to be handled in my custom DefaultContractResolver.

Here is how I create my ModelView from any source type

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());
}

i tested this code as well with field.CustomAttributes and result was same.

propertyBuilder.SetCustomAttribute(
            new CustomAttributeBuilder(
                attribute.Constructor,
                attribute.ConstructorArguments.Select(x => x.Value).ToArray()));

and the problem is here in ContractResolver when the result of attributes is empty,

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();
...
}

and [JsonProperty ...] attributes are not working as well.

P.S. I can get custom attributes from this code

var PropertyType = jProperty.DeclaringType.GetProperties()
                    .Where(property => property.Name.ToLower() == jProperty.PropertyName.ToLower())
                    .FirstOrDefault();

The problem is why is jProperty.AttributeProvider.GetAttributes not working as expected, Why [JsonProperty] not working as expected (marked properties won't serialized).

1
1
1/1/2019 12:21:33 AM

Accepted Answer

This will solve problem, however it's not efficient

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();
    }

    ...
}
0
12/31/2018 9:25:57 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow