Accessing Http headers and body of response using ChannelFactory
-
Friday, March 28, 2008 6:34 PM
I'm calling a RESTful web service from a WCF 3.5 client application using ChannelFactory similar to the code below. I'm wondering how I can access the raw Http headers and body from the response to the MyMethod() call? My need is to get at this data if an exception is thrown but ideally I'd be able to access it anytime after the method call.
using (ChannelFactory<IMyService> factory = new ChannelFactory<IMyService>("MyService"))
{
IMyService proxy = factory.CreateChannel();proxy.MyMethod("requestid");
}
Answers
-
Saturday, March 29, 2008 7:43 PM
The headers of the response are cached, so you can access them after the request is completed (within the scope). The body, however, is not (as it can be quite big). If you want to access the raw bytes of the body, you need to retrieve it on the operation itself. The easiest way to do that is to define a new contract, equivalent to the original one, and use the Raw programming model on the response (setting the operation return type to Stream, and adding a ContentTypeMapper to tell WCF that all the responses are supposed to be treated as raw). The example below shows how to do that.
public class Post3081695b{
[
ServiceContract] public interface ITest{
[
OperationContract] string Echo(string text);}
[
ServiceContract(Name="ITest")] public interface ITest2{
[
OperationContract] Stream Echo(string text);}
public class Service : ITest{
public string Echo(string text){
WebOperationContext.Current.OutgoingResponse.Headers.Add("X-MyHeader", "My header value"); return text;}
}
static Binding GetBinding(){
WebHttpBinding result = new WebHttpBinding(); return result;}
static Binding GetStreamBinding(){
CustomBinding result = new CustomBinding(new WebHttpBinding()); WebMessageEncodingBindingElement webEncoding = result.Elements.Find<WebMessageEncodingBindingElement>();webEncoding.ContentTypeMapper =
new RawMapper(); return result;}
public class RawMapper : WebContentTypeMapper{
public override WebContentFormat GetMessageFormatForContentType(string contentType){
return WebContentFormat.Raw;}
}
public static void Test(){
string baseAddress = "http://" + Environment.MachineName + ":8000/Service"; ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));host.AddServiceEndpoint(
typeof(ITest), GetBinding(), "").Behaviors.Add(new WebHttpBehavior());host.Open();
Console.WriteLine("Host opened");ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
Console.WriteLine(proxy.Echo("Hello"));
string myHeader = WebOperationContext.Current.IncomingResponse.Headers["X-MyHeader"];
Console.WriteLine("X-MyHeader: {0}", myHeader);
}
((IClientChannel)proxy).Close();
factory.Close();
ChannelFactory<ITest2> factory2 = new ChannelFactory<ITest2>(GetStreamBinding(), new EndpointAddress(baseAddress));
factory2.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest2 proxy2 = factory2.CreateChannel();
Stream body = proxy2.Echo("Hello");
int b;
Console.Write("Body bytes: ");
while ((b = body.ReadByte()) > 0)
{
Console.Write("{0:X2} ", b);
}
Console.WriteLine();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
All Replies
-
Friday, March 28, 2008 8:38 PM
You can use IClientMessageInspector to inspect or modify messages as they pass through a WCF client object:
http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector.aspx
-
Friday, March 28, 2008 8:41 PM
You can use the WebOperationContext to access this information:
public class Post3081695{
[
ServiceContract] public interface ITest{
[
OperationContract] string Echo(string text);}
public class Service : ITest{
public string Echo(string text){
WebOperationContext.Current.OutgoingResponse.Headers.Add("X-MyHeader", "My header value"); return text;}
}
static Binding GetBinding(){
WebHttpBinding result = new WebHttpBinding(); return result;}
public static void Test(){
string baseAddress = "http://" + Environment.MachineName + ":8000/Service"; ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));host.AddServiceEndpoint(
typeof(ITest), GetBinding(), "").Behaviors.Add(new WebHttpBehavior());host.Open();
Console.WriteLine("Host opened");ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
Console.WriteLine(proxy.Echo("Hello"));
string myHeader = WebOperationContext.Current.IncomingResponse.Headers["X-MyHeader"];
Console.WriteLine("X-MyHeader: {0}", myHeader);
}
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
-
Saturday, March 29, 2008 12:12 AM
It appears that the key to your sample is the snippet below. Can you explain the significance of the OperationContextScope? I only seem to be able to access the web operation context within that block.
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
Console.WriteLine(proxy.Echo("Hello"));
string myHeader = WebOperationContext.Current.IncomingResponse.Headers["X-MyHeader"];
Console.WriteLine("X-MyHeader: {0}", myHeader);
}
-
Saturday, March 29, 2008 4:41 AMWhen you're within the context of a service operation, the property WebOperationContext.Current (or OperationContext.Current) are set (as WCF "knows" that you're in the context of that operation. In the client side, since there's no "current" operation going on, that property value is null. The OperationContextScope class (http://msdn2.microsoft.com/en-us/library/system.servicemodel.operationcontextscope.aspx) creates one context for you, which is used for the operations which happen while the context is in scope. That allows, for example, for you to add an additional header to the request of a REST operation (at which point no operation has been called yet), or check the headers which occur in the response.
-
Saturday, March 29, 2008 5:17 AM
Thank you Carlos.
One last thing. I am able to access the headers with your help but I do not see any way to get at the body of the http response. Is there some other way to get at that data?
-
Saturday, March 29, 2008 7:43 PM
The headers of the response are cached, so you can access them after the request is completed (within the scope). The body, however, is not (as it can be quite big). If you want to access the raw bytes of the body, you need to retrieve it on the operation itself. The easiest way to do that is to define a new contract, equivalent to the original one, and use the Raw programming model on the response (setting the operation return type to Stream, and adding a ContentTypeMapper to tell WCF that all the responses are supposed to be treated as raw). The example below shows how to do that.
public class Post3081695b{
[
ServiceContract] public interface ITest{
[
OperationContract] string Echo(string text);}
[
ServiceContract(Name="ITest")] public interface ITest2{
[
OperationContract] Stream Echo(string text);}
public class Service : ITest{
public string Echo(string text){
WebOperationContext.Current.OutgoingResponse.Headers.Add("X-MyHeader", "My header value"); return text;}
}
static Binding GetBinding(){
WebHttpBinding result = new WebHttpBinding(); return result;}
static Binding GetStreamBinding(){
CustomBinding result = new CustomBinding(new WebHttpBinding()); WebMessageEncodingBindingElement webEncoding = result.Elements.Find<WebMessageEncodingBindingElement>();webEncoding.ContentTypeMapper =
new RawMapper(); return result;}
public class RawMapper : WebContentTypeMapper{
public override WebContentFormat GetMessageFormatForContentType(string contentType){
return WebContentFormat.Raw;}
}
public static void Test(){
string baseAddress = "http://" + Environment.MachineName + ":8000/Service"; ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));host.AddServiceEndpoint(
typeof(ITest), GetBinding(), "").Behaviors.Add(new WebHttpBehavior());host.Open();
Console.WriteLine("Host opened");ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
Console.WriteLine(proxy.Echo("Hello"));
string myHeader = WebOperationContext.Current.IncomingResponse.Headers["X-MyHeader"];
Console.WriteLine("X-MyHeader: {0}", myHeader);
}
((IClientChannel)proxy).Close();
factory.Close();
ChannelFactory<ITest2> factory2 = new ChannelFactory<ITest2>(GetStreamBinding(), new EndpointAddress(baseAddress));
factory2.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest2 proxy2 = factory2.CreateChannel();
Stream body = proxy2.Echo("Hello");
int b;
Console.Write("Body bytes: ");
while ((b = body.ReadByte()) > 0)
{
Console.Write("{0:X2} ", b);
}
Console.WriteLine();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

