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
Comments
Anonymous
February 26, 2008
In this example, the server and the client is in the same process (same thread as well??). Do you have any suggestions around how to pass client information such as CultureInfo and timezone to a distributed service? Are supporting tokens appropriate for this purpose?Anonymous
February 29, 2008
Hi Rory, There are a couple places that information could go. It could be in supporting tokens for the user identity. It could be an optional message header. It could be part of the context of a session (such as a cookie). It could be built into your application protocol. You could have the server provide fixed options and make the client present the data appropriately localized. I favor the last approach for things like time zones that don't require translation.