Want to show your appreciation? Please to my charity.

Wednesday, September 24, 2008

XmlSerializer Doesn't Serialize TimeSpan to XML Duration Type

Somebody said this is by design. Then this is another design flaw and evidence of the immaturity of .Net Framework?! Google for "convert XML duration type to TimeSpan" returned all kind of workarounds. The general idea is to use a helper string property to make the Dumb XmlSerializer happy. But some of them are just inappropriate due to the way the TimeSpan is converted to string. XML Schema type xs:duration has its special format. This one simply uses TimeSpan.ToString(). There is also a book presented a home grown utility to do the conversion. While XmlSerializer failed to handle it, but it is ironical that there is a .Net Framework class, XmlConvert, does exactly this but sadly, the designer of XmlSerializer simply ignored it.

Below is the workaround I used to map the TimeSpan to xs:duration.

private TimeSpan _timeToLive = new TimeSpan(0, 10, 0); // 10 minutes
 
[XmlIgnore]
public TimeSpan TimeToLive
{
    get { return _timeToLive; }
    set { _timeToLive = value; _expiration = null; }
}
 
[XmlAttribute("TimeToLive", DataType = "duration")]
[DefaultValue("PT10M")]
public string XmlTimeToLive
{
    get { return XmlConvert.ToString(_timeToLive); }
    set { _timeToLive = XmlConvert.ToTimeSpan(value); }
}

Friday, September 19, 2008

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

I'm going to make this post as a summary of our effort to solve the startup performance problem of Spring.Net's WebServiceProxyFactory client. We described the problem, proposed the solution, implemented it, resolved the ClickOnce deployment issue, integrated back with Spring.Net and finally a well tested release. Now, it's the time to look at what exactly we gain by doing all these.

Note: All source code we discussed is available in the WSCodeGen project site. You can download the source code, it is located in the example/CompareSpringProxy folder.

Let's start with a Model project. It has only one simplest interface IHelloWord below:

public interface IHelloWorld
{
    string SayHello(string name);
}

Then we added a Web Service project and defined two identical Web Services. Both implement the IHelloWorld Interface. Below listed the HelloWorld1.asmx and the HelloWorld2.asmx is identical except the class name.

