Reading the HttpResponseMessage Content

Topics: Web Api
Oct 13, 2011 at 11:54 PM
Edited Oct 13, 2011 at 11:58 PM

I'm fooling with some DelegatingHandler code inside the SendAsync().ContinueWith() task. I'm having a hard time trying to read the content of the HttpResponseMessage. The Type is ObjectContent<class> and ReadAs throws an error when doing

typeof(objectContent<class>) or typeof(class);

I am able to set a new content object but i'd like to use the existing object to drive some modifications.

Whats the proper way to take the object in the HttpResponseMessage.Content and turn it into an object?

Coordinator
Oct 14, 2011 at 12:02 AM

We provide a set of ReadAsXxx extension methods that hopefully make this pretty simple:

reponse.Content.ReadAs<myobj>();

Daniel Roth

From: stevegourley [email removed]
Sent: Thursday, October 13, 2011 4:54 PM
To: Daniel Roth
Subject: Reading the HttpResponseMessage Content [wcf:275839]

From: stevegourley

I'm fooling with some DelegatingHandler code inside the SendAsync().ContinueWith() task. I'm having a hard time trying to read the content of the HttpResponseMessage. The Type is ObjectContent<myObj> and ReadAs throws an error when doing typeof(objectContent<myobj>) or typeof(myobj);

Whats the proper way to take the object in the HttpResponseMessage.Content and turn it into an object?

Oct 14, 2011 at 12:32 AM
Edited Oct 14, 2011 at 12:51 AM

Thanks for your reply but that is throwing an error.

No 'MediaTypeFormatter' is available to read an object of type 'Address' with the media type ''undefined''.

Guess I need to make a MediaTypeFormatter for my Address class.

Updated

So I made a formatter and the line below also errors and the formatter is never called.

var t = httpResponseMessage.Content.ReadAs<Address>(new[] { new AddressMediaTypeFormatter() });

Oct 14, 2011 at 2:01 AM

You need to have the Content-Type header set in your response and you need to have added that media type to the SupportedMediaTypes collection in the constructor of your formatter.  Once that is done, hopefully, the default XML/JSON formatters won't override your formatter.

Oct 14, 2011 at 5:56 AM

Hi Darrel,

I am afraid that your workaround might not work. This seems to be a bug in Preview 5.

However, in our latest sources, this problem has been fixed and you will be able to do the following with these fixed bits:

public class ResponseContentModificationHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
            {
                HttpResponseMessage response = responseToCompleteTask.Result;

                // get original data set at service operation
                Order order1 = response.Content.ReadAs<Order>();

                // update this original data here
                order1.OrderValue = order1.OrderValue + 100.00;

                // response would have the updated data
                return response;
            });
        }
    }
Thanks,

Kiran Challa

Oct 17, 2011 at 4:30 PM

Any idea when the next preview will be out with these changes or should I just compile my own dlls from your source?

kiranchalla wrote:

Hi Darrel,

I am afraid that your workaround might not work. This seems to be a bug in Preview 5.

However, in our latest sources, this problem has been fixed and you will be able to do the following with these fixed bits:

public class ResponseContentModificationHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
            {
                HttpResponseMessage response = responseToCompleteTask.Result;

                // get original data set at service operation
                Order order1 = response.Content.ReadAs<Order>();

                // update this original data here
                order1.OrderValue = order1.OrderValue + 100.00;

                // response would have the updated data
                return response;
            });
        }
    }
Thanks,

Kiran Challa

Oct 17, 2011 at 4:47 PM

Hi Steve,

I am not sure about the next preview date. But, can you please let me know what is the scenario that you are trying to achieve? Actually there are 2 options that you can use to intercept an outbound response message and modify the content. One is using Response operation handlers and the other is using Message Handlers...since the second option is currently having a bug in Preview 5, I am just wondering if you can use the first option(response operation handlers) for your purpose?...

thanks, Kiran Challa

Oct 17, 2011 at 5:04 PM
Edited Oct 17, 2011 at 5:06 PM

I actually had a conversation with Glen about what the best way to accomplish what I was trying to do and this was one of the options to try. I'll repeat the scenario though so you and I can continue the discussion a little more thoroughly.

what I am interested in doing is modifying the output response in a Russian doll manner.  Our primary transport format would be json but there would be querystring properties to modify the output of that json. Someone might request GeoJson, esriJson, etc. They could also throw on a property to make the json print human readable, more commonly known as pretty print, or jsonp etc.

My thought would be to follow how you guys implemented the jsonp in the prototype branch, add custom headers that can be acted upon later.  I'd have a WCF model for the output. Order the output response handlers so that jsonp and pretty printing would happen last. Turn the model into the desired format of json, then decorate that json with whatever other querystring params were present.

It seems to me a bit like a decorator pattern. I wasn't able to do this with message formatters since you write to the stream with one and the others don't get executed.

I'm interested to hear what your approach would be.

kiranchalla wrote:

Hi Steve,

