具有FileInfo的ASP.NET Core序列化对象返回不完整的JSON

时间:2019-06-19 15:01:18

标签: c# .net json asp.net-core json.net

我有一个带有控制器的ASP.NET Core 2.2项目,该控制器的GET方法返回一个包含System.IO.FileInfo属性的对象。当我调用API(例如在网络浏览器中)时,它会返回不完整的JSON字符串。

这是实例要序列化的类:

public class Thing
{
    public string Name { get; set; }
    public FileInfo File { get; set; }
}

这是控制器,程序和启动类:

[Route("Test/Home")]
[ApiController]
public class HomeController : Controller
{
    [HttpGet]
    public async Task<ActionResult<Thing>> GetThing()
    {
        return new Thing()
        {
            Name = "First thing",
            File = new FileInfo("c:\file.txt")
        };
    }
}

public class Program
{
    public static async Task Main(string[] args) 
        => await CreateWebHostBuilder(args).Build().RunAsync();

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        services.AddSingleton<Thing>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

以下是网址:

https://localhost:44381/Test/Home

得到的结果是

{"$id":"1","name":"First thing","file":

那为什么JSON字符串不完整,在FileInfo对象处断开? FileInfo is serializable

如果您想自己尝试,这里是完整的项目:

https://github.com/roryap/FileInfoAspNetCoreIssue

我发现的所有涉及此类内容的参考文献(如下面的参考文献)都在谈论EF Core和周期性参考文献,显然这里不是这种情况。

https://stackoverflow.com/a/56365960/2704659

https://stackoverflow.com/a/54633487/2704659

https://stackoverflow.com/a/49224944/2704659

1 个答案:

答案 0 :(得分:3)

这里的基本问题似乎是documentation for FileInfo in netcore-2.2是错误的-{。{1}}实际上在.Net核心中没有标记为[Serializable]。如果没有FileInfo,Json.NET将尝试序列化[Serializable]的公共属性而不是其ISerializable数据,最终导致至少一个属性{{ 1}}。然后,返回的JSON在引发异常的那一刻被截断,因为服务器已经开始在该点写响应。

(实际上,{。{1}}在.Net核心上已被列入黑名单,以避免堆栈溢出,请参见Issue #1541: StackOverflowException when serializing DirectoryInfo object on dotnet core 2。而是抛出自定义异常。)

为确认文档错误,reference source for .Net core(镜像here)显示FileInfo声明如下(声明为FileInfo.Directory.Root.Root...时似乎只有一个文件) ):

FileInfo

reference source for the full framework显示以下内容:

FileInfo

如Json.NET 11 release notes中所述,缺少partial属性,Json.NET将忽略基类上的// Class for creating FileStream objects, and some basic file management // routines such as Delete, etc. public sealed partial class FileInfo : FileSystemInfo { 接口:

  
      
  • 更改-实现ISerializable但不具有[SerializableAttribute]的类型不会使用ISerializable进行序列化
  •   

那么,该怎么办?一种可能性是创建一个custom contract resolver来强制// Class for creating FileStream objects, and some basic file management // routines such as Delete, etc. [Serializable] [ComVisible(true)] public sealed class FileInfo: FileSystemInfo { 使用[Serializable]接口进行序列化:

ISerializable

按如下所示配置合同解析器,例如 Setting JsonConvert.DefaultSettings asp net core 2.0 not working as expected

另一种可能性是为FileInfo创建一个custom JsonConverter,该序列将与整个框架相同的属性序列化和反序列化:

ISerializable

然后将public class FileInfoContractResolver : DefaultContractResolver { protected override JsonContract CreateContract(Type objectType) { if (objectType == typeof(FileInfo)) { return CreateISerializableContract(objectType); } var contract = base.CreateContract(objectType); return contract; } } 添加到JsonSerializerSettings.Converters

注意:

  • 有关当给定类型缺少可序列化属性时Json.NET为何忽略FileInfo的详细信息,请参见this answer Deserializing custom exceptions in Newtonsoft.Json 。 p>

  • 您可能想statically cache the contract resolver for best performance

  • 在部分信任的情况下,通过
  • 通过public class ISerializableJsonConverter<T> : JsonConverter where T : ISerializable { // Simplified from // - JsonSerializerInternalReader.CreateISerializable() // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L1708 // - JsonSerializerInternalWriter.SerializeISerializable() // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L837 // By James Newton-King http://james.newtonking.com/ // Not implemented: // PreserveReferencesHandling, TypeNameHandling, ReferenceLoopHandling, NullValueHandling public override bool CanConvert(Type objectType) { return objectType == typeof(T); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null) return null; if (reader.TokenType != JsonToken.StartObject) throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType)); SerializationInfo serializationInfo = new SerializationInfo(objectType, new JsonFormatterConverter(serializer)); while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject) { switch (reader.TokenType) { case JsonToken.PropertyName: serializationInfo.AddValue((string)reader.Value, JToken.ReadFrom(reader.ReadToContentAndAssert())); break; default: throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType)); } } return Activator.CreateInstance(objectType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { serializationInfo, serializer.Context }, serializer.Culture); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializable = (ISerializable)value; SerializationInfo serializationInfo = new SerializationInfo(value.GetType(), new FormatterConverter()); serializable.GetObjectData(serializationInfo, serializer.Context); writer.WriteStartObject(); foreach (SerializationEntry serializationEntry in serializationInfo) { writer.WritePropertyName(serializationEntry.Name); serializer.Serialize(writer, serializationEntry.Value); } writer.WriteEndObject(); } } public static partial class JsonExtensions { public static JsonReader ReadToContentAndAssert(this JsonReader reader) { return reader.ReadAndAssert().MoveToContentAndAssert(); } public static JsonReader MoveToContentAndAssert(this JsonReader reader) { if (reader == null) throw new ArgumentNullException(); if (reader.TokenType == JsonToken.None) // Skip past beginning of stream. reader.ReadAndAssert(); while (reader.TokenType == JsonToken.Comment) // Skip past comments. reader.ReadAndAssert(); return reader; } public static JsonReader ReadAndAssert(this JsonReader reader) { if (reader == null) throw new ArgumentNullException(); if (!reader.Read()) throw new JsonReaderException("Unexpected end of JSON stream."); return reader; } } internal class JsonFormatterConverter : IFormatterConverter { //Adapted and simplified from // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/FormatterConverter.cs // By James Newton-King http://james.newtonking.com/ JsonSerializer serializer; public JsonFormatterConverter(JsonSerializer serializer) { this.serializer = serializer; } private T GetTokenValue<T>(object value) { JValue v = (JValue)value; return (T)System.Convert.ChangeType(v.Value, typeof(T), CultureInfo.InvariantCulture); } public object Convert(object value, Type type) { if (!(value is JToken)) { throw new ArgumentException("Value is not a JToken.", "value"); } return ((JToken)value).ToObject(type, serializer); } public object Convert(object value, TypeCode typeCode) { if (value is JValue) { value = ((JValue)value).Value; } return System.Convert.ChangeType(value, typeCode, CultureInfo.InvariantCulture); } public bool ToBoolean(object value) { return GetTokenValue<bool>(value); } public byte ToByte(object value) { return GetTokenValue<byte>(value); } public char ToChar(object value) { return GetTokenValue<char>(value); } public DateTime ToDateTime(object value) { return GetTokenValue<DateTime>(value); } public decimal ToDecimal(object value) { return GetTokenValue<decimal>(value); } public double ToDouble(object value) { return GetTokenValue<double>(value); } public short ToInt16(object value) { return GetTokenValue<short>(value); } public int ToInt32(object value) { return GetTokenValue<int>(value); } public long ToInt64(object value) { return GetTokenValue<long>(value); } public sbyte ToSByte(object value) { return GetTokenValue<sbyte>(value); } public float ToSingle(object value) { return GetTokenValue<float>(value); } public string ToString(object value) { return GetTokenValue<string>(value); } public ushort ToUInt16(object value) { return GetTokenValue<ushort>(value); } public uint ToUInt32(object value) { return GetTokenValue<uint>(value); } public ulong ToUInt64(object value) { return GetTokenValue<ulong>(value); } } 进行序列化可能不起作用。

  • 请注意,设置为开箱即用completely different JSON serializer,因此在此处使用此答案将需要一些其他配置工作。有关详细信息,请参见 Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0?