Migrating ASP.NET Web Services to WCF

I recently had to migrate a common ASP.NET web service over to WCF, making sure that clients of the former would still be able to use the latter. There were a couple of things I stumbled across, so I am blogging about the minimal steps I had to perform to get clients of the old ASP.NET web service running with the new WCF one. Let’s use the following simple ASP.NET web service for this tiny tutorial.

[WebService(Namespace = "http://foo.bar.com/Service/Math")]
public class MathAddService : WebService
{
    [WebMethod]
    public int Add(int x, int y)
    {
        // Let's ignore overflows here ;-)
        return x + y;
    }
}

The first thing we need to do is create a new interface which offers the same methods as the web service did and mark it as a service contract. This is required because the WCF endpoints are contract based, i.e. they need such an interface. So we extract the public web service interface of the MathAddService class and decorate it with the WCF attributes:

[ServiceContract(Namespace = "http://foo.bar.com/Service/Math")]
[XmlSerializerFormat]
public interface IMathAddService
{
    [OperationContract(Action = "http://foo.bar.com/Service/Math/Add")]
    int Add(int x, int y);
}

The ServiceContract attribute tells WCF to use the same namespace for the web service as ASP.NET did. If you don’t do this, your clients will not be able to use the migrated service because the namespaces don’t match. The XmlSerializerFormat attribute is used to make sure that WCF uses the standard SOAP format for messages. If you don’t specify this, your clients will likely see strange error messages of mismatching operations / messages. Then, for each method you exposed in the former web service, you need to add the exact same signature here, plus make sure that the OperationContract attribute for each method has the Action property set to ‘/’ . Without this, you’ll get another set of exceptions like ‘operation not defined’.

Now the next step is to implement this interface in a class, but we basically already have this in the former MathAddService class. So we just adapt the class’ definition as follows.

[WebService(Namespace = "http://foo.bar.com/Service/Math")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(Namespace = "http://foo.bar.com/Service/Math")]
public class MathAddService : WebService, IMathAddService
{
    [WebMethod]
    public int Add(int x, int y)
    {
        // Let's ignore overflows here ;-)
        return x + y;
    }
}

As you can see, we’re also adding two new attributes. AspNetCompatibilityRequirements are used to make sure that the new WCF service is really capable of serving old clients. The ServiceBehavior attribute is used to make sure that the WCF hosted service really uses the correct namespace, i.e. the same as the old ASP.NET service used. By the way, you should find all the additional attributes in the System.ServiceModel and System.ServiceModel.Activation namespaces (from the System.ServiceModel assembly).

Now lets get to the configuration of endpoints and bindings for the web service. The following block shows you the new sections in the web.config file for the virtual directory which hosts the WCF service.

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.web>
        <!-- ... -->
    </system.web>
    <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
        <services>
            <service name="MathAddService" behaviorConfiguration="MathAddServiceBehavior">
                <endpoint address=""
                          binding="basicHttpBinding"
                          bindingConfiguration="httpsIwa"
                          bindingNamespace="http://foo.bar.com/Service/Math"
                          contract="IMathAddService"/>
            </service>
        </services>
        <bindings>
            <basicHttpBinding>
                <binding name="httpsIwa">
                    <security mode="Transport">
                        <transport clientCredentialType="Windows" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior name="MathAddServiceBehavior">
                    <serviceMetadata httpsGetEnabled="true" />
                    <serviceDebug httpsHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>

As you can see on lines 19 and 20, we are using HTTPS and IWA for this particular binding, but you should of course make it the same as you had for your ASP.NET service. If you served all requests without HTTP based authentication and without SSL/TLS, then you should stick to that so you don’t break your clients :). You have to make sure that you are offering at least one basicHttpBinding, because that’s what closest matches the ASP.NET SOAP interface.

Finally, we add a new file called ‘MathAddService.svc’ in the virtual directory on IIS with the following contents.

<%@ ServiceHost Service="MathAddService" %>

This will use the implementation of the MathAddService class to serve the request for the IMathAddService interface. Of course your clients will have to be updated to use the new URL now (or you can try a 302 redirect but depending on the client’s policies, this may fail). In case your requests to the new SVC file produce strange results (or send you back the above contents of the file), in the IIS administrative tools make sure that the .svc extension is mapped properly. If it isn’t, you can run the aspnet_regiis.exe tool from the .NET framework to get that done.