I am not sure about the next preview date. But, can you please let me know what is the scenario that you are trying to achieve? Actually there are 2 options that you can use to intercept an outbound response message and modify the content. One is using Response operation handlers and the other is using Message Handlers...since the second option is currently having a bug in Preview 5, I am just wondering if you can use the first option(response operation handlers) for your purpose?...

thanks, Kiran Challa

Oct 17, 2011 at 5:43 PM

Thanks for the detailed explaination Steve...My 2 cents:

So your default format is Json, but that format could change based on the query string values.

Yeah, what you described with building a pipeline of http response handlers in a particular sequence might solve your problem...

But, I feel that what you are trying to achieve is essentially a formatting experience (GeoJson, esriJson, pretty printing etc)...i am thinking that this kind of logic should be part of a formatter, rather than in the upper layers...

Can you put this formatting logic in to a custom formatter (derived probably from the out of box JsonMediaTypeFormatter) and inside the "OnWriteToStream" method, you can look for HttpContentHeaders (headers with flags from query string values) to see what kind of format you would like to build...you could probably have a decorator pipeline in this "OnWriteToStream" method...

this is just my thought...i am not a real expert in WebAPI :-)

Thanks,
Kiran Challa

Oct 17, 2011 at 5:58 PM

Yes, I could probably jam everything into one formatter. I was just thinking it would be nice to separate the files for organizational purposes and single responsibility-ness.

Oct 17, 2011 at 6:02 PM

Just realized something...also, having logic outside the formatter and in operation handlers would not be so useful from the client's perspective because operation handlers is a server side concept only...if we could design the formatter with a pipeline that you are trying to achieve in a way which could be used on the service and client side, to have the wrapping and unwrapping experience...

Thanks, Kiran Challa

Coordinator
Oct 17, 2011 at 6:09 PM
Edited Oct 17, 2011 at 6:13 PM

We had a discussion on the team on this. There a bunch of agreement around using a custom derived HttpContent that encodes into JsonP would be reasonable. In the approach you are describing, JsonP is really more of a separate concern / aspect than a specific formatter. You want to reuse different formatters (JsonP, GeocodedJson, etc) and in some case have them wrapped in JsonP.

You can derive from HttpContent and create a JsonPContent that will decorate a standard HttpContent. That content class will take the stream coming from the inner content and wrap it in JsonP. Using a message handler it would be easy to wire up. The message handler looks at the request uri to see if there is a callback, if there is it just wraps the returned content into the JsonPContent and sets that content onto the response message.

When you derive your custom content, I would make it derive from ActionOfStreamContent as it supports writing to the raw stream rather than buffering.

Thanks

Glenn

Coordinator
Oct 17, 2011 at 6:27 PM

To address Kiran's point, if you use a custom HttpContent class you can use it on the the client as well if you want to parse a JsonP response. In that pattern you would construct a JsonPContent and pass it the HttpContent from the response you received via HttpClient into the ctor. You could also pass it the formatters like Json, GeocodedJson etc. It would then internally unwrap the stream coming from the inner content (remove the JsonP wrapping) and then just allow the unwrapped result ot go to the formatters on the client.

Oct 18, 2011 at 1:00 AM
gblock wrote:

We had a discussion on the team on this. There a bunch of agreement around using a custom derived HttpContent that encodes into JsonP would be reasonable. In the approach you are describing, JsonP is really more of a separate concern / aspect than a specific formatter. You want to reuse different formatters (JsonP, GeocodedJson, etc) and in some case have them wrapped in JsonP.

You can derive from HttpContent and create a JsonPContent that will decorate a standard HttpContent. That content class will take the stream coming from the inner content and wrap it in JsonP. Using a message handler it would be easy to wire up. The message handler looks at the request uri to see if there is a callback, if there is it just wraps the returned content into the JsonPContent and sets that content onto the response message.

When you derive your custom content, I would make it derive from ActionOfStreamContent as it supports writing to the raw stream rather than buffering.

Thanks

Glenn

Thanks Glenn,

ActionOfStreamContent is internal so I can't derive from it. I'll just derive from HttpContent or ObjectContent

Oct 18, 2011 at 1:03 AM
kiranchalla wrote:

Just realized something...also, having logic outside the formatter and in operation handlers would not be so useful from the client's perspective because operation handlers is a server side concept only...if we could design the formatter with a pipeline that you are trying to achieve in a way which could be used on the service and client side, to have the wrapping and unwrapping experience...

Thanks, Kiran Challa

Thanks Kiran,

Our main client currently will be a web browser so I'm not too worried about the client side unwrapping but that's definitely something to keep in mind.

Coordinator
Oct 18, 2011 at 1:35 AM
I believe we made it public in preview 5, did you check

Sent from my Windows Phone

From: stevegourley
Sent: 10/17/2011 6:04 PM
To: Glenn Block
Subject: Re: Reading the HttpResponseMessage Content [wcf:275839]

From: stevegourley

