locked
BaseJsonMediaTypeFormatter WriteToStream does not pass type to Serialize method RRS feed

  • Question

  • User1809142031 posted

    Due to this bug, when a web api action returns an interface or abstract type the root type information is not serialized when using a TypeNameHandling of Auto.

    Example:

    public abstract class Base
    {
    }

    public class Derived : Base
    {
    }

    public Base Get()
    {
        return new Derived(); // The json should contain a $type field
    }

    Tuesday, February 16, 2016 6:32 PM

Answers

  • User36583972 posted

    Hi Tydude4Christ,

    From your description, I did a program on my side, it can be executed correctly. I put my code in the follow for your reference.

        public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter
    
        {
            public JsonSerializerSettings _jsonSerializerSettings;
            public CustomJsonMediaTypeFormatter()
            {
                _jsonSerializerSettings = CustomJsonSettings.Instance;
    
                // Fill out the mediatype and encoding we support
                SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
                SupportedEncodings.Add(new UTF8Encoding(false, true));
            }
    
            public override bool CanReadType(Type type)
            {
                return true;
            }
    
            public override bool CanWriteType(Type type)
            {
                return true;
            }
    
            public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
            {
                // Create a serializer
                JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
    
                // Create task reading the content
                return Task.Factory.StartNew(() =>
                {
                    using (StreamReader streamReader = new StreamReader(stream, SupportedEncodings.First()))
                    {
                        using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                        {
                            return serializer.Deserialize(jsonTextReader, type);
                        }
                    }
                });
            }
    
            public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
            {
                // Create a serializer
                JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
    
                // Create task writing the serialized content
                return Task.Factory.StartNew(() =>
                {
                    using (StreamWriter streamWriter = new StreamWriter(stream, SupportedEncodings.First()))
                    {
                        using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
                        {
                            serializer.Serialize(jsonTextWriter, value);
                        }
                    }
                });
            }
        }
    
        public static class CustomJsonSettings
        {
            private static JsonSerializerSettings _settings;
            public static JsonSerializerSettings Instance
            {
                get
                {
                    if (_settings == null)
                    {
                        var settings = new JsonSerializerSettings();
                        settings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); 
                        settings.DateTimeZoneHandling = DateTimeZoneHandling.Local; 
                        settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                        _settings = settings;
                    }
    
                    return _settings;
                }
            }
        }

    You need to add the following code to my Application_Start method.

       GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
                GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
                GlobalConfiguration.Configuration.Formatters.Add(new CustomJsonMediaTypeFormatter());

    Information about JsonMediaTypeFormatter:

    https://msdn.microsoft.com/en-us/library/system.net.http.formatting.jsonmediatypeformatter(VS.108).aspx

    Best Regards,

    Yohann Lu

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, February 19, 2016 12:45 PM

