Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
684 views
in Technique[技术] by (71.8m points)

c# - How to use ASP.Net core ModelMetadata attribute

[Table("LegalEntity")]
[ModelMetadataType(typeof(LegalEntityMeta))]
public class LegalEntity : Entity<long>
{
}

public class LegalEntityMeta
{
    [JsonProperty(PropertyName = "LegalEntityId")]
    public long Id { get; set; }

    [JsonProperty(PropertyName = "LegalEntityName")]
    public string Name { get; set; }
}

In the Startup.cs ....

        services
            .AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            })
            .AddAutoMapper(typeof(Startup))
            .AddMvcCore()
            .AddJsonFormatters()
            .AddApiExplorer();

My expectation is to see json with attributes legalEntityId and legalEntityName yet the json produced has id and name as attributes. Can someone pleas help me with how to change the json attributes? Thanks Anand

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Json.NET currently has no support for Microsoft.AspNetCore.Mvc.ModelMetadataTypeAttribute. In Issue #1349: Add support for ModelMetadataType for dotnetcore like supported MetadataTypeAttribute in previous versions a request to implement support for it was declined.

Json.NET does support System.ComponentModel.DataAnnotations.MetadataTypeAttribute, albeit with some limitations described in this answer, however even if this attribute were present in .Net core (not sure it is) it would not help you, because you are trying to use the metadata type of a derived class to rename the properties in a base type, which is not an intended usage for metadata type information. I.e. the following works out of the box (in full .Net):

[System.ComponentModel.DataAnnotations.MetadataType(typeof(EntityMeta))]
public class Entity<T>
{
    public T Id { get; set; }

    public string Name { get; set; }
}

public class EntityMeta
{
    [JsonProperty(PropertyName = "LegalEntityId")]
    public long Id { get; set; }

    [JsonProperty(PropertyName = "LegalEntityName")]
    public string Name { get; set; }
}

But the following does not:

[System.ComponentModel.DataAnnotations.MetadataType(typeof(LegalEntityMeta))]
public class LegalEntity : Entity<long>
{
}

public class LegalEntityMeta
{
    [JsonProperty(PropertyName = "LegalEntityId")]
    public long Id { get; set; }

    [JsonProperty(PropertyName = "LegalEntityName")]
    public string Name { get; set; }
}

Why doesn't Json.NET allow derived type metadata information to modify base type contracts? You would have to ask Newtonsoft, but guesses include:

  1. Json.NET is a contract-based serializer where each type specifies its contract through attributes. It's not intended that one type could rewrite the contract of a second type.

  2. DataContractJsonSerializer and DataContractSerializer work the same way.

  3. Doing so would violate the Liskov substitution principle.

So, what are your options?

  1. You could serialize a DTO in place of your LegalEntity, and use something like to map between then:

    public class LegalEntityDTO
    {
        [JsonProperty(PropertyName = "LegalEntityId")]
        public long Id { get; set; }
    
        [JsonProperty(PropertyName = "LegalEntityName")]
        public string Name { get; set; }
    }
    
  2. You could create a custom JsonConverter for LegalEntity with the necessary logic.

  3. You could create a custom contract resolver with the necessary logic, similar to the one here, for instance the following:

    using System.Reflection;
    
    public class ModelMetadataTypeAttributeContractResolver : DefaultContractResolver
    {
        public ModelMetadataTypeAttributeContractResolver()
        {
            // Default from https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs
            this.NamingStrategy = new CamelCaseNamingStrategy();
        }
    
        const string ModelMetadataTypeAttributeName = "Microsoft.AspNetCore.Mvc.ModelMetadataTypeAttribute";
        const string ModelMetadataTypeAttributeProperty = "MetadataType";
    
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var properties = base.CreateProperties(type, memberSerialization);
    
            var propertyOverrides = GetModelMetadataTypes(type)
                .SelectMany(t => t.GetProperties())
                .ToLookup(p => p.Name, p => p);
    
            foreach (var property in properties)
            {
                var metaProperty = propertyOverrides[property.UnderlyingName].FirstOrDefault();
                if (metaProperty != null)
                {
                    var jsonPropertyAttribute = metaProperty.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
                    if (jsonPropertyAttribute != null)
                    {
                        property.PropertyName = jsonPropertyAttribute.PropertyName;
                        // Copy other attributes over if desired.
                    }
                }
            }
    
            return properties;
        }
    
        static Type GetModelMetadataType(Attribute attribute)
        {
            var type = attribute.GetType();
            if (type.FullName == ModelMetadataTypeAttributeName)
            {
                var property = type.GetProperty(ModelMetadataTypeAttributeProperty);
                if (property != null && property.CanRead)
                {
                    return property.GetValue(attribute, null) as Type;
                }
            }
            return null;
        }
    
        static Type[] GetModelMetadataTypes(Type type)
        {
            var query = from t in type.BaseTypesAndSelf()
                        from a in t.GetCustomAttributes(false).Cast<System.Attribute>()
                        let metaType = GetModelMetadataType(a)
                        where metaType != null
                        select metaType;
            return query.ToArray();
        }
    }
    
    public static partial class TypeExtensions
    {
        public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
        {
            while (type != null)
            {
                yield return type;
                type = type.BaseType;
            }
        }
    }
    

    Sample .Net fiddle.

    To serialize directly, do:

    var settings = new JsonSerializerSettings
    {
        ContractResolver = new ModelMetadataTypeAttributeContractResolver(),
    };
    
    var json = JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
    

    To install the contract resolver into Asp.Net Core see here.

    Note I wrote this using full .Net 4.5.1 so it is just a prototype. .Net Core uses a different reflection API, however if you install System.Reflection.TypeExtensions as described here I believe it should work.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...