我有一个带有控制器的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
答案 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);
}
}
进行序列化可能不起作用。
请注意,asp.net-core-3.0设置为开箱即用completely different JSON serializer,因此在此处使用此答案将需要一些其他配置工作。有关详细信息,请参见 Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0? 。