Two Quick Tips on Web Setup in Visual Studio

How to change the name of the Virtual Directory

By default, when you create a Web Setup Project in Visual Studio, the name of the Virtual Directory it will deploy to is the name that you gave to the Web Setup Project. This can be changed in the property window of the "Web Application Folder" element. At the bottom of the property window, there is a property called VirtualDirectory. If you want the setup to install your application in a subdirectory, you can specify directories using the / character.

WebSetupVirtualDirectory

Simple, but took me some time to find it (I don’t use Web Setup Project very often I have to say).

When I deploy (or when I run the Setup), some of the files are not copied

I had this problem lately. My web project contained a .scv file, and the file was never copied to the IIS virtual directory when using the Deploy… option of Visual Studio, nor was it copied by the Web Setup Project. Actually, this has to do with the Build Action property of the file. If it is set to "None", the file will not be copied. It has to be set to "Content" for the file to be copied as is.

FileBuildAction

When I create a .svc file in my projects, I generally select "Text File" and name it with svc extension. By default, text files have their Build Action set to "None".

Here are two links to articles that contain very useful information on Web Setup Projects:

Send XmlDocument using Windows Communication Foundation

Today, I was stuck with a very simple problem. I wanted to send some XML to as service, using WCF. The proxy generated using svcutil left me with a function accepting an object parameter and returning an object parameter. The service is some kind of echo, simply returning what you sent. I had a sample XML file that contained the message that the service expects to test it with.

I first tested the service by sending the sample as a String. The response object was an XmlDocument. I figured that if what I received was an XmlDocument, I could simply send one as input. Wrong. XmlDocument happens to be not serializable, great. I tired to use XmlDocument.DocumentElement instead, which is of type XmlElement, without any success either.

I then tested XDocument and XElement, but that didn’t work either. Gosh.

Next attempt. I created a schema out of the XML sample file (using Visual Studio 2008, it is very easy. Open the XML file then in the menu, go for "XML" then "Create Schema"). I then used xsd tool to generate a serializable class that could hold the XML, and deserialized the sample file into an instance of that class to give it as to the client’s function. Failure again.

VisualStudioXmlCreateSchema

Now, I was left with only one option, at least from what I could see: directly use the Message class from WCF.

I (re) found an article about Message on the excellent Service Station web site (written by Aaron Skonnard who writes amazing articles and records amazing Webcasts). I didn’t want to go that way at the beginning, but I was left with only that.

First, the code has to be changed. Let’s see how to invoke a service using the generic IRequestChannel interface.

var doc = new XmlDocument();
doc.LoadXml(xmlContent);
var message = Message.CreateMessage(MessageVersion.Soap11, "urn:someRequest", new XmlNodeReader(doc));

var factory = new ChannelFactory<IRequestChannel>("serviceHttpSoap11Endpoint");
var channel = factory.CreateChannel();

var response = channel.Request(message);

channel.Close();

So, that’s pretty easy. Basically, the first step is to create a Message object, and give it a reader to the XmlDocument. I then created a ChannelFactory using the endpoint configuration generated by the previous client. Then I create a channel and use it to sent the message a built with the XmlDocument. Nifty!

But wait, there is one thing that is left to do. In the configuration file, the endpoint still uses the client generated contract, while in the code we use IRequestChannel as the contract. Also, the contract needs the full namespace of the contract. Here is the endpoint’s configuration:

<endpoint address="http://foo.com/bar/service"
          binding="basicHttpBinding"
          contract="System.ServiceModel.Channels.IRequestChannel" name="serviceHttpSoap11Endpoint" />

This one worked, at last. I received a nice reply from the service, however not the one I expected, but this another story and is for my Java colleague to solve… heh

One last remark: strangely, if you set up your client to log the messages sent, the message logged at Service Level will not display the content of the Message body element. Instead, it will simply contain a "… stream …" text.

XmlTrace

However, if you log the message at Transport Level, you will see the full content.