[WebService(Namespace = "http://tempuri.org")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class HelloWorld1 : System.Web.Services.WebService, IHelloWorld
{
    [WebMethod]
    public string SayHello(string name)
    {
        return "Hello " + (string.IsNullOrEmpty(name) ? "World" : name);
    }
}

Now comes the protagonist. Let's create the Client project as a console application. Add reference to Spring.Core, Spring.Service and the Model project that we created earlier. Setup the Spring.Net with following XML configuration in the App.config.

<objects default-lazy-init="true"
        xmlns="http://www.springframework.net"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
 
  <object id="CookieContainer" type="System.Net.CookieContainer"/>
 
  <object abstract="true" id="AbstractWebServiceProductTemplate" 
          name="AbstractWSCodeGenClient">
    <property name="CookieContainer" ref="CookieContainer" />
    <property name="UseDefaultCredentials" value="true"/>
    <property name="PreAuthenticate" value="true"/>
  </object>
 
  <object id="AbstractWebServiceClient" abstract="true"
    type="Spring.Web.Services.WebServiceProxyFactory,Spring.Services">
    <property name="ProductTemplate">
      <object parent="AbstractWebServiceProductTemplate"/>
    </property>
  </object>
 
  <object id="WarmUpHelloWorld1" parent="AbstractWebServiceClient">
    <property name="ServiceUri" value="http://localhost:4834/HelloWorld1.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
  <object id="WarmUpHelloWorld2" parent="AbstractWSCodeGenClient"
          type="CompareSpringProxy.Client.WarmUpHelloWorld2, CompareSpringProxy.Client">
    <property name="Url" value="http://localhost:4834/HelloWorld2.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
  <object id="SpringProxyHelloWorld1" parent="AbstractWebServiceClient">
    <property name="ServiceUri" value="http://localhost:4834/HelloWorld1.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
  <object id="SpringProxyHelloWorld2" parent="AbstractWebServiceClient">
    <property name="ServiceUri" value="http://localhost:4834/HelloWorld2.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
  <object id="WSCodeGenHelloWorld1" parent="AbstractWSCodeGenClient"
              type="CompareSpringProxy.Client.HelloWorld1, CompareSpringProxy.Client">
    <property name="Url" value="http://localhost:4834/HelloWorld1.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
  <object id="WSCodeGenHelloWorld2" parent="AbstractWSCodeGenClient"
              type="CompareSpringProxy.Client.HelloWorld2, CompareSpringProxy.Client">
    <property name="Url" value="http://localhost:4834/HelloWorld2.asmx"/>
    <property name="ServiceInterface" 
              value="CompareSpringProxy.Model.IHelloWorld, CompareSpringProxy.Model"/>
  </object>
 
</objects>

We purposely configured the Spring.Net context to use lazy-init so that we can measure the startup time for each object individually. In the configuration, we have configured two warn up object that is used to warm up the Web server and the Spring.Net context. There are two Spring.Net dynamic proxies, SpringProxyHelloWorld1 and SpringProxyHelloWorld2, plus two WSCodeGen clients, WSCodeGenHelloWorld1 and WSCodeGenHelloWorld2, that going against the HelloWorld1.asmx and HelloWorld2.asmx respectively. They are the object that will be used to do the performance test.

In order for WSCodeGen to generate the client, we need to add an XSL file to transform the Spring.Net configuration. The content of the WebServiceClient.xsl file is listed below.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:spring="http://www.springframework.net">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:param name="namespace">CompareSpringProxy.Client</xsl:param>
  <xsl:template match="/">
    <web-service xmlns="urn:web-service-client-configuration-1.0">
      <client-group namespace="{$namespace}" xml-namespace="http://tempuri.org">
        <xsl:for-each select="//spring:object[@parent='AbstractWSCodeGenClient']">
          <client class-name="{substring(substring-before(@type, ','), string-length($namespace)+2)}">
            <interface>
              <xsl:value-of select="spring:property[@name='ServiceInterface']/@value"/>
            </interface>
          </client>
        </xsl:for-each>
      </client-group>
    </web-service>
  </xsl:template>
</xsl:stylesheet>

Add a post build event to the Model project to have the client source code generated.

$(SolutionDir)..\..\build\WebServiceClientGenerator\Debug\WebServiceClientGenerator.exe $(SolutionDir)CompareSpringProxy.Client\App.config $(SolutionDir)CompareSpringProxy.Client\WebServiceClient.cs $(SolutionDir)CompareSpringProxy.Client\WebServiceClient.xsl

The last piece is the Program.cs. I have added comment along the line so it is easy to understand. We need those warm up calls so that the loading of Spring.Net abstract objects, dependency assemblies and initialization of Web Service at the server side will not impact the performance measure of the client.

static void Main(string[] args)
{
    // start the spring context.
    IApplicationContext ctx = ContextRegistry.GetContext();
 
    // warn up the spring as well as the Web Service server
    IHelloWorld warmUp1 = (IHelloWorld)ctx.GetObject("WarmUpHelloWorld1");
    warmUp1.SayHello("warmUp1");
    IHelloWorld warmUp2 = (IHelloWorld)ctx.GetObject("WarmUpHelloWorld2");
    warmUp2.SayHello("warmUp2");
 
 
    DateTime startTime;
    TimeSpan timeSpan;
    string result;
 
    // Gather the performance
 
    startTime = DateTime.Now;
    IHelloWorld codeGen1 = (IHelloWorld)ctx.GetObject("WSCodeGenHelloWorld1");
    result = codeGen1.SayHello("codeGen1");
    timeSpan = DateTime.Now - startTime;
    Console.WriteLine("{0} took {1}", result, timeSpan);
 
    startTime = DateTime.Now;
    IHelloWorld proxy1 = (IHelloWorld)ctx.GetObject("SpringProxyHelloWorld1");
    result = proxy1.SayHello("Proxy1");
    timeSpan = DateTime.Now - startTime;
    Console.WriteLine("{0} took {1}", result, timeSpan);
 
    startTime = DateTime.Now;
    IHelloWorld proxy2 = (IHelloWorld)ctx.GetObject("SpringProxyHelloWorld2");
    result = proxy2.SayHello("Proxy2");
    timeSpan = DateTime.Now - startTime;
    Console.WriteLine("{0} took {1}", result, timeSpan);
 
    startTime = DateTime.Now;
    IHelloWorld codeGen2 = (IHelloWorld)ctx.GetObject("WSCodeGenHelloWorld2");
    result = codeGen2.SayHello("codeGen2");
    timeSpan = DateTime.Now - startTime;
    Console.WriteLine("{0} took {1}", result, timeSpan);
 
    Console.WriteLine("Press enter to continue...");
    Console.ReadLine();
}

Now, we can start the Web project and then run the client application to see the difference. On my Core2 Due X61 Thinkpad, below is the result.

Hello codeGen1 took 00:00:00.0156250
Hello Proxy1 took 00:00:00.1562500
Hello Proxy2 took 00:00:00.1875000
Hello codeGen2 took 00:00:00.0156250

The result clearly shows the advantage of using WSCodeGen. This comparison also demonstrates how easy to replace the WebServiceProxyFactory with WSCodeGen in an existing Spring.Net configuration.

It is now time to put an end to this series of the blog posts about the slow start up issue of the Spring.Net dynamic Web Service client proxy and the solution to this. But his is definitely not the end of the WSCodeGen project. Like exception propagation and using of the collection interface are two major problem of using Web Service with Spring.Net, WSCodeGen can be enhanced to provide solution to those problems.

Tuesday, September 16, 2008

Getting Ready for WSCodeGen 1.0.1 Release

I'm getting close to the planned 1.0.1 release for WSCodeGen. This is a bug fix and minor enhancement release. We are going to upgrade our projects. Below listed the major changes since 1.0.0

Basic VB Support

Basic VB support is in. Didn't use it for our own projects because we don't have any VB project. But tested on a test project and it works fine. To generate the VB code, simply name your source file with .vb extension. For example,

WebServiceClientGenerator.exe pathto\WebServiceClient.xml pathto\ClientSourceFile.vb

Uses Global Type Reference

By using global type reference in the generated code, we are able to avoid the namespace conflict. For example, the generated code now has type as global::Namespace.ClassName in C# and Global.Namespace.ClassName in VB.

Will Not Overwrite The Source File When There Is No Changes

Generator won't overwrite the source file when the generated source code is identical to what's already in the source file. This helps to avoid unnecessary build of VS.Net solution.

API Documentation and Unit Test

All public classes and their public members are documented so that the API documentation can be generated. Over fifty Unit Test cases are passed. 100% covers core generator classes. More then 80% of overall coverage.

Monday, September 15, 2008

Create or Delete a Service in Windows Server 2003

I need to delete a service was installed by a software that was already uninstalled. I search and came across various method, like manual registry editing, using of inssvr.exe and etc. Finally I found the this piece information to use the build in sc.exe. The instruction is for Windows XP but it works great for Server 2003.

Services are added from the Command Prompt. You need to know the actual service name as opposed to what Microsoft calls the Display Name. For example, if you wanted to create or delete the Help and Support service, the name used at the Command Prompt would be "helpsvc" rather than the Display Name of "Help and Support". The actual service name can be obtained by typing services.msc in Run on the Start Menu and then double clicking the Display Name of the service. Once you know the name;

To Create A Service
  • Start | Run and type cmd in the Open: line. Click OK.
  • Type: sc create <service name>
  • Reboot the system


Fig. 01

To Delete A Service
  • Start | Run and type cmd in the Open: line. Click OK.
  • Type: sc delete <service name>
  • Reboot the system

Monday, September 08, 2008

Using WSCodeGen with Spring.Net

Since I posted the Slow Startup Performance with Spring.Net WebServiceProxyFactory about 3 weeks ago, we have made great progress towards the solution to this problem. In an earlier post, I demonstrated how to use WSCodeGen in a typical application. In this post, I'm going to discuss how we effectively used Spring.Net's configuration file to drive the WSCodeGen, a nice idea from one of my colleagues.

Using the generated client in Spring IoC container

Certainly, we can easily use the generated client in the Spring.Net to replace the WebServiceProxyFactory, but for every each Web Service client, we will have to create a definition entry in the WebServiceClient.xml file as well as an entry in the Spring.Net's XML configuration file. Taking the example in this post, we'll have something like below in the Spring.Net's XML configuration file. The WSCodeGen client can accept any property that is allowed in the ProductTemplate of the WebServiceProxyFactory, plus two dummy properties, "ServiceInterface" and "Tag", we'll use the "ServiceInterface" in the later section.

<object id="HelloWorld"
      type="Example.Client.WebService.HelloWorldClient, Example.Client.WebService">
  <property name="Timeout" value="${WebService.DefaultTimeout}" />
  <property name="EnableDecompression" value="true"/>
  <property name="Url" value="http://localhost:3586/HelloWorld.asmx"/>
</object>

It is tedious and error prone maintaining the similar configuration information in two different XML files and keeping them in sync. Wouldn't be great if we can just use the Spring.Net's configuration file only. The good news is that WSCodeGen supports any XML file as long as a proper XSLT file is also supplied to transformed it.

Eliminate the duplicated configuration

Let's take a look at the old Spring.Net's XML configuration file below that uses WebServiceProxyFactory. Thanks to the Spring's abstract object definition, I'm so glad to see that all our Web Service client definitions inherit from AbstractWebServiceClient. This abstract layer made our cut over to WSCodeGen very easy.

We have more then forty Web Service clients and I'm showing only one in the XML section below. AbstractWebServiceClient object defines an abstract template for any WebServiceProxyFactory based client. It uses AbstractWebServiceProductTemplate which in turn uses CookieContainer. The object for the "target" property of CustomViewService is an Web Service client.

Note: The AbstractXmlCarrierUnpacker is a AOP proxy we used to overcome the problem that Web Service doesn't work with ICollection<T>, IList<T> and IDictionary<T> as parameter or return type in the domain interface. Let's ignore this for now.

<object id="CookieContainer" type="System.Net.CookieContainer"/>
 
<object abstract="true" id="AbstractWebServiceProductTemplate">
  <property name="Timeout" value="${WebService.DefaultTimeout}" />
  <property name="CookieContainer" ref="CookieContainer" />
  <property name="EnableDecompression" value="true"/>
  <property name="UseDefaultCredentials" value="true"/>
  <property name="PreAuthenticate" value="true"/>
</object>
 
<object id="AbstractWebServiceClient" abstract="true"
  type="Spring.Web.Services.WebServiceProxyFactory,Spring.Services">
  <property name="ProductTemplate">
    <object parent="AbstractWebServiceProductTemplate"/>
  </property>
</object>
 
<object id="CustomViewService" parent="AbstractXmlCarrierUnpacker">
  <property name="proxyInterfaces" 
    value="Demo.Service.ICustomViewService, Demo.BizModel"/>
  <property name="target">
    <object parent="AbstractWebServiceClient">
      <property name="ServiceUri" 
        value="${WebService.IM2.BaseURL}/CustomViewService.asmx"/>
      <property name="ServiceInterface"
        value="Demo.Service.Web.ICustomViewServiceWS, Demo.BizModel"/>
    </object>
  </property>
</object>

To make the configuration change to replace WebServiceProxyFactory based client with the WSCodeGen generated client, I used WinVi32.exe, which allows me to use the regular expression as well as binary mode. I have underlined all the changes in the final XML below. The AbstractWebServiceClient becomes useless and can be deleted after all clients are converted.

<object id="CookieContainer" type="System.Net.CookieContainer"/>
 
<object abstract="true" id="AbstractWebServiceProductTemplate" 
        name="AbstractWSCodeGenClient">
  <property name="Timeout" value="${WebService.DefaultTimeout}" />
  <property name="CookieContainer" ref="CookieContainer" />
  <property name="EnableDecompression" value="true"/>
  <property name="UseDefaultCredentials" value="true"/>
  <property name="PreAuthenticate" value="true"/>
</object>
 
<object id="AbstractWebServiceClient" abstract="true"
  type="Spring.Web.Services.WebServiceProxyFactory,Spring.Services">
  <property name="ProductTemplate">
    <object parent="AbstractWebServiceProductTemplate"/>
  </property>
</object>
 
<object id="CustomViewService" parent="AbstractXmlCarrierUnpacker">
  <property name="proxyInterfaces" 
    value="Demo.Service.ICustomViewService, Demo.BizModel"/>
  <property name="target">
    <object parent="AbstractWSCodeGenClient"
      type="Demo.WSClient.CustomViewService, Demo.WSClient">
      <property name="Url" value="${BaseURL}/CustomViewService.asmx"/>
      <property name="ServiceInterface"
        value="Demo.Service.Web.ICustomViewServiceWS, Demo.BizModel"/>
    </object>
  </property>
</object>

If you are still with me so far, you may noticed that the "ServiceInterface" property of the WSCodeGen client. Since the client was generated at compile time, what is point of setting the implemented interface at the runtime time again? Good question!

Dummy properties of the generated client class

There are two dummy properties, "ServiceInterface" and "Tag", being added to each Web Service client that is created by the WSCodeGen. They are called dummy properties because their setters do absolutely nothing. The purpose of those properties is to allow us adding additional information about the client in the Spring.Net's XML configuration file so we can keep the configuration information in one place. An XSLT transformer can then easily extract those information to create the XML document needed by the WSCodeGen.

Now let's add this XSLT file that picks up all the objects inherited from AbstractWSCodeGenClient and transform them to another XML document that is understood by the WSCodeGen.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:spring="http://www.springframework.net">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:param name="namespace">Demo.WSClient</xsl:param>
  <xsl:template match="/">
    <web-service xmlns="urn:web-service-client-configuration-1.0">
      <client-group namespace="{$namespace}" xml-namespace="http://demo.com/1.0">
        <xsl:for-each select="//spring:object[@parent='AbstractWSCodeGenClient']">
          <client class-name="{substring(substring-before(@type,','), string-length($namespace)+2)}" 
                  xml-namespace="#">
            <interface>
              <xsl:value-of select="spring:property[@name='ServiceInterface']/@value"/>
            </interface>
          </client>
        </xsl:for-each>
      </client-group>
    </web-service>
  </xsl:template>
</xsl:stylesheet>

Finally, we need to tell the VS.Net to take the Spring.Net's configuration file and transform it using the XSLT file to generate the source code. The following line should be used in the after build event of the Model project.

..\..\..\dependency\WSCodeGen\net\2.0\Debug\WebServiceClientGenerator.exe ..\..\..\src\Demo.SmartClient\app.config ..\..\..\src\Demo.WSClient\WSClient.cs ..\..\..\src\Demo.WSClient\WSClient.xsl

Your mileage may vary

The above was exactly how we integrated the WSCodeGen into our real world project. Indeed the examples are real except the true domain name is replaced by "demo.com". The XSLT was created based on our conventions illustrated below with examples.

  1. Web Services are named in PascalCase. For example, CustomViewService.
  2. Web Services are exposed as <name>.asmx. For example, CustomViewService.asmx. We actually, uses "CustomViewService.asmx" literally as object ID in our Spring.Net configuration file. Take a look at this post of how we made this possible to avoid naming conflicts.
  3. XML namespace of every Web Service starts with "http://demo.com/1.0" and followed by the name of the Web Service. For CustomViewService, it is http://demo.com/1.0/CustomViewService.
  4. Uses the Web Service name as the class name of the generated client. This is to ensure that the client name matches with the XML namespace.

The name match between 3 and 4 is essential for the XSLT to work because we use xml-namespace="#" to indicate that class name is used to suffix the XML namespace inherited from the group configuration. If you have no control to the XML namespace of the Web Services that clients are connecting to. You should consider to make user the the dummy property "Tag" of the client to embed that information in the Spring.Net configuration file.

Q&A

Questions can be posted to http://www.codeplex.com/WSCodeGen/Thread/List.aspx, or leave a comment here.

Thursday, September 04, 2008

ClickOnce Deployment + SGen Problem and The Workaround

In the post Slow Startup Performance with Spring.Net WebServiceProxyFactory, I discussed how important it is to have serialization assemblies pre-generated. In the process of achieving this goal, we found a something wrong with the ClickOnce Deployment handling the pre-generated serialization assembly. I believe this is a bug of ClickOnce Deployment.

In this post, I'm going to demonstrate how to re-produce the problem and implement a workaround for it. You can follow it step by step or if can download the source code.

Source code

All example solution source code I used in this post can be downloaded from here.

Setup the test environment

  1. Start with a new ClickOnceBug solution with a Web Service Project called ClickOnceBug.WebSerivce.
  2. Run the default Default Service1.asmx and make sure we can run HelloWorld.
  3. Add a Console Application Project called ClickOnceBug.Client to the solution.
  4. Add a Web Reference to the ClickOnceBug.Client referencing to the Service1 in the ClickOnceBug.WebSerivce project.
  5. Add a line below to the Main method of the Program class.
    Console.WriteLine(new localhost.Service1().HelloWorld());
  6. Run the client. Text "Hello World" should be printed.

Reproduce the problem

  1. This is not required to reproduce the bug, but it helps to show the impact of this bug to your ClickOnce deployed application (see this post for details). Add the XML section below to the app.config file in the Client project.

      <!--

       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>

  2. Double click (not expend) on the Properties folder of the Client project. In the Build tab, set the "Generate serialization assembly" option to "On".
    ClientPropertySetting
  3. Go to Publish tab, select "The application is available online only" option (again this is not necessary to reproduce the bug, it only helps to avoid the installation process when we test later). Then click on Publish Now button. The application will be built and the publish folder will open.
    ClickOncePublish
  4. Compare the publish folder and the bin\Debug folder. The ClickOnceBug.Client.XmlSerializers.dll is missing in the publish folder. This is a problem of ClickOnce deployment.
    ClientBinDebug ClientPublish
  5. If we go back to the publish tab in step 3 and click the "Application Files..." button. We'll find that the serialization assembly is listed as "Include(Auto)" although it actually failed to include. It doesn't help if we change it to "Include". If we do so, we may get build error or warning depends on other settings.
    PublishFiles

The impact

So what's the big deal of this? When we set the "Generate serialization assembly" to "On", we ask SGen utility to pre-generate XmlSerializers for our Web Service clients. Without the pre-generated serialization assembly, the XmlSerializer will need to compile a temporary assemble for each type to be XML serialized in runtime. It impacts the runtime performance. Grant Drake has a blog discusses this in depth.

Want to see it in action? Let's open the Temp folder (one easy way is go to Start->Run, enter "%Temp%" without quotation mark and click OK). Put it in detail view and sort files by date, scroll to the end of the list. Run the client application in Visual Studio and there should be no new files created in the Temp folder. Then run the "ClickOnceBug.Client.application" in the publish folder, 7 new files will show up in the Temp folder.

Workaround

Fortunately, we found that this problem only affect the application project that is being published. ClickOnce does deploy the pre-generated serialization assemblies of the dependent class library projects. Let me illustrate it step by step.

  1. Add class library project called ClickOnceBug.Workaround.
  2. Add a Web Reference to the ClickOnceBug.Workaround referencing to the Service1 in the ClickOnceBug.WebSerivce project. Similar to what we did in step 4 of the "Setup the test environment" section.
  3. Go to the Built tab of ClickOnceBug.Workaround project properties. Turn "On" the "Generate serialization assembly" option. See step 2 of the "Reproduce the problem" section for a screenshot.
  4. Add a project reference to the ClickOnceBug.Client referencing to the ClickOnceBug.Workaround project.
  5. The Web Reference in the ClickOnceBug.Client is no longer useful. Let's remove it but this is not necessary to demonstrate the workaround.
  6. Set the "Generate serialization assembly" option to "Auto" for the ClickOnceBug.Client project.
  7. Add the namespace Workaround (bolded) to the line below that we have added to the Program class earlier.
    Console.WriteLine(new Workaround.localhost.Service1().HelloWorld());
  8. Delete the ClickOnceBug.Client.XmlSerializers.dll file in the ClickOnceBug.Client\bin\Debug folder and delete everything in the publish folder.
  9. Rebuild and publish the solution. We can see the ClickOnceBug.Workaround.XmlSerializers.dll is there in a sub folder of the publish location. 
     WorkaroundPublishWorkaroundBinDebug  

We can verify the effect by running the ClickOnceBug.Client.application when monitoring the Temp folder. No temporary files is generated.