我想序列化一个自定义对象:
public class MyCustomObject
{
public string Name { get; set; }
public DateTime Date { get; set; }
public List<HttpPostedFileBase> Files { get; set; }
public MyCustomObject()
{
Files = new List<HttpPostedFileBase>();
}
}
在json。为此,我使用自定义转换器:
public class HttpPostedFileConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var stream = (Stream)value;
using (var sr = new BinaryReader(stream))
{
var buffer = sr.ReadBytes((int)stream.Length);
writer.WriteValue(Convert.ToBase64String(buffer));
}
}
我使用JsonSerializerSettings序列化到json.net知道哪个类型的工具(对于HttpPostedFileBase)。
var settings = new JsonSerializerSettings();
settings.Converters.Add(new HttpPostedFileConverter());
settings.TypeNameHandling = TypeNameHandling.Objects;
对象已正确序列化,但序列化时出现此错误:
JsonSerializationException Error converting value "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDA
这是我序列化对象的值:
{
"$type": "ConsoleApplication1.MyCustomObject, ConsoleApplication1",
"Name": "Test2",
"Date": "2016-11-03T12:35:14.6020154+01:00",
"Files": [
{
"$type": "System.Web.HttpPostedFileWrapper, System.Web",
"ContentLength": 1024,
"FileName": "Pannigale.jpg",
"ContentType": "image/jpg",
"InputStream": "/9j/4AAQ...KKAP//Z"
}
]
}
反序列化有什么问题?
修改 我测试了一个类来测试......现在它可以工作了:
public class TestHttpFile : HttpPostedFileBase
{
string fullFileName = @"C:\Pictures\SBK-1299-Panigale-S_2015_Studio_R_B01_1920x1080.mediagallery_output_image_[1920x1080].jpg";
public override int ContentLength
{
get
{
return 1024;
}
}
public override string FileName
{
get
{
return "Pannigale.jpg";
}
}
public override string ContentType
{
get
{
return "image/jpg";
}
}
public override Stream InputStream
{
get
{
return File.OpenRead(fullFileName);
}
}
}
在序列化中,我注意到了这种差异:
"$type": "ConsoleApplication1.TestHttpFile, ConsoleApplication1",
而不是
"$type": "System.Web.HttpPostedFileWrapper, System.Web",
但最后我不想创建一个包装器或其他......我不明白为什么它适用于这种类型,而不是HttpPostedFileWrapper
。
答案 0 :(得分:5)
为什么反序列化失败
简而言之:您尝试反序列化的类型,即HttpPostedFileWrapper
及其基础HttpPostedFile
,不可公开构建。
据推测,您的HttpPostedFileConverter
实际上是流转换器,其工作方式如下:
public class StreamConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Stream).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var bytes = serializer.Deserialize<byte[]>(reader);
return new MemoryStream(bytes);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var stream = (Stream)value;
var bytes = stream.ReadAllBytesAndReposition();
serializer.Serialize(writer, bytes);
}
}
public static class StreamExtensions
{
public static byte[] ReadAllBytesAndReposition(this Stream stream)
{
const int bufferSize = 4096;
using (var ms = new MemoryStream())
{
byte[] buffer = new byte[bufferSize];
int count;
var position = stream.CanSeek ? stream.Position : (long?)null;
while ((count = stream.Read(buffer, 0, buffer.Length)) != 0)
ms.Write(buffer, 0, count);
if (position != null)
{
// Restore position
stream.Position = position.Value;
}
return ms.ToArray();
}
}
}
在这种情况下,假设我还在序列化设置中设置TypeNameHandling = TypeNameHandling.Auto
,我可以为您的MyCustomObject
类生成类似于您的JSON。但是,反序列化将失败,因为具体类型System.Web.HttpPostedFileWrapper
只有一个构造函数,需要HttpPostedFile
。来自reference source:
public class HttpPostedFileWrapper : HttpPostedFileBase {
private HttpPostedFile _file;
public HttpPostedFileWrapper(HttpPostedFile httpPostedFile) {
if (httpPostedFile == null) {
throw new ArgumentNullException("httpPostedFile");
}
_file = httpPostedFile;
}
由于构造函数是公共的,Json.NET会调用它,但由于JSON中没有名为httpPostedFile
的属性,它将为该值传递null
,从而导致ArgumentNullException
被抛出。
这将我们带到下一个问题:HttpPostedFile
没有公共构造函数。相反,要直接创建一个,您必须通过反射调用几个不同的Microsoft内部方法,如How to instantiate a HttpPostedFile中所示。因此,即使您为HttpPostedFileWrapper
创建custom JsonConverter
,在内部构造必需的HttpPostedFile
也是非常棘手的。 Json.NET当然不能自动完成。
那么,你有什么选择?
选项1:创建自己的HttpPostedFilesBase
您可以创建自己的HttpPostedFilesBase
子类型,可以成功序列化和反序列化,然后在序列化期间使用适当的HttpPostedFilesBase
将JsonConverter
的所有实例映射到此类型。以下类型MemoryHttpPostedFile
执行此操作:
public sealed class MemoryHttpPostedFile : HttpPostedFileBase
{
readonly string contentType;
readonly string fileName;
readonly MemoryStream inputStream;
public MemoryHttpPostedFile(string contentType, string fileName, [JsonConverter(typeof(StreamConverter))] MemoryStream inputStream)
{
if (inputStream == null)
throw new ArgumentNullException("inputStream");
this.contentType = contentType;
this.fileName = fileName;
this.inputStream = inputStream;
}
public override int ContentLength { get { return (int)inputStream.Length; } }
public override string ContentType { get { return contentType; } }
public override string FileName { get { return fileName; } }
[JsonConverter(typeof(StreamConverter))]
public override Stream InputStream { get { return inputStream; } }
//TODO: implement SaveAs()
public override void SaveAs(string filename)
{
// Implement based on HttpPostedFile.SaveAs()
// https://referencesource.microsoft.com/#System.Web/HttpPostedFile.cs,678e7f8bc95c149f
throw new NotImplementedException();
}
}
public class HttpPostedFileBaseConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(HttpPostedFileBase).IsAssignableFrom(objectType)
&& !typeof(MemoryHttpPostedFile).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var postedFile = (HttpPostedFileBase)value;
// Save position
var wrapper = new MemoryHttpPostedFile(postedFile.ContentType, postedFile.FileName, new MemoryStream(postedFile.InputStream.ReadAllBytesAndReposition()));
serializer.Serialize(writer, wrapper);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var wrapper = serializer.Deserialize<MemoryHttpPostedFile>(reader);
return wrapper;
}
}
由于HttpPostedFilesBase
的所有子类型都已映射到MemoryHttpPostedFile
,因此不再需要设置TypeNameHandling.Auto
。注意我在本回答的前面部分使用了StreamConverter
和StreamExtensions
。
将转换器添加到如下设置中:
var settings = new JsonSerializerSettings
{
Converters = new JsonConverter[] { new HttpPostedFileBaseConverter() },
Formatting = Formatting.Indented,
};
选项2:实际序列化和反序列化HttpPostedFileWrapper
要做到这一点,我们需要使用相当复杂的&#34; hacky&#34;代码从this answer到How to instantiate a HttpPostedFile由paracycle完成,如下所示:
public class HttpPostedFileBaseConverter : JsonConverter
{
class HttpPostedFileSurrogate
{
public string ContentType { get; set; }
public string FileName { get; set; }
public byte[] InputStream { get; set; }
}
public override bool CanConvert(Type objectType)
{
return typeof(HttpPostedFileBase).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var wrapper = (HttpPostedFileWrapper)value;
// Save position
var surrogate = new HttpPostedFileSurrogate
{
ContentType = wrapper.ContentType,
FileName = wrapper.FileName,
InputStream = wrapper.InputStream.ReadAllBytesAndReposition(),
};
serializer.Serialize(writer, surrogate);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var surrogate = serializer.Deserialize<HttpPostedFileSurrogate>(reader);
var file = HttpPostedFileExtensions.ConstructHttpPostedFile(surrogate.InputStream, surrogate.FileName, surrogate.ContentType);
return new HttpPostedFileWrapper(file);
}
}
public static class HttpPostedFileExtensions
{
public static HttpPostedFile ConstructHttpPostedFile(byte[] data, string filename, string contentType)
{
// Adapted from https://stackoverflow.com/questions/5514715/how-to-instantiate-a-httppostedfile/5515134#5515134
// Get the System.Web assembly reference (they seem to be in different assemblies in different versions of .Net
var assemblies = new[] { typeof(HttpPostedFile).Assembly, typeof(HttpPostedFileBase).Assembly };
// Get the types of the two internal types we need
Type typeHttpRawUploadedContent = assemblies.Select(a => a.GetType("System.Web.HttpRawUploadedContent")).Where(t => t != null).First();
Type typeHttpInputStream = assemblies.Select(a => a.GetType("System.Web.HttpInputStream")).Where(t => t != null).First();
// Prepare the signatures of the constructors we want.
Type[] uploadedParams = { typeof(int), typeof(int) };
Type[] streamParams = { typeHttpRawUploadedContent, typeof(int), typeof(int) };
Type[] parameters = { typeof(string), typeof(string), typeHttpInputStream };
// Create an HttpRawUploadedContent instance
object uploadedContent = typeHttpRawUploadedContent
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, uploadedParams, null)
.Invoke(new object[] { data.Length, data.Length });
// Call the AddBytes method
typeHttpRawUploadedContent
.GetMethod("AddBytes", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(uploadedContent, new object[] { data, 0, data.Length });
// This is necessary if you will be using the returned content (ie to Save)
typeHttpRawUploadedContent
.GetMethod("DoneAddingBytes", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(uploadedContent, null);
// Create an HttpInputStream instance
object stream = (Stream)typeHttpInputStream
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, streamParams, null)
.Invoke(new object[] { uploadedContent, 0, data.Length });
// Create an HttpPostedFile instance
HttpPostedFile postedFile = (HttpPostedFile)typeof(HttpPostedFile)
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, parameters, null)
.Invoke(new object[] { filename, contentType, stream });
return postedFile;
}
}
注意我在本回答的前面部分使用StreamExtensions
。此版本的HttpPostedFileBaseConverter
也会像往常一样添加到JsonSerializerSettings.Converters
。
(老实说,我不推荐这个解决方案,因为它过分依赖于微软未实现的HttpPostedFile
实现的无证内部,这可能会在以后的版本中轻易改变。)< / p>
答案 1 :(得分:3)
实际上有两个不同的问题。
流转换器
我想在json中序列化自定义对象...为此,我使用自定义转换器......
虽然您已将其称为HttpPostedFileConverter
,但发布的代码表明其所做的只是将Stream
转换为Base64
编码string
。因此,请以不同方式调用它并实现所有方法:
public class StreamConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Stream).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new MemoryStream(Convert.FromBase64String((string)reader.Value));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var stream = (Stream)value;
using (var sr = new BinaryReader(stream))
{
var buffer = sr.ReadBytes((int)stream.Length);
writer.WriteValue(Convert.ToBase64String(buffer));
}
}
}
它将用于InputStream
成员序列化/反序列化,因此在这两个地方你都会使用:
settings.Converters.Add(new StreamConverter());
对于序列化,这就是你所需要的。现在是棘手的部分。
反序列化HttpPostedFileBase
个实例
但最后我不想创建一个包装器或其他......我不明白为什么它适用于这种类型,而不是
HttpPostedFileWrapper
问题是用于序列化的HttpPostedFileWrapper
没有无参数构造函数。实际上,唯一可用的构造函数具有以下签名:
public HttpPostedFileWrapper(
HttpPostedFile httpPostedFile
)
如您所见,它需要HttpPostedFile
。但HttpPostedFile
根本没有公共构造。
因此,无论是否想要,除了创建用于反序列化HttpPostedFileBase
实例的自定义类之外别无选择。
我们这样做:
public class CustomHttpPostedFile : HttpPostedFileBase
{
[JsonProperty("ContentLength")]
private int contentLength;
[JsonProperty("FileName")]
private string fileName;
[JsonProperty("ContentType")]
private string contentType;
[JsonProperty("InputStream")]
private Stream inputStream;
[JsonIgnore]
public override int ContentLength { get { return contentLength; } }
[JsonIgnore]
public override string FileName { get { return fileName; } }
[JsonIgnore]
public override string ContentType { get { return contentType; } }
[JsonIgnore]
public override Stream InputStream { get { return inputStream; } }
public override void SaveAs(string filename)
{
using (var output = File.Create(fileName))
InputStream.CopyTo(output);
}
}
但这还不够。为了让反序列化程序使用此类而不是HttpPostedFileWrapper
,您需要{JEN.NET Deserialize with CustomCreationConverter示例中的CustomCreationConverter
:
public class HttpPostedFileBaseConverter : CustomCreationConverter<HttpPostedFileBase>
{
public override HttpPostedFileBase Create(Type objectType)
{
return new CustomHttpPostedFile();
}
}
并仅在反序列化时使用它:
settings.Converters.Add(new StreamConverter());
settings.Converters.Add(new HttpPostedFileBaseConverter());
一切都会奏效。
回顾一下,您遇到的主要问题是由于抽象类的对象内部的使用及其无法控制的具体实现而导致无法反序列化(因为不是为了使用而设计的)完全序列化)。所以好处是解决方案存在,编写一些自定义代码的成本。
答案 2 :(得分:2)
HttpPostedFileBase
是一个抽象类,HttpPostedFile
是它的具体实现。当您从ASP.Net中的用户上传文件时,其类型实际上是HttpPostedFile
。所以你正在寻找的包装器已经存在,你不必写一个。
var file = (HttpPostedFile) Request.Files[0];
似乎你需要存储的内容不是严格意义上的HttpPostedFile
或HttpPostedFileBase
,而是一个任意类,它具有大小,mime类型,文件名和流等属性作为字节数组。如果要将对象存储在数据库等中,这会更有意义。为什么不从HttpPostedFile
创建类并复制属性?
修改强>
似乎json无法反序列化为HttpPostedFile
,因为它没有构造函数。您可以选择编写一个继承自HttpPostedFileBase
的类或编写一个新类。
以下是我曾经测试过的JsonConverter:
public class StreamConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var stream = (Stream)value;
using (var sr = new BinaryReader(stream))
{
var buffer = sr.ReadBytes((int)stream.Length);
writer.WriteValue(Convert.ToBase64String(buffer));
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var str = reader.ReadAsString();
var stream = new MemoryStream();
using (var sr = new BinaryWriter(stream))
{
sr.Write(Convert.FromBase64String(str));
}
return stream;
}
public override bool CanConvert(Type objectType)
{
return typeof(Stream).IsAssignableFrom(objectType);
}
}