Deploying a WCF Service using a Web Setup Project

With this blog post I hope I can save others some time and besides that it is also nicely stored for me in case I need it again.

For my current project I’m building some WCF services. One of the requirements is to have a MSI to hand-over to the system engineers for deployment.

The setup should:

  1. support deployment of the WCF artifacts
  2. be able to be deployed to whatever website is available on the server
  3. be able to set ASP.NET security to NTLM
  4. be able to gather service settings during setup
  5. be able to change custom AppSettings in the web.config according to the settings

While I’m familiar with Visual Studio Setup projects, I came across some interesting challenges.

Requirement 1 and 2 are easy. The regular Web Setup project is capable of doing that out of the box. If you add a Web Setup project to your solution and specify the “File System on Target Machine”. Put the svc and web.config in the “Web Application Folder” and the output of the WCF project to the “bin” folder. This will deploy your WCF service to the website you specify in the setup wizard.

filesystem[1]

One caveat however is to set the value of the Virtual Directory as it is shown by default when you run the setup. This is by default the name of your WebSetup project. In my case this was <company>.<division>.<Project>.<Subproject>.<Setup>

This is definitely not the virtual directory name you want to see in IIS. This cannot be changed in Visual Studio because it is not shown as a property, you need a text editor like Notepad. So open the project file of the WebSetup project: the ‘vdproj’ file. Search for “VirtualDirectory” and change the value to something suitable.

The 3rd requirement is a bit more difficult. By default the web services are installed with anonymous authentication so to change that, a step in the setup should be appended. After some searching I found out I had to add a so called custom installer. That is a class that inherits the System.Configuration.Install.Installer class. You can override the ‘Install’ and/or ‘Uninstall’ methods to add custom code to the installer.

So I added a CustomInstaller Class Library project to host the CustomInstaller class. Next step was how to modify the authentication of the just installed web service? This can be done using WMI or ADSI and I decided to go for WMI. I already did some things with WMI but now I needed to do more. Unfortunately the documentation is not very ‘user friendly’ and after some trying I decided to look for a ‘WMI query analyzer’ or something like that to get at least an overview of the possible queries. I was very happy to find the WMI Explorer by KS-Soft. It is a free download that exactly does what I needed: give an overview of what is out there regarding classes, instances and properties. A very valuable tool.

I needed a query to access the properties of the WCF service. Therefore I needed to query the “IIsWebVirtualDirSetting” class, based on the virtual directory of my WCF service. But how to get the virtual directory name? The system engineer can change that value so we need to ask the runtime to get it.

By default the runtime keeps some variables that you can retrieve in your code. For WebSetup projects that are:

  • TARGETVDIR – virtual directory to be created
  • TARGETSITE – website where the virtual directory is to be created (site must exist), example: "/LM/W3SVC/1
  • TARGETAPPPOOL – application pool to use (must exist, it won’t be created)

If you supply the parameters (to which I will come back later), you can access the runtime context to get the variables using Context.Parameters. The code below does that and more.

wmicode[1]

I create a ManagementScope for “\localhostrootMicrosoftIISv2” to connect to the local IIS instance. Next I query for the IISWebVirtualDirSettings of my WCF service to get access to the properties. This query is based on the website name and the virtual directory. If there are multiple websites in IIS, we have to get the correct one.

With the following code we can iterate over the result (which should only contain one record) and set the property.

wmicode2[1]

So now the code is in place to set the authentication, but how do these parameters get into the Context object? You have to specify a custom action to your project and the custom installer is the action you need to add.

setupcustomaction[1]

Then you can specify properties on the custom installer and they will end up in the Context object.

setupcustomaction2[1]

As you can see the parameter starts with ‘/’ and the value is in double quotes and square brackets if it is a variable which is gathered during the setup process in the wizard. You can also add plain text like I did with ‘localhost’ in this case. Warning: do think about the double quotes, if forgotten nothing works and there are no messages to tell you why!

Requirement 4 is simply extending the user interface. You can find a very good blog post here so I won’t cover that in detail.

Finally the 5th requirement: set some AppSettings values in the web.config

The blog post I just mentioned also describes how to modify web.config settings from a setup project. There is one difference to my situation and that is that I have to change the configuration file for a process I haven’t loaded the host of. So I cannot use the OpenExeConfiguration method of the ConfigurationManager class. Also the web.config specific method OpenWebConfiguration of the WebConfigurationManager class cannot be used.

Via WMI I was able to find the physical path of the web.config and I was almost thinking about using some XmlDocument class to read and process the settings when I found this forum topic.

It describes exactly what I need. I have to use the OpenMappedWebConfiguration method of the WebconfigurationManager class. Then I can still use the capabilities of this class to modify the settings without having to foul around with XML tags myself. With this in place it’s a piece of cake to change the settings.

modifywebconfig[1]

Looking back everything is very easy, as always, but I had a hard time finding the right information. Also I have my doubts about WMI over ADSI. It seems like the ADSI queries are more intuitively but I’m not sure about that.

If you have a better, easier, quicker or fancier solution, please let me know!

InvalidOperationException with Load testing WCF services using VSTS

This is actually a blog post to share my problems with setting up a simple load test for WCF service testing with Visual Studio Team System 2008 Test Edition.

It looks very simple and it is very simple, if you know the trick.

Just for information purposes I tried to setup a load test for a very simple WCF service. It was a service running in IIS and a unit test project calling it. The unit test I wrote worked just fine, so I thought I could use that for the load test.

I added a new Test item and ran the wizard. So far so good. The first thing that went wrong when I tried to run the load test was that the Load Test database was missing. So I had to create the Load Test Results Repository myself. How to do that is described here, but it actually is nothing more than to run a SQL script. If the repository is created, set the connection string in the ‘Administer Test Controller’ menu.

Ready for the second try, but it failed again with the following error:

System.InvalidOperationException: Could not find default endpoint element that references contract….

It was clear that the load test couldn’t find the WCF client configuration somehow. After quite some searching I finally found this document with the solution.

When you create a new load test, the “Run unit tests in application domain” setting is false by default, however it should be true in my situation.

runsettings2[1]

Like with most issues, once you know what term to search for it appears to be described fairly well. This quote is from this blog, specifying what this setting is doing:

When a unit test is run by itself, a separate application domain is created in the test process for each unit test assembly. There is some overhead associated with marshalling tests and test results across the application domain boundary, so when running unit tests in a load test, the application domain is not created by default. This provides some performance boost in terms of the number of tests per second that the test process can execute before running out of CPU. The only drawback is that if the unit test depends on an app.config file, this doesn’t work without creating the app domain. In this case, you can enable the creation of app domain for the unit tests: in the Load Test editor’s Run Setting’s properties set the property “Run unit tests in application domain” to True.

So after I set the setting to true, everything went smoothly and I must say that load testing is then very easy and cool to do.