Want to show your appreciation? Please to my charity.

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.

1 comment:

Anonymous said...

Hello

You make my Day :-))

I had the same problem and had already despaired.
Thank you a lot.

Greetings
J├╝rgen

Post a Comment