I have a small REST service that I am running via the HttpSelfHostServer, but have am having some problems deserializing some data that is posted to the server. The method signature is as follows:
public HttpResponseMessage PostServers(ServerType serverType)
The method is being called fine, however on deserialization of the data using the following code:
var servers = Request.Content.ReadAsAsync<List<ServerZoneInformation>>().Result;
...
an IOException is thrown with the message "Cannot access a closed Stream.". The same error occurs when trying to deserialize the code via
XmlSerializer serializer = new XmlSerializer(typeof(List<ServerZoneInformation>));
var servers = (List<ServerZoneInformation>)serializer.Deserialize(Request.Content.ReadAsStreamAsync().Result);
...
However I can get the method to work if I use the following:
XmlSerializer serializer = new XmlSerializer(typeof(List<ServerZoneInformation>));
string data = Request.Content.ReadAsStringAsync().Result;
using (MemoryStream ms = new MemoryStream(UTF8Encoding.UTF8.GetBytes(data)))
{
var servers = (List<ServerZoneInformation>)serializer.Deserialize(ms);
...
}
Am I doing somthing wrong in the first two steps, or is this a bug in the WebApi?
Note: I have not tried this when hosting via IIS yet.
Yeah, this is a bug in Web API. When you use ReadAsAsync<T>(), it uses the default formatters that Web API ships with, like Xml and Json.
These Xml and Json formatters currently close the read stream after they are done and hence you are seeing the problem.
The first time consumption happens by ModelBinding stage, which uses the formatters to read the incoming request in order to bind to your Action method's parameter 'ServerType'. After this consumption, the request's stream would have been closed by the
formatters.
This issue is going to be fixed for the next release.
Thanks,
Kiran Challa
Marked as answer by nz-Ben-nz on Apr 01, 2012 10:56 PM
Yeah, I could think of the following workaround...but i am afraid its not very simple though...
Basically, we do not want our default formatters to close the stream on Read...so we can wrap this stream inside another stream and override the Close() method and do nothing (btw, this is the technique which we use in our RC bits too)...
In the following example, we are customizing the default Json formatter...you can then add this customized one into the Configuration's formatter collection...
using System.IO;
using System;
using System.Net.Http.Formatting;
public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(Type type, Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
{
NonClosingDelegatingStream wrapperStream = new NonClosingDelegatingStream(innerStream: stream);
return base.ReadFromStreamAsync(type, wrapperStream, contentHeaders, formatterLogger);
}
}
public class NonClosingDelegatingStream : DelegatingStream
{
public NonClosingDelegatingStream(Stream innerStream)
:base(innerStream)
{
}
public override void Close()
{
//NOTE: do not call Close on the InnerStream
}
}
public class DelegatingStream : Stream
{
protected Stream _innerStream;
protected DelegatingStream(Stream innerStream)
{
if (innerStream == null)
{
throw new ArgumentNullException("innerStream");
}
_innerStream = innerStream;
}
public override bool CanRead
{
get { return _innerStream.CanRead; }
}
public override bool CanSeek
{
get { return _innerStream.CanSeek; }
}
public override bool CanWrite
{
get { return _innerStream.CanWrite; }
}
public override long Length
{
get { return _innerStream.Length; }
}
public override long Position
{
get { return _innerStream.Position; }
set { _innerStream.Position = value; }
}
public override int ReadTimeout
{
get { return _innerStream.ReadTimeout; }
set { _innerStream.ReadTimeout = value; }
}
public override bool CanTimeout
{
get { return _innerStream.CanTimeout; }
}
public override int WriteTimeout
{
get { return _innerStream.WriteTimeout; }
set { _innerStream.WriteTimeout = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_innerStream.Dispose();
}
base.Dispose(disposing);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _innerStream.Seek(offset, origin);
}
public override int Read(byte[] buffer, int offset, int count)
{
return _innerStream.Read(buffer, offset, count);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _innerStream.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _innerStream.EndRead(asyncResult);
}
public override int ReadByte()
{
return _innerStream.ReadByte();
}
public override void Flush()
{
_innerStream.Flush();
}
public override void SetLength(long value)
{
_innerStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
_innerStream.Write(buffer, offset, count);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _innerStream.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
_innerStream.EndWrite(asyncResult);
}
public override void WriteByte(byte value)
{
_innerStream.WriteByte(value);
}
}
In the following example, we are customizing the default Json formatter...you can then add this customized one into the Configuration's formatter collection...
The example posted by Kiran almost works, but not quite. I no longer got stream closed exceptions, however started getting deserialization issues. With the following changes to the Type Formatter I did manage to get the class working for me with no errors.
Also note that the formatters need to be defined in the initial HttpConfiguration/HttpSelfHostConfiguration class. Simply calling the ReadAsAsync override that allows you to define the MediaTypeFormatters will not work.
Marked as answer by nz-Ben-nz on Apr 27, 2012 12:05 AM
You have to be careful with the logic of resetting the stream's Position back to 0. This is because sometimes the stream provided to the ReadFromStreamAsync method could be a non-rewindable stream.
You should first check if the stream supplied to the ReadFromStreamAsync method is Seekable (you can use the CanSeek property to check) and only then reset the position to 0.
For example, the scenario where you could be supplied a non-rewindable stream is when you set the TransferMode of the HttpSelfHostConfiguration to be Streamed instead of the default one Buffered.
nz-Ben-nz
Member
1 Points
5 Posts
Deserialization Issues with ReadAsAsync
Apr 01, 2012 10:12 PM|LINK
Hi All,
I have a small REST service that I am running via the HttpSelfHostServer, but have am having some problems deserializing some data that is posted to the server. The method signature is as follows:
The method is being called fine, however on deserialization of the data using the following code:
an IOException is thrown with the message "Cannot access a closed Stream.". The same error occurs when trying to deserialize the code via
However I can get the method to work if I use the following:
XmlSerializer serializer = new XmlSerializer(typeof(List<ServerZoneInformation>)); string data = Request.Content.ReadAsStringAsync().Result; using (MemoryStream ms = new MemoryStream(UTF8Encoding.UTF8.GetBytes(data))) { var servers = (List<ServerZoneInformation>)serializer.Deserialize(ms); ... }Am I doing somthing wrong in the first two steps, or is this a bug in the WebApi?
Note: I have not tried this when hosting via IIS yet.
Cheers
Ben
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Deserialization Issues with ReadAsAsync
Apr 01, 2012 10:44 PM|LINK
Yeah, this is a bug in Web API. When you use ReadAsAsync<T>(), it uses the default formatters that Web API ships with, like Xml and Json.
These Xml and Json formatters currently close the read stream after they are done and hence you are seeing the problem.
The first time consumption happens by ModelBinding stage, which uses the formatters to read the incoming request in order to bind to your Action method's parameter 'ServerType'. After this consumption, the request's stream would have been closed by the formatters.
This issue is going to be fixed for the next release.
Kiran Challa
fedak
Member
18 Points
13 Posts
Re: Deserialization Issues with ReadAsAsync
Apr 08, 2012 10:00 PM|LINK
Is there a workaround for this?
Santos
Member
6 Points
5 Posts
Re: Deserialization Issues with ReadAsAsync
Apr 17, 2012 05:44 PM|LINK
Any workaround available for this?
Santos
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Deserialization Issues with ReadAsAsync
Apr 17, 2012 06:36 PM|LINK
Yeah, I could think of the following workaround...but i am afraid its not very simple though...
Basically, we do not want our default formatters to close the stream on Read...so we can wrap this stream inside another stream and override the Close() method and do nothing (btw, this is the technique which we use in our RC bits too)...
In the following example, we are customizing the default Json formatter...you can then add this customized one into the Configuration's formatter collection...
using System.IO; using System; using System.Net.Http.Formatting; public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter { public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(Type type, Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger) { NonClosingDelegatingStream wrapperStream = new NonClosingDelegatingStream(innerStream: stream); return base.ReadFromStreamAsync(type, wrapperStream, contentHeaders, formatterLogger); } } public class NonClosingDelegatingStream : DelegatingStream { public NonClosingDelegatingStream(Stream innerStream) :base(innerStream) { } public override void Close() { //NOTE: do not call Close on the InnerStream } } public class DelegatingStream : Stream { protected Stream _innerStream; protected DelegatingStream(Stream innerStream) { if (innerStream == null) { throw new ArgumentNullException("innerStream"); } _innerStream = innerStream; } public override bool CanRead { get { return _innerStream.CanRead; } } public override bool CanSeek { get { return _innerStream.CanSeek; } } public override bool CanWrite { get { return _innerStream.CanWrite; } } public override long Length { get { return _innerStream.Length; } } public override long Position { get { return _innerStream.Position; } set { _innerStream.Position = value; } } public override int ReadTimeout { get { return _innerStream.ReadTimeout; } set { _innerStream.ReadTimeout = value; } } public override bool CanTimeout { get { return _innerStream.CanTimeout; } } public override int WriteTimeout { get { return _innerStream.WriteTimeout; } set { _innerStream.WriteTimeout = value; } } protected override void Dispose(bool disposing) { if (disposing) { _innerStream.Dispose(); } base.Dispose(disposing); } public override long Seek(long offset, SeekOrigin origin) { return _innerStream.Seek(offset, origin); } public override int Read(byte[] buffer, int offset, int count) { return _innerStream.Read(buffer, offset, count); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return _innerStream.BeginRead(buffer, offset, count, callback, state); } public override int EndRead(IAsyncResult asyncResult) { return _innerStream.EndRead(asyncResult); } public override int ReadByte() { return _innerStream.ReadByte(); } public override void Flush() { _innerStream.Flush(); } public override void SetLength(long value) { _innerStream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { _innerStream.Write(buffer, offset, count); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return _innerStream.BeginWrite(buffer, offset, count, callback, state); } public override void EndWrite(IAsyncResult asyncResult) { _innerStream.EndWrite(asyncResult); } public override void WriteByte(byte value) { _innerStream.WriteByte(value); } }Kiran Challa
Santos
Member
6 Points
5 Posts
Re: Deserialization Issues with ReadAsAsync
Apr 17, 2012 08:45 PM|LINK
Thanks, I'll give it a try!
Regards,
Santos
Santos
Member
6 Points
5 Posts
Re: Deserialization Issues with ReadAsAsync
Apr 17, 2012 08:45 PM|LINK
Thanks, I'll give it a try!
Regards,
Santos
nz-Ben-nz
Member
1 Points
5 Posts
Re: Deserialization Issues with ReadAsAsync
Apr 27, 2012 12:05 AM|LINK
The example posted by Kiran almost works, but not quite. I no longer got stream closed exceptions, however started getting deserialization issues. With the following changes to the Type Formatter I did manage to get the class working for me with no errors.
public class CustomJsonMediaTypeFormatter : JsonMediaTypeFormatter { protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { NonClosingDelegatingStream wrapperStream = new NonClosingDelegatingStream(stream); var task = base.OnReadFromStreamAsync(type, wrapperStream, contentHeaders, formatterContext); task.ContinueWith(t => { stream.Position = 0; }); return task; } }Also note that the formatters need to be defined in the initial HttpConfiguration/HttpSelfHostConfiguration class. Simply calling the ReadAsAsync override that allows you to define the MediaTypeFormatters will not work.
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Deserialization Issues with ReadAsAsync
Apr 27, 2012 12:29 AM|LINK
You have to be careful with the logic of resetting the stream's Position back to 0. This is because sometimes the stream provided to the ReadFromStreamAsync method could be a non-rewindable stream.
You should first check if the stream supplied to the ReadFromStreamAsync method is Seekable (you can use the CanSeek property to check) and only then reset the position to 0.
For example, the scenario where you could be supplied a non-rewindable stream is when you set the TransferMode of the HttpSelfHostConfiguration to be Streamed instead of the default one Buffered.
Kiran Challa