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

ror*_*.ap 4 .net c# json json.net asp.net-core

我有一个带有控制器的 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; }
}
Run Code Online (Sandbox Code Playgroud)

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

[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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是网址:

https://localhost:44381/Test/Home
Run Code Online (Sandbox Code Playgroud)

我得到的结果:

{"$id":"1","name":"First thing","file":
Run Code Online (Sandbox Code Playgroud)

那么为什么 JSON 字符串不完整,在FileInfo对象处中断? FileInfo是可序列化的

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

https://github.com/roryap/FileInfoAspNetCoreIssue

我发现的所有参考文献都涵盖了这类事情,就像下面的参考文献一样,讨论了 EF Core 和循环引用,这显然不是这里的情况。

/sf/answers/3945617231/

/sf/answers/3824344121/

/sf/answers/3445746111/

dbc*_*dbc 5

这里的基本问题似乎是netcore-2.2中的文档FileInfo是错误的——FileInfo事实上,[Serializable]在 .Net core 中没有标记。如果没有[Serializable],Json.NET 将尝试序列化 的公共属性FileInfo而不是其ISerializable数据,最终导致至少一个属性FileInfo.Directory.Root.Root.... 返回的 JSON 会在抛出异常时被截断,因为服务器在那时已经开始写入响应。

(实际上,它似乎FileInfo在 .Net 核心上被列入黑名单以避免堆栈溢出,请参阅问题 #1541:在 dotnet 核心 2 上序列化 DirectoryInfo 对象时出现 StackOverflowException。而是抛出自定义异常。)

为了确认文档错误,.Net 核心参考源此处镜像)显示FileInfo如下声明(虽然声明为partial它似乎只有一个文件):

// Class for creating FileStream objects, and some basic file management
// routines such as Delete, etc.
public sealed partial class FileInfo : FileSystemInfo
{
Run Code Online (Sandbox Code Playgroud)

虽然完整框架参考源显示如下:

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

缺少该[Serializable]属性,Json.NET 将忽略ISerializable基类上的接口,如 Json.NET 11发行说明中所述

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

那么,有什么办法呢?一种可能性是创建一个自定义合约解析器,强制FileInfo使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

配置合同解析器,例如,设置 JsonConvert.DefaultSettings asp net core 2.0 not working as expected

另一种可能性是为序列化和反序列化与完整框架相同的属性创建自定义JsonConverterFileInfo

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

笔记:


归档时间:

查看次数:

1359 次

最近记录:

6 年,10 月 前