gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 02:33:11 25 4
gpt4 key购买 nike

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

这是其实例正在被序列化的类:

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

这是 Controller 、程序和启动类:

[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

最佳答案

这里的基本问题似乎是 documentation for FileInfo in netcore-2.2是错误的--FileInfo事实上,并没有标记为 [Serializable] 在.Net核心中。没有[Serializable] ,Json.NET将尝试序列化 FileInfo 的公共(public)属性而不是它的 ISerializable 数据,最终导致至少一个属性 FileInfo.Directory.Root.Root... 出现堆栈溢出异常。 。然后,返回的 JSON 在抛出异常时被截断,因为服务器此时已经开始写入响应。

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

要确认文档错误,reference source for .Net core (镜像 here )显示 FileInfo声明如下(虽然声明为partial,但它似乎只有一个文件):

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

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

// 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基类上的接口(interface),如 Json.NET 11 release notes 中所述:

  • Change - Types that implement ISerializable but don't have [SerializableAttribute] are not serialized using ISerializable

那么,可以做什么呢?一种可能性是创建一个 custom contract resolver这迫使FileInfo使用ISerializable进行序列化接口(interface):

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

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

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

另一种可能性是创建 custom JsonConverter 对于 FileInfo序列化和反序列化与完整框架相同的属性:

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 .

注释:

关于c# - 带有 FileInfo 的 ASP.NET Core 序列化对象返回不完整的 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56670748/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com