All replies

  • User36583972 posted

    Hi Tydude4Christ,
    From to your description, I have made a sample on my side. It is works. You can try the following code to implement your needs.
    Base class:

        [JsonConverter(typeof(MyCustomConverter))]
        public abstract class Base
        {
            public string Hello;
            public string Mobile;
    
            private class MyCustomConverter : JsonCreationConverter<Base>
            {
                protected override Base Create(Type objectType,
                  Newtonsoft.Json.Linq.JObject jObject)
                {
                    //if ("ID".Equals(jObject.Value<string>("1"))
                    return new Derived();
                }
            }
        }
    
        public class Derived : Base
        {
            public string ID { get; set; }
            public string name { get; set; }
            public string address { get; set; }
        }
    
    
    
        public abstract class JsonCreationConverter<T> : JsonConverter
        {
            /// <summary>
            /// this is very important, otherwise serialization breaks!
            /// </summary>
            public override bool CanWrite
            {
                get
                {
                    return false;
                }
            }
            /// <summary> 
            /// Create an instance of objectType, based properties in the JSON object 
            /// </summary> 
            /// <param name="objectType">type of object expected</param> 
            /// <param name="jObject">contents of JSON object that will be 
            /// deserialized</param> 
            /// <returns></returns> 
            protected abstract T Create(Type objectType, JObject jObject);
    
            public override bool CanConvert(Type objectType)
            {
                return typeof(T).IsAssignableFrom(objectType);
            }
    
            public override object ReadJson(JsonReader reader, Type objectType,
              object existingValue, JsonSerializer serializer)
            {
                if (reader.TokenType == JsonToken.Null)
                    return null;
                // Load JObject from stream 
                JObject jObject = JObject.Load(reader);
    
                // Create target object based on JObject 
                T target = Create(objectType, jObject);
    
                // Populate the object properties 
                serializer.Populate(jObject.CreateReader(), target);
    
                return target;
            }
    
            public override void WriteJson(JsonWriter writer, object value,
              JsonSerializer serializer)
            {
                throw new NotImplementedException();
            }
        }

    API method:

            [AllowAnonymous]
            public IHttpActionResult Get()
            {
                //return new string[] { "value1", "value2" };
                return Ok(new Derived() { ID = "1", name = "name", address = "1222" , Hello="hello", Mobile="123456789"});
            }

    Icon:


    Best Regards,
     
    Yohann Lu

    Wednesday, February 17, 2016 5:44 AM
  • User1809142031 posted

    Thanks Yohann, that seems like it might work but is very dependent on knowing all derived types. What I did was simply to create a custom Media Type Formatter and register it. Here it is.

    public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter
    {
        public override void WriteToStream(Type type, object value, Stream writeStream, Encoding effectiveEncoding)
        {
            using (var jsonWriter = CreateJsonWriter(type, writeStream, effectiveEncoding))
            {
                CreateJsonSerializer().Serialize(jsonWriter, value, type);
            }
        }
    }

    Wednesday, February 17, 2016 1:21 PM
  • User36583972 posted

    Hi Tydude4Christ,

    From your description, I did a program on my side, it can be executed correctly. I put my code in the follow for your reference.

        public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter
    
        {
            public JsonSerializerSettings _jsonSerializerSettings;
            public CustomJsonMediaTypeFormatter()
            {
                _jsonSerializerSettings = CustomJsonSettings.Instance;
    
                // Fill out the mediatype and encoding we support
                SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
                SupportedEncodings.Add(new UTF8Encoding(false, true));
            }
    
            public override bool CanReadType(Type type)
            {
                return true;
            }
    
            public override bool CanWriteType(Type type)
            {
                return true;
            }
    
            public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
            {
                // Create a serializer
                JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
    
                // Create task reading the content
                return Task.Factory.StartNew(() =>
                {
                    using (StreamReader streamReader = new StreamReader(stream, SupportedEncodings.First()))
                    {
                        using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                        {
                            return serializer.Deserialize(jsonTextReader, type);
                        }
                    }
                });
            }
    
            public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
            {
                // Create a serializer
                JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
    
                // Create task writing the serialized content
                return Task.Factory.StartNew(() =>
                {
                    using (StreamWriter streamWriter = new StreamWriter(stream, SupportedEncodings.First()))
                    {
                        using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
                        {
                            serializer.Serialize(jsonTextWriter, value);
                        }
                    }
                });
            }
        }
    
        public static class CustomJsonSettings
        {
            private static JsonSerializerSettings _settings;
            public static JsonSerializerSettings Instance
            {
                get
                {
                    if (_settings == null)
                    {
                        var settings = new JsonSerializerSettings();
                        settings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); 
                        settings.DateTimeZoneHandling = DateTimeZoneHandling.Local; 
                        settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                        _settings = settings;
                    }
    
                    return _settings;
                }
            }
        }

    You need to add the following code to my Application_Start method.

       GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
                GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
                GlobalConfiguration.Configuration.Formatters.Add(new CustomJsonMediaTypeFormatter());

    Information about JsonMediaTypeFormatter:

    https://msdn.microsoft.com/en-us/library/system.net.http.formatting.jsonmediatypeformatter(VS.108).aspx

    Best Regards,

    Yohann Lu

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, February 19, 2016 12:45 PM