WCF Web Service Hosting Part 2 – netTcpBinding and IIS

This post is about how to host WCF services in IIS with netTcpBinding. I use the same test service and test client as in the previous post.

WCF Service Host

First, let us try it with the WCF Service Host.

1. Add a new endpoint to the service App.config.

<endpoint address=”net.tcp://localhost:8732/Design_Time_Addresses/TestService/Service1/” binding=”netTcpBinding” contract=”TestService.IService1″ />

If you want to expose a metadata exchange endpoint via netTcp, I suggest you add a base address, and have two endpoints with addresses relative to the base:

<add baseAddress=”net.tcp://localhost:8732/Design_Time_Addresses/TestService/Service1/”/>

<endpoint address=”” binding=”netTcpBinding” contract=”TestService.IService1″/>
<endpoint address=”mex” binding=”mexTcpBinding” contract=”IMetadataExchange”/>

2. Add the new endpoint to the client app.config and modify Program.cs:

<endpoint address=”net.tcp://localhost:8732/Design_Time_Addresses/TestService/Service1/”
binding=”netTcpBinding”
contract=”ServiceReference1.IService1″
name=”NetTcpBinding_IService1″ />

Service1Client client = new Service1Client(“NetTcpBinding_IService1”);

IIS

Now about IIS 7.

1. You must add the Non-HTTP Activation feature. Here is a screen-shot from Windows Server 2008:

image

2. You might have to add the net.tcp binding to IIS. Start IIS Manager, select your web site and click on Bindings under Actions. Add a new binding of type net.tcp. Specify “<port number>:*” (without the quotes) as binding information. I chose port 808.

image

3. Enable net.tcp for your application. Select WcfTest (my test application) and click on Advanced Settings. In enabled protocols, specify both http and net.tcp.

image

4. If you are running Windows Firewall, look for an inbound rule called “Windows Communication Foundation Net.TCP Listener Adapter (TCP-In)” which allows traffic on the selected port. If you don’t have it you are going to get an error messages like this:

Could not connect to net.tcp://localhost:808/WcfTest/TestService.Service1.svc. The connection attempt lasted for a time span of 00:00:02.0727927. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:808.

If this is the case, create the rule manually:

image

 

WCF Web Service Hosting Part 1 – wsHttpBinding and IIS

This, and follow up posts, is about hosting WCF web services in IIS using different bindings. This post deals with the simplest scenario – wsHttpBinding.

Create Test Service and Test Client

The first step is to create a web service using Visual Studio 2010. I created a project called “TestService”. The following App.config is generated:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <!– When deploying the service library project, the content of the config file must be added to the host’s
  app.config file. System.Configuration does not support config files for libraries. –>
  <system.serviceModel>
    <services>
      <service name="TestService.Service1" behaviorConfiguration="TestService.Service1Behavior">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8732/Design_Time_Addresses/TestService/Service1/" />
          </baseAddresses>
        </host>
        <!– Service Endpoints –>
        <!– Unless fully qualified, address is relative to base address supplied above –>
        <endpoint address ="" binding="wsHttpBinding" contract="TestService.IService1">
          <!–
              Upon deployment, the following identity element should be removed or replaced to reflect the
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity
              automatically.
          –>
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <!– Metadata Endpoints –>
        <!– The Metadata Exchange endpoint is used by the service to describe itself to clients. –>
        <!– This endpoint does not use a secure binding and should be secured or removed before deployment –>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="TestService.Service1Behavior">
          <!– To avoid disclosing metadata information,
          set the value below to false and remove the metadata endpoint above before deployment –>
          <serviceMetadata httpGetEnabled="True"/>
          <!– To receive exception details in faults for debugging purposes,
          set the value below to true.  Set to false before deployment
          to avoid disclosing exception information –>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

As you can see, VS chose wsHttpBinding. The base address looks kind of funny, but it is used in the WCF Service Host, which is started when you press F5 (Debug). You can view the service in Internet Explorer using http://localhost:8732/Design_Time_Addresses/TestService/Service1/.

After creating a test client and adding a service reference, the following client app.config is generated:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
                    allowCookies="false">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <reliableSession ordered="true" inactivityTimeout="00:10:00"
                        enabled="false" />
                    <security mode="Message">
                        <transport clientCredentialType="Windows" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="Windows" negotiateServiceCredential="true"
                            algorithmSuite="Default" establishSecurityContext="true" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8732/Design_Time_Addresses/TestService/Service1/"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
                contract="ServiceReference1.IService1" name="WSHttpBinding_IService1">
                <identity>
                    <dns value="localhost" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

Add the following code to Main:

Service1Client client = new Service1Client();
Console.WriteLine(client.GetData(1));
client.Close();

Set test client as startup project and press F5.

Host in IIS

Now, to move this service to IIS, perform the following steps:

1. If you like, you can remove the base address in App.config. It is not used by IIS.

2. Publish the service to IIS. Right-click on TestService and select Publish. Create a new web application and call it WCFTest.

image

Now you can test the IIS hosted web service in IE using address http://localhost/WcfTest/TestService.Service1.svc.

If you get an error saying .svc is not recognized you probably installed IIS after WCF. You must run
“C:WindowsMicrosoft.NETFramework64v3.0Windows Communication FoundationServiceModelReg.exe” –i
For details, see IIS Hosted Service Fails.

3. Modify your test client by adding a new endpoint in app.config:

