I've been working on this problem for quite a long time, here are my findings and requirements:
We have two endpoints:
- TCP endpoint
- WebHttp endpoint
Through the WebHttp endpoint, we need to support JSON and XML but with a custom response format. This is the format needed (only JSON shown for clarity):
{
"status": "success",
"data" : {}
}
What we need is for each object returned to be serialized normally and be put under data in the hierarchy. Let's say we have this OperationContract:
ObjectToBeReturned test();
and ObjectToBeReturned is:
[DataContract]
class ObjectToBeReturned {
[DataMember]
public string A {get; set;}
[DataMember]
public string B {get; set;}
}
Now, we would like through TCP to exchange directly the ObjectToBeReturned object but through WebHttp to have the following format as a response:
{
"status": "success",
"data": {
"A": "atest",
"B": "btest"
}
}
Possibility 1
We have considered two possibilities. The first one would be to have an object say named Response that would be the return object of all our OperationContract and it would contain the following:
[DataContract]
class Response<T> {
[DataMember]
public string Status {get; set;}
[DataMember]
public T Data {get; set;}
}
The problem with that is we would be required to exchange this object also through the TCP protocol but that is not our ideal scenario.
Possibility 2
We have tried to add a custom EndpointBehavior with a custom IDispatchMessageFormatter that would only be present for the WebHttp endpoint.
In this class, we implemented the following method:
public Message SerializeReply(
MessageVersion messageVersion,
object[] parameters,
object result)
{
var clientAcceptType = WebOperationContext.Current.IncomingRequest.Accept;
Type type = result.GetType();
var genericResponseType = typeof(Response<>);
var specificResponseType = genericResponseType.MakeGenericType(result.GetType());
var response = Activator.CreateInstance(specificResponseType, result);
Message message;
WebBodyFormatMessageProperty webBodyFormatMessageProperty;
if (clientAcceptType == "application/json")
{
message = Message.CreateMessage(messageVersion, "", response, new DataContractJsonSerializer(specificResponseType));
webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
}
else
{
message = Message.CreateMessage(messageVersion, "", response, new DataContractSerializer(specificResponseType));
webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Xml);
}
var responseMessageProperty = new HttpResponseMessageProperty
{
StatusCode = System.Net.HttpStatusCode.OK
};
message.Properties.Add(HttpResponseMessageProperty.Name, responseMessageProperty);
message.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
return message;
}
This seems really promising. The problem with that method is that when serializing with the DataContractSerializer we get the following error:
Consider using a DataContractResolver if you are using DataContractSerializer or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to the serializer.
We really don't want to list all of our known types above our Response class since there will be too many and maintenance will be a nightmare (when we listed the knowntypes we were able to get the data). Please note that all the objects passed to response will be decorated with a DataContract attribute.
I have to mention that we don't care if changing the message format can cause the WebHttp endpoint to not be accessible via a ServiceReference in another C# project, they should use TCP for that.
Questions
Basically, we only want to customize the return format for WebHttp so, the questions are:
- Is there an easier way to accomplish that than what we are doing?
- Is there a way to tell the serializer the knowntype from the
resultparameter's type in theSerializeReplymethod? - Should we implement a custom
Serializerthat will be called in theMessageDispatcherFormatterthat will tweak the format to fit ours ?
We feel we are on the right path but some parts are missing.