WCF Extensibility – Message Formatters
This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page .
Message formatters are the component which do the translation between CLR operations and the WCF Message object – their role is to convert all the operation parameters and return values (possibly via serialization) into a Message on output, and deconstruct the message into parameter and return values on input. Anytime a new format is to be used (be it a new serialization format for currently supported CLR types, or supporting a new CLR type altogether), a message formatter is the interface you’d implement to enable this scenario. For example, even though the type System.IO.Stream is not serializable (it’s even abstract), WCF allows it to be used as the input or return values for methods (for example, in the WCF REST Raw programming model) and a formatter deals with converting it into messages. Like the message inspectors, there are two versions, one for the server side (IDispatchMessageFormatter), and one for the client side (IClientMessageFormatter).
Among the extensibility points listed in this series, the message formatters are the first kind to be required in the WCF pipeline – a service does not need to have any service / endpoint / contract / operation behavior, nor any message / parameter inspector. If the user doesn’t add any of those, the client / service will just work (some behaviors are automatically added to the operations when one adds an endpoint, but that’s an implementation detail for WCF – they aren’t strictly necessary). Formatters, on the other hand, are required (to bridge the gap between the message world and the operations using CLR types). If we don’t add any behaviors to the service / endpoint / contract / operation that sets a formatter, WCF will add one formatter to the operation (usually via the DataContractSerializerOperationBehavior, which is added by default in all operation contracts).
Public implementations in WCF
None. As with most of the runtime extensibility points, there are no public implementations of either the dispatch or the client message formatter. There are a lot of internal implementations, though, such as a formatter which converts operation parameters into a message (both using the DataContractSerializer and the XmlSerializer), formatters which map parameters to the HTTP URI (used in REST operations with UriTemplate), among others.
Interface declaration
- public interface IDispatchMessageFormatter
- {
- void DeserializeRequest(Message message, object[] parameters);
- Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result);
- }
- public interface IClientMessageFormatter
- {
- Message SerializeRequest(MessageVersion messageVersion, object[] parameters);
- object DeserializeReply(Message message, object[] parameters);
- }
The formatter interfaces each have two methods, one to convert between input outgoing CLR objects into the Message object (serialize), and one to take the incoming message object apart and break it down into incoming CLR objects (deserialize).
At the server side, after the the message was decoded, passed through the channel stack and other extensibility points and is about to be dispatched to the service operation, the method DeserializeRequest is called to deserialize the message object into an array of parameters to be passed to the operation. The object argument “parameters” will have been allocated, so the implementation of the formatter only needs to read the message and set the operation parameters in their correct order in the array passed to it. After the operation is invoked (and any parameter inspector has a chance to change the operation outputs), SerializeReply is called, to take the operation return, plus the final value of any out/ref parameters which the operation may have, and package them into a Message object to be sent down the WCF channel stack.
At the client side the mirror happens: when the operation is called (and after the parameter inspector has a chance to change the inputs), SerializeRequest is called to package all the input parameters into the outgoing message object. After the message is sent to the server and the response is decoded by the client’s encoder, DeserializeReply is called to open the message object and populate the return value and any possible out/ref parameters.
One small parenthesis about out/ref parameters: all parameters in WCF calls are passed by value – a copy of the value is sent across the wire. If an operation declares a parameter with a ref modifier (ByRef in Visual Basic), the service will receive a copy (i.e., by value) of that parameter, and then return another copy of the value of the parameter at the end of the method. This second copy will be deserialized at the client into a new instance of that type. So even if you pass an instance of your type by reference to a WCF service, and the service doesn’t change it at all, the value at the end of the call at the client side the reference will point to a different object.
- [ServiceContract]
- public interface ITest
- {
- [OperationContract]
- void UpdatePerson(ref Person person);
- [OperationContract]
- void InOutRef(int x, ref int y, out int z, out int w);
- }
- public class Client
- {
- public static void Main()
- {
- ChannelFactory<ITest> factory = new ChannelFactory<ITest>();
- ITest proxy = factory.CreateChannel();
- Person p1 = new Person();
- Person p2 = p1;
- Console.WriteLine(object.ReferenceEquals(p1, p2)); // true
- proxy.UpdatePerson(ref p2);
- Console.WriteLine(object.ReferenceEquals(p1, p2)); // false, even if service doesn't change reference
- }
- }
Going back to the formatters, the list of “input” parameters (those in the *Request) operations are either the input (ByVal) parameters or reference (ByRef without <Out>). The list of “output” parameters are ones marked with ref (ByRef) and out (<Out> ByRef). In the operation InOutRef above, the parameters x and y are considered to be input parameters, so the parameters array in the *Request operations will have two objects). The parameters y, z and w are considered to be output parameters, so the parameters array in the *Reply operations will have three elements.
How to add message formatters
At the server side: the formatter is a property of the operation runtime, so at the server side it’s available at the DispatchOperation object. This object is typically accessed via an operation behavior, or by traversing the operation list from an endpoint behavior. both in the ApplyDispatchBehavior method. Another way is if you’re creating a REST endpoints (i.e., one with a behavior which inherits from WebHttpBehavior). WebHttpBehavior exposes two protected methods, GetRequestDispatchFormatter and GetReplyDispatchFormatter, which can be overridden to to return the formatter to be used by the operation.
- public class MyDispatchMessageFormatter : IDispatchMessageFormatter
- {
- OperationDescription operation;
- public MyDispatchMessageFormatter(OperationDescription operation) { ... }
- }
- public class MyRestBehavior : WebHttpBehavior
- {
- protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
- {
- return new MyDispatchMessageFormatter(operationDescription);
- }
- }
At the client side: Similarly, the operation formatter at the client side is available at the ClientOperation object, typically accessed via an operation or endpoint behavior. And for behaviors inheriting from WebHttpBehavior, there are two protected methods, GetRequestClientFormatter and GetReplyClientFormatter, which can be used to return the operation formatter as well.
- public class MyClientFormatter : IClientMessageFormatter
- {
- }
- public class MyOperationBehavior : IOperationBehavior
- {
- public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
- {
- clientOperation.Formatter = new MyClientFormatter();
- }
- }
A quick note about wrapping the default WCF formatter. As I mentioned before, the default formatter is usually added by the DataContractSerializerOperationBehavior when it’s executed. If we want to use that default formatter, and wrap it in our own formatter, we need to make sure that the DataContractSerializerOperationBehavior is a position in the operation behavior list which is executed before our own operation behavior is. Take this seemingly problem-free example:
- [ServiceContract]
- public interface ITest
- {
- [OperationContract]
- [MyOperationBehavior]
- int Add(int x, int y);
- }
- public class Service : ITest
- {
- public int Add(int x, int y) { return x + y; }
- }
- public class MyClientFormatter : IClientMessageFormatter
- {
- IClientMessageFormatter inner;
- public MyClientFormatter(IClientMessageFormatter inner)
- {
- if (inner == null) throw new ArgumentNullException("inner");
- this.inner = inner;
- }
- public object DeserializeReply(Message message, object[] parameters)
- {
- return this.inner.DeserializeReply(message, parameters);
- }
- public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
- {
- return this.inner.SerializeRequest(messageVersion, parameters);
- }
- }
- public class MyOperationBehaviorAttribute : Attribute, IOperationBehavior
- {
- public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
- {
- }
- public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
- {
- clientOperation.Formatter = new MyClientFormatter(clientOperation.Formatter);
- }
- public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
- {
- }
- public void Validate(OperationDescription operationDescription)
- {
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
- ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
- host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
- host.Open();
- ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
- foreach (var opBehavior in factory.Endpoint.Contract.Operations[0].Behaviors)
- {
- Console.WriteLine(opBehavior);
- }
- ITest proxy = factory.CreateChannel();
- Console.WriteLine(proxy.Add(3, 4));
- }
- }
The call to CreateChannel in the channel factory will fail – the constructor of MyClientFormatter will throw. That’s because the order of the operation behaviors (printed in the code) is the following:
System.ServiceModel.Dispatcher.OperationInvokerBehavior
MyOperationBehaviorAttribute
System.ServiceModel.OperationBehaviorAttribute
System.ServiceModel.Description.DataContractSerializerOperationBehavior
System.ServiceModel.Description.DataContractSerializerOperationGenerator
I have no idea why attribute-based operation behaviors are added in this position, but the fact is that this doesn’t work. So for this scenario we cannot use attribute-based behaviors, we need to add it “manually”, as shown below.
- [ServiceContract]
- public interface ITest
- {
- [OperationContract]
- //[MyOperationBehavior]
- int Add(int x, int y);
- }
- class Program
- {
- static void Main(string[] args)
- {
- string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
- ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
- ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
- endpoint.Contract.Operations.Find("Add").Behaviors.Add(new MyOperationBehaviorAttribute());
- host.Open();
- ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
- ITest proxy = factory.CreateChannel();
- Console.WriteLine(proxy.Add(3, 4));
- }
- }
After doing that, the code runs and works fine.
Real world scenario: supporting different data and serialization formats
The first version of WCF (.NET Framework 3.0) supported essentially SOAP endpoints – an XML payload following some rules defined in the SOAP specification (you actually could do POX/REST, but it was awfully hard). On the next version (.NET Framework 3.5), the REST programming model was added and that started supporting a whole new set of formats, mostly notably POX (plain-old XML, no SOAP involved), JSON (based on the DataContractJsonSerializer) and a special “raw” mode, where one could declare a single Stream parameter in an operation for receiving arbitrary data, or returning a Stream value for returning arbitrary data. The raw mode can be used to support essentially all data and serialization formats, but that comes at a price that the operation itself has to deal with serializing / deserializing the parameters. Also, there are scenarios (from the forums) where the user needs to expose the same operation in non-REST endpoints (or even non-HTTP, as in the scenario described in the forum post), where the Stream trick won’t work. This example will expand on the answer to the forum question and provide formatters which will use a custom serializer (in this case, the JSON.NET one, available from codeplex at https://json.codeplex.com).
And the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). Also, for simplicity sake it doesn’t have a lot of error handling which a production-level code would, and it isn’t fully integrated with the WCF REST pipeline (i.e., it doesn’t support UriTemplates, it doesn’t integrate with the REST help page, doesn’t support out/ref parameters,, etc.).
First, some data contracts to set up the scenario. The service will process people and their pets. The data contracts are decorated both with WCF serialization attributes (DataContract, DataMember), and with the JSON.NET serialization attributes (JsonObejct, JsonProperty, JsonConverter) so that it can be mapped according to both formats.
- [DataContract]
- [Newtonsoft.Json.JsonObject(MemberSerialization = Newtonsoft.Json.MemberSerialization.OptIn)]
- public class Person
- {
- [DataMember(Order = 1), Newtonsoft.Json.JsonProperty]
- public string FirstName;
- [DataMember(Order = 2), Newtonsoft.Json.JsonProperty]
- public string LastName;
- [DataMember(Order = 3),
- Newtonsoft.Json.JsonProperty,
- Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.IsoDateTimeConverter))]
- public DateTime BirthDate;
- [DataMember(Order = 4), Newtonsoft.Json.JsonProperty]
- public List<Pet> Pets;
- [DataMember(Order = 5), Newtonsoft.Json.JsonProperty]
- public int Id;
- }
- [DataContract, Newtonsoft.Json.JsonObject(MemberSerialization = Newtonsoft.Json.MemberSerialization.OptIn)]
- public class Pet
- {
- [DataMember(Order = 1), Newtonsoft.Json.JsonProperty]
- public string Name;
- [DataMember(Order = 2), Newtonsoft.Json.JsonProperty]
- public string Color;
- [DataMember(Order = 3), Newtonsoft.Json.JsonProperty]
- public string Markings;
- [DataMember(Order = 4), Newtonsoft.Json.JsonProperty]
- public int Id;
- }
Next the interface definition, and the service implementation. It’s not very interesting, but I added operations using both GET and POST methods, and operations with single and multiple parameters (the goal of this sample is to demonstrate the formatter, not a specific service, so I think I can slack off on the service example a little ).
- [ServiceContract]
- public interface ITestService
- {
- [WebGet, OperationContract]
- Person GetPerson();
- [WebInvoke, OperationContract]
- Pet EchoPet(Pet pet);
- [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest), OperationContract]
- int Add(int x, int y);
- }
- public class Service : ITestService
- {
- public Person GetPerson()
- {
- return new Person
- {
- FirstName = "First",
- LastName = "Last",
- BirthDate = new DateTime(1993, 4, 17, 2, 51, 37, 47, DateTimeKind.Local),
- Id = 0,
- Pets = new List<Pet>
- {
- new Pet { Name= "Generic Pet 1", Color = "Beige", Id = 0, Markings = "Some markings" },
- new Pet { Name= "Generic Pet 2", Color = "Gold", Id = 0, Markings = "Other markings" },
- },
- };
- }
- public Pet EchoPet(Pet pet)
- {
- return pet;
- }
- public int Add(int x, int y)
- {
- return x + y;
- }
- }
Now the behavior which will set everything together. Since this is a REST endpoint, instead of writing a new endpoint, I’ll simply inherit from WebHttpBehavior, and override the protected methods to return the client and server formatters. Notice that in some cases when there are no parameters or for GET requests (where the parameters are in the query string, not in the request body), so in those cases I’ll simply reuse the default formatter from the base class. Another logic which is implemented at the behavior is validation – we know what our formatter doesn’t support, so we’ll simply throw while the service (or the client channel) is being opened, giving the user a clear message explaining why the behavior failed the validation (some of the validation code is not in the snippet below, but it can be found in the source code for this post).
- public class NewtonsoftJsonBehavior : WebHttpBehavior
- {
- public override void Validate(ServiceEndpoint endpoint)
- {
- base.Validate(endpoint);
- BindingElementCollection elements = endpoint.Binding.CreateBindingElements();
- WebMessageEncodingBindingElement webEncoder = elements.Find<WebMessageEncodingBindingElement>();
- if (webEncoder == null)
- {
- throw new InvalidOperationException("This behavior must be used in an endpoint with the WebHttpBinding (or a custom binding with the WebMessageEncodingBindingElement).");
- }
- foreach (OperationDescription operation in endpoint.Contract.Operations)
- {
- this.ValidateOperation(operation);
- }
- }
- protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
- {
- if (this.IsGetOperation(operationDescription))
- {
- // no change for GET operations
- return base.GetRequestDispatchFormatter(operationDescription, endpoint);
- }
- if (operationDescription.Messages[0].Body.Parts.Count == 0)
- {
- // nothing in the body, still use the default
- return base.GetRequestDispatchFormatter(operationDescription, endpoint);
- }
- return new NewtonsoftJsonDispatchFormatter(operationDescription, true);
- }
- protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
- {
- if (operationDescription.Messages.Count == 1 || operationDescription.Messages[1].Body.ReturnValue.Type == typeof(void))
- {
- return base.GetReplyDispatchFormatter(operationDescription, endpoint);
- }
- else
- {
- return new NewtonsoftJsonDispatchFormatter(operationDescription, false);
- }
- }
- protected override IClientMessageFormatter GetRequestClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
- {
- if (operationDescription.Behaviors.Find<WebGetAttribute>() != null)
- {
- // no change for GET operations
- return base.GetRequestClientFormatter(operationDescription, endpoint);
- }
- else
- {
- WebInvokeAttribute wia = operationDescription.Behaviors.Find<WebInvokeAttribute>();
- if (wia != null)
- {
- if (wia.Method == "HEAD")
- {
- // essentially a GET operation
- return base.GetRequestClientFormatter(operationDescription, endpoint);
- }
- }
- }
- if (operationDescription.Messages[0].Body.Parts.Count == 0)
- {
- // nothing in the body, still use the default
- return base.GetRequestClientFormatter(operationDescription, endpoint);
- }
- return new NewtonsoftJsonClientFormatter(operationDescription, endpoint);
- }
- protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
- {
- if (operationDescription.Messages.Count == 1 || operationDescription.Messages[1].Body.ReturnValue.Type == typeof(void))
- {
- return base.GetReplyClientFormatter(operationDescription, endpoint);
- }
- else
- {
- return new NewtonsoftJsonClientFormatter(operationDescription, endpoint);
- }
- }
- }
On to the formatters. I’ll start with the dispatch formatter. As the new formatter was only added for POST operations with at least one parameter, we can safely assume that the messages will not be empty, and the request will be stored in the message body. But the message body is always represented in XML, so in order to support a custom format, we need to either define a mapping between that format and XML (which is what was done internally for supporting JSON in 3.5) – and implement XML readers and writers to support this mapping – or reuse one of the existing formats for which we already have a mapping for. And as in the raw programming model, the raw format (described in the post about message inspectors) is a great candidate for it – we can get the raw bytes from the request (and write raw bytes in the response) and parse them using our custom format. We must ensure, however, that the incoming message actually was read with the raw encoder (by default messages with JSON content type are read with the JSON encoder, we need a custom content type mapper to ensure that they are read as raw messages).
- class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter
- {
- OperationDescription operation;
- Dictionary<string, int> parameterNames;
- public NewtonsoftJsonDispatchFormatter(OperationDescription operation, bool isRequest)
- {
- this.operation = operation;
- if (isRequest)
- {
- int operationParameterCount = operation.Messages[0].Body.Parts.Count;
- if (operationParameterCount > 1)
- {
- this.parameterNames = new Dictionary<string, int>();
- for (int i = 0; i < operationParameterCount; i++)
- {
- this.parameterNames.Add(operation.Messages[0].Body.Parts[i].Name, i);
- }
- }
- }
- }
- public void DeserializeRequest(Message message, object[] parameters)
- {
- object bodyFormatProperty;
- if (!message.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatProperty) ||
- (bodyFormatProperty as WebBodyFormatMessageProperty).Format != WebContentFormat.Raw)
- {
- throw new InvalidOperationException("Incoming messages must have a body format of Raw. Is a ContentTypeMapper set on the WebHttpBinding?");
- }
- XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
- bodyReader.ReadStartElement("Binary");
- byte[] rawBody = bodyReader.ReadContentAsBase64();
- MemoryStream ms = new MemoryStream(rawBody);
- StreamReader sr = new StreamReader(ms);
- Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
- if (parameters.Length == 1)
- {
- // single parameter, assuming bare
- parameters[0] = serializer.Deserialize(sr, operation.Messages[0].Body.Parts[0].Type);
- }
- else
- {
- // multiple parameter, needs to be wrapped
- Newtonsoft.Json.JsonReader reader = new Newtonsoft.Json.JsonTextReader(sr);
- reader.Read();
- if (reader.TokenType != Newtonsoft.Json.JsonToken.StartObject)
- {
- throw new InvalidOperationException("Input needs to be wrapped in an object");
- }
- reader.Read();
- while (reader.TokenType == Newtonsoft.Json.JsonToken.PropertyName)
- {
- string parameterName = reader.Value as string;
- reader.Read();
- if (this.parameterNames.ContainsKey(parameterName))
- {
- int parameterIndex = this.parameterNames[parameterName];
- parameters[parameterIndex] = serializer.Deserialize(reader, this.operation.Messages[0].Body.Parts[parameterIndex].Type);
- }
- else
- {
- reader.Skip();
- }
- reader.Read();
- }
- reader.Close();
- }
- sr.Close();
- ms.Close();
- }
- public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
- {
- byte[] body;
- Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
- using (MemoryStream ms = new MemoryStream())
- {
- using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
- {
- using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
- {
- writer.Formatting = Newtonsoft.Json.Formatting.Indented;
- serializer.Serialize(writer, result);
- sw.Flush();
- body = ms.ToArray();
- }
- }
- }
- Message replyMessage = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new RawBodyWriter(body));
- replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
- HttpResponseMessageProperty respProp = new HttpResponseMessageProperty();
- respProp.Headers[HttpResponseHeader.ContentType] = "application/json";
- replyMessage.Properties.Add(HttpResponseMessageProperty.Name, respProp);
- return replyMessage;
- }
- }
When the formatter is serializing the reply of the message , it will create a new message using a “raw” body writer, which is a simple implementation of the BodyWriter class. It will then set the body format property to ensure that the raw encoder will be used when converting that message into bytes on the wire.
- class RawBodyWriter : BodyWriter
- {
- byte[] content;
- public RawBodyWriter(byte[] content)
- : base(true)
- {
- this.content = content;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- writer.WriteStartElement("Binary");
- writer.WriteBase64(content, 0, content.Length);
- writer.WriteEndElement();
- }
- }
The client formatter is similar to the service one. On reply, since it doesn’t support multiple return values (no out/ref support implemented), it simply gets the bytes from the message and feeds them to the JSON.NET deserializer. When serializing the request, the formatter will either simply serialize a single parameter if it’s the only one, or it will wrap it in a JSON object with properties named by the operation parameter name. The last thing it does on serialization is to again set the body format property (to ensure that the raw encoder will be used for that message) and also set the “To” header of the message (which is required for REST messages, since it uses manual addressing on the transport).
- class NewtonsoftJsonClientFormatter : IClientMessageFormatter
- {
- OperationDescription operation;
- Uri operationUri;
- public NewtonsoftJsonClientFormatter(OperationDescription operation, ServiceEndpoint endpoint)
- {
- this.operation = operation;
- string endpointAddress = endpoint.Address.Uri.ToString();
- if (!endpointAddress.EndsWith("/"))
- {
- endpointAddress = endpointAddress + "/";
- }
- this.operationUri = new Uri(endpointAddress + operation.Name);
- }
- public object DeserializeReply(Message message, object[] parameters)
- {
- object bodyFormatProperty;
- if (!message.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatProperty) ||
- (bodyFormatProperty as WebBodyFormatMessageProperty).Format != WebContentFormat.Raw)
- {
- throw new InvalidOperationException("Incoming messages must have a body format of Raw. Is a ContentTypeMapper set on the WebHttpBinding?");
- }
- XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
- Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
- bodyReader.ReadStartElement("Binary");
- byte[] body = bodyReader.ReadContentAsBase64();
- using (MemoryStream ms = new MemoryStream(body))
- {
- using (StreamReader sr = new StreamReader(ms))
- {
- Type returnType = this.operation.Messages[1].Body.ReturnValue.Type;
- return serializer.Deserialize(sr, returnType);
- }
- }
- }
- public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
- {
- byte[] body;
- Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
- using (MemoryStream ms = new MemoryStream())
- {
- using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
- {
- using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
- {
- writer.Formatting = Newtonsoft.Json.Formatting.Indented;
- if (parameters.Length == 1)
- {
- // Single parameter, assuming bare
- serializer.Serialize(sw, parameters[0]);
- }
- else
- {
- writer.WriteStartObject();
- for (int i = 0; i < this.operation.Messages[0].Body.Parts.Count; i++)
- {
- writer.WritePropertyName(this.operation.Messages[0].Body.Parts[i].Name);
- serializer.Serialize(writer, parameters[0]);
- }
- writer.WriteEndObject();
- }
- writer.Flush();
- sw.Flush();
- body = ms.ToArray();
- }
- }
- }
- Message requestMessage = Message.CreateMessage(messageVersion, operation.Messages[0].Action, new RawBodyWriter(body));
- requestMessage.Headers.To = operationUri;
- requestMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
- HttpRequestMessageProperty reqProp = new HttpRequestMessageProperty();
- reqProp.Headers[HttpRequestHeader.ContentType] = "application/json";
- requestMessage.Properties.Add(HttpRequestMessageProperty.Name, reqProp);
- return requestMessage;
- }
- }
Now for testing the formatters. The first thing we need is the content type mapper, to ensure that all incoming requests will be read by the raw encoder. Its implementation is quite simple.
- class MyRawMapper : WebContentTypeMapper
- {
- public override WebContentFormat GetMessageFormatForContentType(string contentType)
- {
- return WebContentFormat.Raw;
- }
- }
Now for the test itself. I’m using a helper method (SendRequest) to send arbitrary HTTP requests to the server, but I’ll skip it here (it’s just setting up HttpWebRequest and displaying the results from HttpWebResponse). The test code sets up a service and adds two endpoints, one “normal” (SOAP) and one JSON.NET enabled
- static void Main(string[] args)
- {
- string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
- ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
- host.AddServiceEndpoint(typeof(ITestService), new BasicHttpBinding(), "soap");
- WebHttpBinding webBinding = new WebHttpBinding();
- webBinding.ContentTypeMapper = new MyRawMapper();
- host.AddServiceEndpoint(typeof(ITestService), webBinding, "json").Behaviors.Add(new NewtonsoftJsonBehavior());
- Console.WriteLine("Opening the host");
- host.Open();
- ChannelFactory<ITestService> factory = new ChannelFactory<ITestService>(new BasicHttpBinding(), new EndpointAddress(baseAddress + "/soap"));
- ITestService proxy = factory.CreateChannel();
- Console.WriteLine(proxy.GetPerson());
- SendRequest(baseAddress + "/json/GetPerson", "GET", null, null);
- SendRequest(baseAddress + "/json/EchoPet", "POST", "application/json", "{\"Name\":\"Fido\",\"Color\":\"Black and white\",\"Markings\":\"None\",\"Id\":1}");
- SendRequest(baseAddress + "/json/Add", "POST", "application/json", "{\"x\":111,\"z\":null,\"w\":[1,2],\"v\":{\"a\":1},\"y\":222}");
- Console.WriteLine("Now using the client formatter");
- ChannelFactory<ITestService> newFactory = new ChannelFactory<ITestService>(webBinding, new EndpointAddress(baseAddress + "/json"));
- newFactory.Endpoint.Behaviors.Add(new NewtonsoftJsonBehavior());
- ITestService newProxy = newFactory.CreateChannel();
- Console.WriteLine(newProxy.Add(444, 555));
- Console.WriteLine(newProxy.EchoPet(new Pet { Color = "gold", Id = 2, Markings = "Collie", Name = "Lassie" }));
- Console.WriteLine(newProxy.GetPerson());
- Console.WriteLine("Press ENTER to close");
- Console.ReadLine();
- host.Close();
- Console.WriteLine("Host closed");
- }
And that’s it for the formatter example!
New WCF Web API
One note that I want to leave here is that it’s not too easy to integrate any new formats in the existing WCF REST pipeline. In the example above I had to deal with formatters, Message objects, take a dependency on a content type mapper, just to be able to use this different serializer. And even after all of this, it’s not completely integrated with the pipeline – UriTemplates aren’t supported, for example. In an upcoming release, however, the WCF team is working hard to make WCF really a great platform for hosting HTTP services, and that includes a new platform (built on top of the current one) for HTTP services specifically. In the new WCF Web API (you can preview the bits on the Codeplex site) implementing such a scenario is a lot simpler, and other features (such as UriTemplate) will just continue to work after the new formatter is plugged in. Glenn Block has posted a formatter that does exactly that on the thread https://wcf.codeplex.com/discussions/255873, you decide which one is easier :)
Coming up
Operation selectors.
Comments
Anonymous
October 16, 2011
Hello,I read u r blog ..it’s nice..i think that u r the person who help me to find out the solution about this..I have XML files in my one of the project and I have another project in which I have to add the path of that XML Files in web.config file. When we use the path of our local machine its working fine. But for the deployment of the project in the cloud I need to use a relative path.What I want to do is add the relative path of the XML files in web.config file of WCF service.and Fetch From there in Class libraryi usedAppDomain.CurrentDomain;Which get the Service path bcoz i add the class library refrence into serviceHttpContext.GetAppConfig(“XmlFiles”);ConfigurationManager.AppSettings["XmlFiles"];HostingEnvironment.MapPath(“~/EmployeeManagement.xml”);I used All Above..But in o/p i get the relative path of Service not Class..I refer almost post from internet related to relative path but don’t get proper solution.For Last 3 days i m searching about this..Will it work in the cloud? How to add the relative path of the file???Is there any other way to access those Xml files in cloud ??please help me.Thanks in advance for your help..Anonymous
October 20, 2011
Hi kinjal, the answer for whether it will work "in the cloud" really depends on which provider you have. Some of them will allow you access to the file share, some won't. I'd suggest you to post a question in a general forum (StackOverflow works quite well) with specific details about your problem (language, where you're hosting, ASP.NET version, etc), and likely you'll get some answers.Good luck!Anonymous
November 23, 2011
Is there a way to retain the ability to use automatic formatting with the end point and send either XML or Json in the accept header...and yet, "IF" required format is Json, to encode/decode it using Json.Net? In the example above, it looks like the code won't work if I were to send XML in the "Accept" header to the "Json" specific endpoint (since we've made it Json specific). I know its easier to do with the WebAPI, but am just curious if there's a way to change the default Json serialization in standard WCF.Thanks,PriyaAnonymous
February 29, 2012
Hi Priya, that's possible to do - the easiest way would be to wrap the original formatter in your JSON.NET-aware formatter. Then when deserializing the request, first check the content-type. If it's application/json (or text/json), then use the code which does JSON.NET as we have. Otherwise, you can rewrite the message to be read in XML format using the code I have at blogs.msdn.com/.../supporting-raw-requests-for-individual-operations.aspx, then pass it to the original formatter.Anonymous
October 16, 2012
Why doesn't Microsoft allow us to see any of their implementations of these different extensibility classes? Without seeing how real life, production quality extensions are written, the task of producing your own production quality extensions becomes MUCH harder.Being able to see the actual code is one of the HUGE benefits to the open source software movement. You can see the code! And you can get the source code for Java as well. I know that there are a few sections of .Net code where the source can be viewed at least in the debugger. It sure would be nice to be able to see WCF added to the list of modules for which at least this debugger level of access is provided.Anonymous
October 16, 2012
Hi Bradley, I don't work for the WCF team anymore, so I don't know exactly what are their plans regarding open source for the platform. But that's the top (by far) request in the UserVoice page for WCF - aspnet.uservoice.com/.../147206-wcf-web-services - so you can also vote for that request in that page.One thing that you can consider using to see the implementation is to use a tool such as Reflector or IL Spy to just open the WCF assemblies and look at their disassembly. I've actually found those tools quite handy exactly to see how some internal features were implemented.Anonymous
July 19, 2013
Hi Carlos,sorry to bother you about this old post, we've got has a few hundreds services exposed using SOAP with a custom binding.Now, a new request was made, to use the current SOAP envelope but with the message body encoded in json, I was under the idea that I could switch the serialization/deserialization process to use json.net maintaining the soap envelope very easily, but I'm having a hard time to make it work.I tried to use your example as my basis. We create the end points and set the behaviors and the rest of the configurations programmatically (we don't use any configuration file, we store our settings in a db), we use a a windows service to expose all the services.A few questions, is the request feasible? (SOAP envelop with Json body - even in base64 like in your example)I tried to use your classes (with minor changes) and include them in our project and create a new end point per service with a different port and a set of behaviors (JsonBehavior - copy of your class, EnableCrossOriginResourceSharingBehavior and JsonEndPointBehavior - just to set a custom message inspector), I used your client with my interfaces and I'm always getting the error:Message: The remote server returned an unexpected response: (400) Bad RequestInnerException.Status: system.net.webexceptionstatus.protocolerrorInnerException.Response: {System.Net.HttpWebResponse}Can you help me? Is there an easier way to do this?Thanks for the help a for you postsAnonymous
July 24, 2013
Hi Carlos, just to tell that I managed to make everything work, thanks for the exampleAnonymous
April 01, 2014
Hi - this is a very interesting post, I was wondering if there is anything which be done in the dispatch formatter to deserialise the incoming POST message in case-insensitive manner.Anonymous
January 14, 2015
Hi Carlos,Old article, I know, but I hope you are still reading the replies.Thanks for your knowledge, it helped me a great deal, but I ran eventually into an issue. I'm using a MessageContract and I can't seem to get the SerializeReply to work.I am able to receive incomming JSON requests and deserialize them into my used types, pass them to my service class and 'do my thing', but when I return the response, WCF totally forgets about the JSON.net serializer and keeps using the std serializer for the response. When I decorate my model with [DataContract] instead of [MessageContract], then suddenly the WCF stack get routed through SerializeReply.Can you give me a lead?Cheers!Anonymous
January 29, 2015
The comment has been removedAnonymous
January 29, 2015
★★★★★ =)Anonymous
April 02, 2015
Thanks for this. I needed a wrapped request with 4 parameters, but this code's DeserializeRequest errored with "No token to close. Path ''." at serializer.Deserialize when it Read to the end of the root's first property. Here's my implementation (with some hard-coded serializer settings and obviously not yet prod-ready): public void DeserializeRequest(Message message, object[] parameters) { var jsonSettings = new JsonSerializerSettings() { TraceWriter = new Newtonsoft.Json.Serialization.DiagnosticsTraceWriter() { LevelFilter = System.Diagnostics.TraceLevel.Verbose }, DateFormatHandling = this.DateFormatHandling, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore }; //stackoverflow.com/.../599238: string messageBody; using (MemoryStream mss = new MemoryStream()) { using (var messageWriter = System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonWriter(mss)) { message.WriteMessage(messageWriter); messageWriter.Flush(); messageWriter.Close(); messageBody = Encoding.UTF8.GetString(mss.ToArray()); log.Debug(messageBody); } } var requestJObject = Newtonsoft.Json.Linq.JObject.Parse(messageBody); int operationParameterCount = operationDescription.Messages[0].Body.Parts.Count; if (operationParameterCount > 0) { for (int i = 0; i < operationParameterCount; i++) { var parameter = operationDescription.Messages[0].Body.Parts[i]; if (requestJObject.Properties().Any(t => t.Name == parameter.Name)) { var currentParameter = requestJObject[parameter.Name]; parameters[parameter.Index] = JsonConvert.DeserializeObject( currentParameter.ToString(), parameter.Type, jsonSettings); } } } else { parameters[0] = JsonConvert.DeserializeObject(requestJObject.ToString(), this.operationDescription.Messages[0].Body.Parts[0].Type, jsonSettings); } }Anonymous
April 02, 2015
actually, correction: switched the single/multiple parameter count check to match yours, starting at if (parameters.Length == 1) {Anonymous
October 03, 2015
I am using WCF service default serialization and binding but for one of my call I want to use Jason as my this call is bringing heavy data to client side and want to improve the performance. Can I use Jason for operation contract only? if yes how ?