<endpoint address="http://localhost/WCFTest/TestService.Service1.svc" binding="wsHttpBinding" contract="ServiceReference1.IService1" name="IIS_wsHttp"/>

You must now supply the endpoint name to constructor to Service1Client in order for the client to know which endpoint to use:

Service1Client client = new Service1Client("IIS_wsHttp");

Transport/Certificate Authentication and Party Resolution

I recently worked on a solution where partners would call web services, and “transport security” (SSL) would be used for confidentiality and client authentication. In this solution, partner information, like agreement ID, should be looked up. Wouldn’t it be nice if data from the client certificate used for authentication could be used in party resolution? Yes, but I haven’t found a way to make it work.

If I look at the context properties written by the WCF adapter, there are no certificate related. (In ASP and ASP.NET, you can use the CERT_SUBJECT or CERT_SERIALNUMBER server variables.) In other words, it is not even possible to write a custom party resolution component to do the party resolution.

With the SOAP adapter, it is the same story, but there you could at least set up certificate to user mapping in IIS. If you do that, the Windows user ID ends up in the WindowsUser context property, and the built-in party resolution component works. This mapping technique doesn’t seem to work with the WCF adapter, though. The only workaround I can come up with is to use message security instead, where the signature and certificate is part of the SOAP header.

If anyone has another idea, I would be glad to hear it.

WCF Message Security

This is a follow up my previous post “Accessing a BizTalk WCF Service over SSL with Client Certificate Authentication”.

To change from transport to message security, I did the following:

  1. Changed the receive location configuration.

    image

    The service certificate is, I believe, used to sign the response.

  2. Uncommented
    <endpoint name="HttpMexEndpoint" address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" />
    in the service Web.config again.
  3. Changed security mode in App.config:
    <security mode="Message">
      <message clientCredentialType="Certificate" algorithmSuite="Basic256"/>
    </security>
  4. Since the service certificate did not have the correct common name, I also had to provide a DNS identity in App.config:
    <client>
      <endpoint address=”http://…/Orchestrations/WcfService_Orchestrations.svc”
        binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITwoWayAsync"
        contract="ServiceReference1.WcfService_Orchestrations" name="WSHttpBinding_ITwoWayAsync">
        <identity >
          <dns value="…"/>
        </identity>
      </endpoint>
    </client>

Accessing a BizTalk WCF Service over SSL with Client Certificate Authentication

Accessing a WCF Service published by BizTalk over SSL with client certificate authentication proved to be difficult. Here are some of the steps I had to do:

1. Publish the web service using the BizTalk WCF Service Publishing Wizard.

2. Add a service reference in the test client.

3. Change the receive location security mode to Transport and Transport client credential type to Certificate.

image

4. Generate a server certificate for IIS. Using IIS 7 Manager, I generated a self-signed certificate by selecting the server (top node), opening Server Certificates and then clicking on “Create a Self-Signed Certificate…”.

5. Create an https binding. (Select the web site and click on Bindings… on the right.)

6. Create a self-signed client certificate for testing. I used the following commands:

makecert -r -pe -n "CN=Henrik" -b 01/01/2007 -e 01/01/2010 -sky exchange Client.cer -sv Client.pvk
pvk2pfx.exe -pvk Client.pvk -spc Client.cer -pfx Client.pfx

7. Add Client.pfx to the Current User/Personal store and Client.cer to the Local Computer/Trusted People and Trusted Root Certificate Authorities store.

8. Select the application, double click SSL Settings. I used the following settings: Require SSL, require client certificates.

9. Obviously, the endpoint address in the client’s App.config must be changed from http://&#8230; to https://. But it must also be changed to use client certificates:

<bindings>
  <wsHttpBinding>
    <binding …
        <security mode="Transport">
          <transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
          …

10. To choose authentication certificate, you can either add the following code:

myClient.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySerialNumber, …)

or use the following configuration:

<behaviors>
  <endpointBehaviors>
    <behavior name="ClientCertificateBehavior">
      <clientCredentials>
        <clientCertificate findValue="…" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySerialNumber" />
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>
<client>
  <endpoint … behaviorConfiguration="ClientCertificateBehavior" />
  …

11. When I ran the test client, I got an exception. Looking in the application event log, I had the following error: “…Could not find a base address that matches scheme http for the endpoint with binding MetadataExchangeHttpBinding. Registered base address schemes are [https]…”

12. To resolve this, I had to modify the service Web.config. First I commented out
<endpoint name="HttpMexEndpoint" address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" />
and uncommented
<endpoint name="HttpsMexEndpoint" address="mex" binding="mexHttpsBinding" bindingConfiguration="" contract="IMetadataExchange" />
It didn’t work. Now, I got “…The SSL settings for the service ‘None’ does not match those of the IIS ‘Ssl, SslNegotiateCert, SslRequireCert’…” After a great deal of binging, I found that the solution was to comment out both these endpoints. Not so obvious!

DTCTester on 64 bit Windows

To use DTCTester, you must create an ODBC data source. But if you’re on a 64 system and use the control panel to create the data source, you will receive an error when you run DTCTester:
 
SQLSTATE=IM002,Native error=0,msg='[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified’
 
This is because 32 bit applications like DTCTester have a different set of data sources. Run the following command to configure:
 
c:windowssyswow64odbcad32.exe

Then, run the following command:

DTCTester.exe <data source name> dummy

(Normally, the user ID provided doesn’t matter and password can be left out.)