gblock wrote:

We had a discussion on the team on this. There a bunch of agreement around using a custom derived HttpContent that encodes into JsonP would be reasonable. In the approach you are describing, JsonP is really more of a separate concern / aspect than a specific formatter. You want to reuse different formatters (JsonP, GeocodedJson, etc) and in some case have them wrapped in JsonP.

You can derive from HttpContent and create a JsonPContent that will decorate a standard HttpContent. That content class will take the stream coming from the inner content and wrap it in JsonP. Using a message handler it would be easy to wire up. The message handler looks at the request uri to see if there is a callback, if there is it just wraps the returned content into the JsonPContent and sets that content onto the response message.

When you derive your custom content, I would make it derive from ActionOfStreamContent as it supports writing to the raw stream rather than buffering.

Thanks

Glenn

Thanks Glenn,

ActionOfStreamContent is internal so I can't derive from it. I'll just derive from HttpContent or ObjectContent

Oct 18, 2011 at 1:36 AM

pretty sure i have preview 5 source and it's internal.

Coordinator
Oct 18, 2011 at 3:33 AM

Just checked and you are right. I am surprised as you are as I thought we had opened it up. I will talk to the team to see if we can get it opened. 

Oct 18, 2011 at 11:58 PM
Edited Oct 19, 2011 at 12:20 AM
nevermind
Coordinator
Oct 19, 2011 at 4:00 PM

WebApiConfiguration and MapServiceRoute are in the prototype branch with the rest of the Web API enhancements. You will want to clone the repro and update to the prototype branch.

Daniel Roth

From: stevegourley [email removed]
Sent: Tuesday, October 18, 2011 4:58 PM
To: Daniel Roth
Subject: Re: Reading the HttpResponseMessage Content [wcf:275839]

From: stevegourley

Kiran, I dl'd the source from https://hg01.codeplex.com/wcf. Are these the latest sources you mention? Also I can't find the namespace that contains WebApiConfiguration and RouteTable.Routes.MapServiceRoute. Are those in the web api enhancements? Advice on what source to checkout and build to make this work would be awesome!!

kiranchalla wrote:

Hi Darrel,

I am afraid that your workaround might not work. This seems to be a bug in Preview 5.

However, in our latest sources, this problem has been fixed and you will be able to do the following with these fixed bits:

public class ResponseContentModificationHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
            {
                HttpResponseMessage response = responseToCompleteTask.Result;
 
                // get original data set at service operation
                Order order1 = response.Content.ReadAs<Order>();
 
                // update this original data here
                order1.OrderValue = order1.OrderValue + 100.00;
 
                // response would have the updated data
                return response;
            });
        }
    }

Thanks,

Kiran Challa

Oct 20, 2011 at 1:54 AM

Hey Glenn,

Is this what you had in mind?

 public class JsonpContent : HttpContent
    {

        private string outputResponse;
        private string callback;

        public JsonpContent(string content, string callback)
        {
            this.outputResponse = content;
            this.callback = callback;
        }

        protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext context)
        {
            var writer = new StreamWriter(stream);
            
            writer.Write(string.Format("{0}({1});", callback, outputResponse));
            
            writer.Flush();
        }

        protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context)
        {
            throw new NotImplementedException();
        }

        protected override bool TryComputeLength(out long length)
        {
            throw new NotImplementedException();
        }
    }

Coordinator
Oct 20, 2011 at 2:59 AM
On first glance, yes :-)

Sent from my Windows Phone

From: stevegourley
Sent: 10/19/2011 6:56 PM
To: Glenn Block
Subject: Re: Reading the HttpResponseMessage Content [wcf:275839]

From: stevegourley

Hey Glenn,

Is this what you had in mind?

 public class JsonpContent : HttpContent
    {

        private string outputResponse;
        private string callback;

        public JsonpContent(string content, string callback)
        {
            this.outputResponse = content;
            this.callback = callback;
        }

        protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext context)
        {
            var writer = new StreamWriter(stream);
            
            writer.Write(string.Format("{0}({1});", callback, outputResponse));
            
            writer.Flush();
        }

        protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context)
        {
            throw new NotImplementedException();
        }

        protected override bool TryComputeLength(out long length)
        {
            throw new NotImplementedException();
        }
    }

Oct 20, 2011 at 4:42 PM

yes :)

Btw, the initial problem of ObjectContent not setting the Content-Type in time is a known issue and we have been evaluating various ways of fixing it so that it works well with other HttpContent implementations. We will likely switch to a model where the content negotiation is set upon construction so that we always have a consistent set of Content-* headers.

Dec 1, 2011 at 12:35 AM

Hi, I'm revisiting this with preview 6. I do not see ActionOfStreamContent anymore on httpContent. Was that replaced? I'm having issues in preview 6 of using a delegating handler to push out modified content.

 

httpResponse.Content = new StringContent(JsonConvert.SerializeObject(feature));
Says it's already been disposed.