The biggest bottleneck was the initial call made to the service which used the model in its service contract. WCF generates serialization code on the fly when the first service request is sent – and due to the size and structure of the schema – the amount of generated code was large – 20mb!
The reason why the structure was an issue is because the model dictates that you use a generic message wrapper type which contains an element which allows you to specify the request and response type (basically in the XSD definition, this element contains several hundred choices that it could be – so when it’s deserialized – this is of type object – with several hundred XmlElementAttribute attributes above it as it could be any of these types specified in the XmlElementAttribute attributes. A truncated example of the deserialized code is below.
[System.Xml.Serialization.XmlElementAttribute("exampleRequest1", typeof(ExampleRequest1))] [System.Xml.Serialization.XmlElementAttribute("exampleResponse1", typeof(ExampleResponse1))]
…
[System.Xml.Serialization.XmlElementAttribute("exampleRequest200", typeof(ExampleRequest200))] [System.Xml.Serialization.XmlElementAttribute("exampleResponse200", typeof(ExampleResponse200))]
… you get the idea
public object Item {…
So the generated serialization code needs to handle every possible combination that could possibly be thrown at it (so it can handle the all the possible types that are declared in the XmlElement attributes). So in other words, a lot of if statements
To overcome the cost of generating the serialization code, I used the steps described in this MSDN article - http://msdn.microsoft.com/en-us/library/aa751883.aspx. I went with option 3: “Compile the generated serialization code into your application assembly and add the XmlSerializerAssemblyAttribute to the service contract that uses the XmlSerializerFormatAttribute. Do not set the AssemblyName or CodeBase properties. The default serialization assembly is assumed to be the current assembly”.
I used the following method to generate my assembly with the serialization code:
- Add the XmlSerializerAssembly attribute and XmlSerializerFormat to my service contract (example below).
[ServiceContract(Name = "ITest", Namespace = "http://testnamespace"), XmlSerializerFormat]
[XmlSerializerAssemblyAttribute()]
public interface ITest
{
- Compile my assembly
- Run svcutil against my assembly – an example of the command line: svcutil C:\Source\Project\TestServiceContract \bin\TestServiceContract.dll /t:xmlserializer.
- Copy the generated file that was produced in step 3 to the project folder of the assembly and include the generated file in the project.
- Rebuild the assembly.
- Done.
One option I looked at was the ServiceKnownType attribute – which is specific to the DataContractSerializer. The positives with the DataContractSerializer is it really is fast. So even with the below
[System.Xml.Serialization.XmlElementAttribute("exampleRequest1", typeof(ExampleRequest1))] [System.Xml.Serialization.XmlElementAttribute("exampleResponse1", typeof(ExampleResponse1))]
…
[System.Xml.Serialization.XmlElementAttribute("exampleRequest200", typeof(ExampleRequest200))] [System.Xml.Serialization.XmlElementAttribute("exampleResponse200", typeof(ExampleResponse200))]
… you get the idea
public object Item {…
If I update my service contract to the following:
[ServiceContract(Name = "ITest", Namespace = "http://testnamespace"), DataContractFormat]
[ServiceKnownType(typeof(ExampleRequest1))]
[ServiceKnownType(typeof(ExampleResponse1))]
public interface ITest
{
The service performance is amazingly faster. In fact, even though I am still using the ChannelFactory, so the channel isn’t cached (I’ll get onto that later) – the cost of generating the channel is very cheap (I’m assuming since the amount of types that need to be reflected when creating the channel is considerably low since we have specified the types the Service is interested in by using ServiceKnownType).
However, since we build our services using the Contract First pattern – the DataContract serializer is not an option since the payload that is produced across the wire doesn’t resemble at all the original XSD – in other words you have no control on how the XML will look.
So the next bottleneck in performance was the generation of the channel. Since the ChannelFactory is being used, the channel is not cached; however if you are using a generated proxy class by svcutil, ClientBase caches its internal ChannelFactory. More details are here: http://blogs.msdn.com/b/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx.
Again an assumption on my part for why the channel generation is costly in this example: in the client improvement article by Wenlong Dong, when the channel is created, all of the required CLR types are reflected, and because of the Item property in the example and all of the possible types it could be – there are a lot of the types that are reflected (which is where I believe ServiceKnownType mention before resolves this issue since the channel generation is fast, ServiceKnownType must supress the unneeded types that are not used by the service). I needed a solution like this using the XmlSerializer. And I worked it out using the below process
- Generate the XmlSerializer file against the service dll with all the required XmlElement attributes (i.e. many XmlElement attributes above the Item property).
- Copy the generated serialization file to the service folder; add it to the project etc…
- Remove all the XmlElement attributes for the Item property – and add [System.Xml.Serialization.XmlElementAttribute(Order = 0)]
- Rebuild the project that contains the CLR schema definitions.
- Latency is very low.
So using the assumption for why the channel generation is so costly - the cost of generating the channel is now reduced as it doesn’t have to worry about reflecting as many types (which according to Wenlong, is the biggest cost) – however serialization is successful as I said before since we generated the serialization code is aware of these types.