簡體   English   中英

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

[英]ASP.NET Core serialized object with a FileInfo returns incomplete JSON

我有一個帶有控制器的ASP.NET Core 2.2項目,其GET方法返回一個包含System.IO.FileInfo屬性的對象。 當我調用API時(例如在Web瀏覽器中),它返回一個不完整的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();
    }
}

這是URL:

https://localhost:44381/Test/Home

結果我得到了:

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

那么為什么JSON字符串不完整,在FileInfo對象中斷? FileInfo是可序列化的

如果您想自己嘗試,這是完整的項目:

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

這里的基本問題似乎是netcore-2.2中FileInfo文檔是錯誤的 - 事實上, FileInfo在.Net核心中沒有標記為[Serializable] 如果沒有[Serializable] ,Json.NET將嘗試序列化FileInfo的公共屬性而不是其ISerializable數據,最終導致至少一個屬性FileInfo.Directory.Root.Root...的堆棧溢出異常FileInfo.Directory.Root.Root... 返回的JSON然后在拋出異常時被截斷,因為服務器已經開始在該點寫入響應。

(事實上​​,似乎FileInfo被列入.Net核心列入黑名單以避免堆棧溢出,請參閱問題#1541:在dotnet核心2上序列化DirectoryInfo對象時出現StackOverflowException 。而是拋出自定義異常。)

為確認文檔錯誤, .Net core參考源此處鏡像)顯示FileInfo聲明如下(雖然聲明為partial但它似乎只有一個文件):

// Class for creating FileStream objects, and some basic file management
// routines such as Delete, etc.
public sealed partial class FileInfo : FileSystemInfo
{

雖然完整框架參考源顯示以下內容:

// Class for creating FileStream objects, and some basic file management
// routines such as Delete, etc.
[Serializable]
[ComVisible(true)]
public sealed class FileInfo: FileSystemInfo
{

缺少[Serializable]屬性,Json.NET將忽略基類上的ISerializable接口,如Json.NET 11 發行說明中所述

  • 更改 - 使用ISerializable不會序列化實現ISerializable但沒有[SerializableAttribute]的類型

那么,可以做些什么呢? 一種可能性是創建一個自定義合約解析器 ,強制使用ISerializable接口序列化FileInfo

public class FileInfoContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (objectType == typeof(FileInfo))
        {
            return CreateISerializableContract(objectType);
        }

        var contract = base.CreateContract(objectType);
        return contract;
    }
}

配置合同解析程序,例如, 設置JsonConvert.DefaultSettings asp net core 2.0不能按預期工作

另一種可能性是為FileInfo創建一個自定義JsonConverter ,它可以像完整框架那樣序列化和反序列化相同的屬性:

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

然后將new ISerializableJsonConverter<FileInfo>()JsonSerializerSettings.Converters

筆記:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM