![](/img/trans.png)
[英]ASP.NET Core Web API returns incomplete JSON data when I use 'include' in my query
[英]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
這里的基本問題似乎是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
。
筆記:
有關當給定類型缺少serializable屬性時Json.NET忽略ISerializable
原因的詳細信息,請參閱Newtonsoft.Json中反序列化自定義異常的 答案 。
您可能希望靜態緩存合同解析程序以獲得最佳性能 。
通過ISerializable
序列化可能不適用於部分信任的情況。
請注意, asp.net-core-3.0設置為使用完全不同的JSON序列化程序 ,因此需要一些額外的配置工作才能在此處使用此答案。 有關詳細信息,請參閱IMvcBuilder AddJsonOptions在.Net Core 3.0中的位置? 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.