Want to show your appreciation?
Please a cup of tea.

Tuesday, August 19, 2008

Slow Startup Performance with Spring.Net WebServiceProxyFactory

Update: This is the beginning of a series blog posts of how this problem is described and eventually addressed. I'm including a table of content here for easier navigation.

Table Of Content

Describe the Problem: Slow Startup Performance with Spring.Net WebServiceProxyFactory (this post)

Propose the Solution: Generate Fast Startup .Net Web Service Client That Implements Domain Interface

Implement the Solution: Integrate Web Service Client Generation into Build Process

Resolve ClickOnce Deployment Issue: ClickOnce Deployment + SGen Problem and The Workaround

Integrate with Spring.Net: Using WSCodeGen with Spring.Net

Release the WSCodeGen Project: Getting Ready for WSCodeGen 1.0.1 Release

Conclusion: Web Service Client Startup Performance: Spring.Net Proxy v.s. WSCodeGen

If anybody is still writing the Web Service using the WebService and WebMethod attributes at server side and adding Web Reference at the client size, then they should seriously look at how Spring.Net does the Web Service.

Let's list some benefits of using Spring.Net for Web Service.

  1. You can just write your web method in PONO and ask Spring.Net to expose it as Web Service.
  2. Your PONO will typically implement a service interface. And of course, you can use dependency inject.
  3. Spring.Net can expose the same PONO as other type of service, for example .Net Remoting.
  4. At the client side, Spring.Net can generate a Web Service client dynamic proxy that implements the same interface.
  5. Because the proxy client implements the same interface that the service object does. You can actually use your own domain objects in the Web Service calls. Just like calling directly calling the service object at the server side. Cool, isn't it?

Yes, we have been using it in our projects and running in production for a year now. Everything was great until recently we started to look at the startup performance of our SmartClient application.

Regardless of being Web Reference or Spring.Net dynamic proxy, they all inherit from SoapHttpClientProtocol class. That class uses XmlSerializer to convert between object and XML. When the first time XmlSerializer for a given type was created, it actually creates a temporary assembly using c# compiler. You can see those temporary files in the %TEMP% folder if you configure your application to diagnose the XmlSerializer by adding the following XML snip to the app.config file. This compiling process is slow and it does this for every type it encounters.

  <!--

   Never deliver your application with this.

   It fills up your temp folder in no time.

  -->

  <system.diagnostics>

    <switches>

      <add name="XmlSerialization.Compilation" value="4"/>

    </switches>

  </system.diagnostics>

When our SmartClient needs to connect to multiple Web Services at startup, the XmlSerializer assembly creation process was taking significant amount of time during application startup.

Microsoft realized that problem so a utility called SGen.exe was released. SGen scans the types in an assembly, say Example.dll, and generates all serializer into a sister assembly, named Example.XmlSerializers.dll. VS 2005 also provides "generate serialization assembly" on the Build tab of project property. Grant Drake has a nice blog about this.

When the "generate serialization assembly" option was set to Auto or On, the SGen does generate the serializer assembly for Web Reference client to eliminate the temporary assembly creation in runtime. This greatly improves the runtime startup performance.

But when come to the Spring.Net dynamic proxy Web Service client, no mater how I tweaked the options, the temporary assembly was always created in runtime which seriously impacts the startup performance of the client applications, especially when multiple dynamic proxies are used on a slower PC.

I started a thread about this problem at the Spring.Net's support forum back in Feb 2008. Since then, I started to explore a way out myself.

My first venture was to manually create the Web Service client. I copied the code from the Web Reference generated source and changed the class to implements our service interface. I can then use this class to replace the dynamic proxy in the Spring.Net's application context. SGen is happily pre-compiling the serializer for it, just like it does to Web Reference client!

Look at the following code as an example of Web Service clients that I have manually created. This solution works, fast, not difficult to write. It is a good option for a client that only calls a few Web Services. But for a client that talks to a few dozens of Web Services, I have to say that it is repeated task and tedious. Can we generate them in source code at compile time instead of using dynamic proxy at runtime? stay tuned...

#pragma warning disable 1591

namespace Example.Client {

 

 

    [System.Web.Services.WebServiceBindingAttribute(Namespace="http://tempuri.org/")]

    public class MyTestClient : System.Web.Services.Protocols.SoapHttpClientProtocol, Example.BizService.Client.Program.IMyTest {

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void Simple() {

            object[] result = this.Invoke("Simple", new object[0]);

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void OneParam(int i) {

            object[] result = this.Invoke("OneParam", new object[] {

                        i});

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void AnotherParam(Example.BizService.Client.Program.Another i) {

            object[] result = this.Invoke("AnotherParam", new object[] {

                        i});

            return;

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual int SimpleReturn() {

            object[] result = this.Invoke("SimpleReturn", new object[0]);

            return ((int)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual int OneParamReturn() {

            object[] result = this.Invoke("OneParamReturn", new object[0]);

            return ((int)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string RefParam(ref int i, int j) {

            object[] result = this.Invoke("RefParam", new object[] {

                        i,

                        j});

            i = ((int)(result[1]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string OutParam(out int i) {

            object[] result = this.Invoke("OutParam", new object[0]);

            i = ((int)(result[1]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual string RefAnother(ref Example.BizService.Client.Program.Another a, out int i) {

            object[] result = this.Invoke("RefAnother", new object[] {

                        a});

            a = ((Example.BizService.Client.Program.Another)(result[1]));

            i = ((int)(result[2]));

            return ((string)(result[0]));

        }

 

        [System.Web.Services.Protocols.SoapDocumentMethodAttribute()]

        public virtual void OutAnother(out Example.BizService.Client.Program.Another a) {

            object[] result = this.Invoke("OutAnother", new object[0]);

            a = ((Example.BizService.Client.Program.Another)(result[0]));

            return;

        }

    }

}

#pragma warning restore 1591

No comments:

Post a Comment