Using Call Context Initializers for Culture

Let's build on a few earlier samples to actually demonstrate a working call context initializer. I'll start with yesterday's skeleton for a call context initializer and behavior. To that skeleton I'll add implementations of BeforeInvoke and AfterInvoke that initialize the operation thread with custom culture information and then clean the thread up once the operation return.

 class CultureInitializer : ICallContextInitializer
{
   CultureInfo newInfo;

   public CultureInitializer(CultureInfo newInfo)
   {
      this.newInfo = newInfo;
   }

   public void AfterInvoke(object correlationState)
   {
      Thread.CurrentThread.CurrentCulture = correlationState as CultureInfo;
   }

   public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
   {
      CultureInfo oldInfo = Thread.CurrentThread.CurrentCulture;
      Thread.CurrentThread.CurrentCulture = newInfo;
      return oldInfo;
   }
}

Then, I'll fill out ApplyDispatchBehavior to apply my call context initializer to every operation on the given endpoint.

 class CultureInitializerBehavior : IEndpointBehavior
{
   CultureInfo newInfo;

   public CultureInitializerBehavior(CultureInfo newInfo)
   {
      this.newInfo = newInfo;
   }

   public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
   {
   }

   public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
   {
   }

   public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
   {
      foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
      {
         operation.CallContextInitializers.Add(new CultureInitializer(newInfo));
      }
   }

   public void Validate(ServiceEndpoint endpoint)
   {
   }
}

Finally, I'll take last week's custom fault encoding sample and install my behavior. Notice that the only change at the service level is to add the behavior to the endpoint behavior collection. If I had written the code to apply the behavior in an attribute or configuration file, either of those approaches would have worked as well.

 [ServiceContract]
public interface IMyService
{
   [OperationContract]
   Message Fail();
}

public class MyService : IMyService
{
   public Message Fail()
   {
      XmlDocument document = new XmlDocument();
      document.LoadXml("<tag attributeName=\"value\"><moretags>blah</moretags></tag>");
      throw new FaultException<XmlElement>(document.FirstChild as XmlElement);
   }
}

public class Program
{
   static void Main(string[] args)
   {
      string address = "https://localhost:8000/";
      BasicHttpBinding binding = new BasicHttpBinding();
      ServiceHost host = new ServiceHost(typeof(MyService), new Uri(address));
      ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IMyService), binding, "");
      endpoint.Behaviors.Add(new CultureInitializerBehavior(new CultureInfo("en-GB")));
      host.Open();
      ChannelFactory<IMyService> factory = new ChannelFactory<IMyService>(binding);
      IMyService proxy = factory.CreateChannel(new EndpointAddress(address));
      Message response = proxy.Fail();
      Console.WriteLine(response.ToString());
      Console.ReadLine();
      host.Close();
   }
}

Now when I run the full sample notice that the xml:lang changes in the fault reason because the fault reason is a localizable field and based by default on the current culture.

 <s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body>
    <s:Fault>
      <faultcode>s:Client</faultcode>
      <faultstring xml:lang="en-GB">The creator of this fault did not specify a Reason.</faultstring>
      <detail>
        <tag attributeName="value">
          <moretags>blah</moretags>
        </tag>
      </detail>
    </s:Fault>
  </s:Body>
</s:Envelope>

Next time: Runtime Limits in IIS