Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts

Tuesday, July 27, 2010

WCF DataContractSerializer Working Process
How does this class serialize the contract? And why does it have performance issue? These questions make me mad these days, because my project suffers a lot from this class. I have to spend several hours to figure this thing out. Here is what it does.
There is an internal class named DataContract used in this process, and it’s important. When it starts to serialize/deserialize something, it first gets different DataContract according to the data types. In other words, there are many subclasses of DataContract:
ClassDataContract
CollectionDataContract
EnumDataContract
GenericParameterDataContract
PrimitiveDataContract (this class has many subclasses for bool, int, object etc.)
SpecialTypeDataContract
SurrogateDataContract
XmlDataContract
This class is important because I think it’s the class that does the real serialization. In this class, there is an important member method: WriteXmlValue, it should be easy to tell what it does by the name. When the DataContract for the specific type is created, it will be stored in a cache, its key is generated based the type.
After getting the correct DataContract, it creates XmlObjectSerializerWriteContext object. This class is also an internal class, which is used to maintain the object references in contracts. XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel will be used to serialize the object. If there is a DataContractResolver, it will be used here to resolve the type information. Since this is not my focus, I just let it go. Then, SerializeAndVerifyType is invoked in which WriteDataContractValue is called. In this WriteDataContractValue method, it calls DataContract.WriteXmlValue, which is mentioned above.
Here, I will take ClassDataContract as an example to explain how this class works. When it’s the first to invoke WriteXmlValue, it will dynamically generate an XmlFormatWriterDelegate by XmlFormatWriterGenerator.GenerateClassWriter. Following is the implementation copied from Reflector.
internal XmlFormatClassWriterDelegate GenerateClassWriter(ClassDataContract classContract)
{
this.ilg = new CodeGenerator();
bool allowPrivateMemberAccess = classContract.RequiresMemberAccessForWrite(null);
try
{
this.ilg.BeginMethod("Write" + classContract.StableName.Name + "ToXml", Globals.TypeOfXmlFormatClassWriterDelegate, allowPrivateMemberAccess);
}
catch (SecurityException exception)
{
if (!allowPrivateMemberAccess !exception.PermissionType.Equals(typeof(ReflectionPermission)))
{
throw;
}
classContract.RequiresMemberAccessForWrite(exception);
}
this.InitArgs(classContract.UnderlyingType);
this.DemandSerializationFormatterPermission(classContract);
this.DemandMemberAccessPermission(allowPrivateMemberAccess);
this.WriteClass(classContract);
return (XmlFormatClassWriterDelegate) this.ilg.EndMethod();
}
After that, this delegate will be used to do the serialization. There is also another XmlFormatReaderDelegate, which works like the same way. This method generation spends some time and may cause performance issue.
As above mentioned, it’s put in a cache, at the second time the same DataContract is used; it won’t do this again, so it saves much time.

Tuesday, November 04, 2008

What WCF client does ... (1)

Recently, I am curious about what WCF client really does when creating proxy object (ClientBase) or use the ChannelFactory directly.
If service reference is added by using the Visual Studio directly, it will automatically create a service client for you, it implements the service contract and inherits from the ClientBase; the generic type TChannel, it is not the IChannel, it is the service contract class. The following diagram is the class diagram for ClientBase.

It is just a snapshot; actually, there are many other members and methods. I only show you two important members: ChannelFactoryRef and EndpointTrait. When you create the client object, in the ClientBase class, the constructor will create one EndpointTrait object, it is used to create ChannelFactory. After that, the ClientBase is going to initialize the ChannelFactoryRef object.
Indeed, there is a cache called factoryRefCache of type ChannelFactoryRefCache, it is initialized in the static constructor of the ClientBase. When it initializes the ChannelFactoryRef object, it tries to get the object from the cache first, but if the state of the object isn’t opened, that means the object cannot be used, it will remove it from the cache and create a new object; else it uses it and adds one more reference. So, multiple clients for the same service may use the same ChannelFactory.
Let’s see what is going to happen when it needs to create a new object. It uses the EndpointTrait object to create the ChannelFactory object, and passes it to the ChannelFactoryRef constructor. You may have some imagination already; the ChannelFactoryRef is just a reference counter class for the ChannelFactory. After that, the ClientBase delegates all the work to the inner ChannelFactory object.
What is the job of the ChannelFactory? It creates the service endpoint first; then, it needs to configure this service endpoint. I use a diagram to show you the process.

From the diagram, it searches the configuration file first, if there is such a file, it read the configuration from it (LoadChannelBehaviors); if not, it uses some common configuration (LoadCommonClientBehaviors). In the LoadBehaviors() method, it gets all the IEndpointBehavior objects. Behavior is the most useful extensible point in WCF, IEndpointBehavior allows you to achieve the extensibility in client. If you want to apply you behavior to the client, you need to add your IEndpointBehavior to the IEndpointBehavior collection of ServiceEndpoint before it calls the ChannelFactory.Open() method.
Now, we need to use ChannelFactory.CreateChannel(), here is what happens.

It ensures the communication state is opened first. If the state isn’t opened, it calls Open(), in this step, it initializes the channel and applies some configuration (call opening()), let’s see what happens in detail.

ComputeContractRequirements() method is used to inject into the ContractDescription, it collects some information from it, such as the session mode, whether the operation is one-way etc. . In the BuildProxyBehavior() method, it gets the service endpoint’s contract description; then, based on the operation descriptions of the contract, it determines which operation type should be built. If the operation is initiated by the server, then it builds the dispatch operation (BuildDispatchOperation), because it acts as a receiver; otherwise, it builds a proxy operation (BuildProxyOperation), because it acts as a sender. In both methods, it adds the operation into the one collection of the ClientRuntime. The next step is to apply the client behavior, which is why I say you should add your IEndpointBehavior to the collection before you open it. In the BuildOperations() method, the IOperationBehavior will be applied according to their operation type: ApplyClientBehavior or ApplyDispatchBehavior. The ComputeRequiredChannels() uses the results from the ComputeContractRequirements() to determine which channel type it needs. Then, it compares these requirements with the Binding. If something mismatch, it throws exception. That’s all what happens before it opens the channel.
In order to get the service client, it creates a ServiceChannel object, CreateProxy() method creates the service client, the ServiceChannelProxy inherits the RealProxy, which is in the Remoting. The client uses the client to communicate with the service. WCF client runtime will actually intercept every WCF service call by injecting a TransparentProxy and ServiceChannelProxy between WCF service call site and underlying the channel stack. The reason WCF implements the client runtime the way it is that WCF needs to differ between normal method invocation and WCF service invocation on service proxy objects.
OK, the channel is prepared; we are ready to invoke the service, we can call the service just as invoking local method through the client. It is something like RPC or RMI, but there are many differences, I will explain what happens in this process in future.