c# - 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]
不能按预期工作(标记的属性不会序列化)。
解决方案
这将解决问题,但效率不高
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();
}
...
}
推荐阅读
- json - 使用 jq 并循环遍历 json 并获取多个值
- sql-server - 选择具有不同名称的不同 ID,并选择与所选 ID 相关的名称:/
- metal - 在 Xcode 中的 Metal 中获取鼠标和键盘输入
- bash - 为什么我的while循环不起作用我试图编写一个循环的bash脚本,只要我没有选择第四个选择
- java - 如何处理数字格式异常并将 int/double 变量替换为默认值 0/0.0?
- javascript - JavaScript/jQuery 点击函数在 AJAX 调用 API 时未触发
- bash - 作为 Azure DevOps 中发布管道的一部分,将更改推送到 GitHub 存储库
- macos - Visual Studio for Mac 2019 - 加载失败:链接属性值无效
- docker - Docker 中的 Kibana 和 Elasticsearch,与标准安装共存
- java - 对 API 进行多少次调用的最佳实践是什么?