WCF December CTP: Header-stuffing from the client and back.

Because there have been a few changes along the path of the product, I thought I'd make it clear how OperationContext works with respect to adding and reading out-of-band headers in the messages using headers that do not appear in the service contract itself. Let's start with the various pieces and then I'll post more complete code.

Client first, because reading or attaching headers on the service seems more straightforward to me.

 Create a new OperationContextScope and do your proxy work within it if you want to access the context for the messages flowing through that proxy.

using (OperationContextScope scope = new OperationContextScope
{
using (SampleServiceProxy proxy = new SampleServiceProxy())
{ ... }
}

Then you get to use the current OperationContext to add your message header to -- don't create a new context. The new OperationContextScope did that for you. Like so:

using (OperationContextScope scope = new OperationContextScope())
{
// This code uses duplex communication, hence the passed InstanceContext.
using (SampleServiceProxy proxy = new SampleServiceProxy(new InstanceContext(this)))
{
try
{
MessageHeader header
= MessageHeader.CreateHeader(
"Service-Bound-RequestReplyHeader",
"http://Microsoft.WCF.Documentation",
"Custom Happy Value."
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);

Note: Recall that if you receive the following error using duplex over http, that you should adopt one of the strategies mentioned by Kenny here. I turn IIS off for a while. :-)

"There was a communication problem. HTTP could not register URL http://+:80/Temporary_Listen_Addresses/354173be-86af-4fdb-a5c2-606fb848d782/ because TCP port 80 is being used by another application."

Back to stuff. Here's the client code I used to test this all out. It uses duplex communication (to demonstrate one-way using a client instead of two services), but it all works the same.

Client:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;

public class Client : ISampleServiceCallback
{
ManualResetEvent wait = null;

Client()
{
this.wait = new ManualResetEvent(false);
}

public static void Main()
{
Client client = new Client();
client.Run();
}

void Run()
{
using (OperationContextScope scope = new OperationContextScope())
{
// Picks up configuration from the config file.
using (SampleServiceProxy proxy = new SampleServiceProxy(new InstanceContext(this)))
{
try
{
MessageHeader header
= MessageHeader.CreateHeader(
"Service-Bound-RequestReplyHeader",
"http://Microsoft.WCF.Documentation",
"Custom Happy Value."
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);

// Making calls.
Console.WriteLine("Enter the greeting to send: ");
string greeting = Console.ReadLine();

// Request-Reply
Console.WriteLine(proxy.RequestReply(greeting));
this.WriteHeaders(OperationContext.Current.IncomingMessageHeaders);

header = MessageHeader.CreateHeader(
"Service-Bound-OneWayHeader",
"http://Microsoft.WCF.Documentation",
"Different Happy Value."
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);

// One-way
proxy.Push(greeting);
this.wait.WaitOne();

// Done with service.
proxy.Close();
Console.WriteLine("Done!");
Console.ReadLine();
}
catch (TimeoutException timeProblem)
{
Console.WriteLine("The service operation timed out. " + timeProblem.Message);
}
catch(CommunicationException commProblem)
{
Console.WriteLine("There was a communication problem. " + commProblem.Message);
Console.ReadLine();
}
}
}
}

#region ISampleServiceCallback Members

public void PushBack(string msg)
{
Console.WriteLine("Service said: " + msg);
this.WriteHeaders(OperationContext.Current.IncomingMessageHeaders);
this.wait.Set();
}

void WriteHeaders(MessageHeaders headers)
{
foreach (IMessageHeaderInfo h in headers)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("IncomingHeader:");
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("\t" + h.Actor);
Console.WriteLine("\t" + h.Name);
Console.WriteLine("\t" + h.Namespace);
Console.WriteLine("\t" + h.Relay);
Console.ResetColor();
}
}
#endregion
}

and the service:

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;

namespace Microsoft.WCF.Documentation
{
[ServiceContract(
Namespace="http://Microsoft.WCF.Documentation",
CallbackContract=typeof(IClientCallbackContract),
Session=true
)]
public interface ISampleService{
[OperationContract(IsOneWay=true)]
void Push(string msg);
[OperationContract]
string RequestReply(string message);
}

interface IClientCallbackContract
{
[OperationContract(IsOneWay = true)]
void PushBack(string msg);
}

class SampleService : ISampleService
{
#region ISampleService Members

public string RequestReply(string message)
{
Console.WriteLine("Proxy: " + message);
MessageHeaders incomingHeaders = OperationContext.Current.IncomingMessageHeaders;
this.WriteHeaders(OperationContext.Current.IncomingMessageHeaders);

MessageHeader outBoundHeader
= MessageHeader.CreateHeader("Client-Bound-Reply-Header", "http://Microsoft.WCF.Documentation", "Custom Outbound Header");
OperationContext.Current.OutgoingMessageHeaders.Add(outBoundHeader);
Console.WriteLine("OutgoingHeader: ");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(outBoundHeader.ToString());
Console.ResetColor();
return "Received your greeting.";
}

public void Push(string msg)
{
Console.WriteLine("Proxy: " + msg);
this.WriteHeaders(OperationContext.Current.IncomingMessageHeaders);
MessageHeader outBoundHeader
= MessageHeader.CreateHeader(
"Client-Bound-One-Way-Header",
"http://Microsoft.WCF.Documentation",
"Custom Outbound Header"
);
OperationContext.Current.OutgoingMessageHeaders.Add(outBoundHeader);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("OutgoingHeader:");
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(outBoundHeader.ToString());
Console.ResetColor();
OperationContext.Current.GetCallbackChannel<IClientCallbackContract>().PushBack("Here's something to examine in response.");
}

void WriteHeaders(MessageHeaders headers)
{
foreach (IMessageHeaderInfo h in headers)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("IncomingHeader:");
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("\t" + h.Actor);
Console.WriteLine("\t" + h.Name);
Console.WriteLine("\t" + h.Namespace);
Console.WriteLine("\t" + h.Relay);
Console.ResetColor();
}
}

#endregion
}
}

Hope this helps someone. It helps me write the documentation, that's for sure.