Introduction to WCF - Part 2 - Self-Hosted WCF Services

02 May 2011

Self-Hosting

Having successfully developed a simple IIS-hosted service, we’ll now take a slightly different approach to hosting which will highlight more of details of the process. We'll develop a new HelloService service and host it in a standard Windows console app. Once that's done we can develop a simple console-based client.

Self-hosting allows any Windows application or service to host WCF services.

Creating the Service

  1. In Visual Studio 2010, create a new Class Library project called HelloService
  2. Delete the auto-generated Class1.cs, then right-click the HelloService project in Solution Explorer and select Add New Item
  3. Add a new WCF Service named Hello:
  4. Adding the service also adds a reference to System.ServiceModel and creates an app.Config file (here I've changed the enpoint address from the default value - see below). The configuration of the service can be done either programmatically or via config files, or with a mixture of the two. In this example we're using config files:
  5. <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <behaviors>
                <serviceBehaviors>
                    <behavior name="">
                        <serviceMetadata httpGetEnabled="true" />
                        <serviceDebug includeExceptionDetailInFaults="false" />
                    </behavior>
                </serviceBehaviors>
            </behaviors>
            <services>
                <service name="HelloService.Hello">
                    <endpoint address="" 
                              binding="wsHttpBinding" 
                              contract="HelloService.IHello">
                        <identity>
                            <dns value="localhost" />
                        </identity>
                    </endpoint>
                    <endpoint address="mex" 
                              binding="mexHttpBinding" 
                              contract="IMetadataExchange" />
                    <host>
                        <baseAddresses>
                            <add baseAddress="http://localhost:9001/" />
                        </baseAddresses>
                    </host>
                </service>
            </services>
        </system.serviceModel>
    </configuration>
    
  6. The important points to note in the app.Config file are as follows:
    • <serviceMetadata httpGetEnabled="true" /> 
      

      configures the service to allow HTTP/GET requests for metadata about the service. This will allow service consumers to use a browser to review service information. It also permits the Visual Studio Add Service Reference feature to auto-generate a proxy for the service
    • <endpoint address="" binding="wsHttpBinding" contract="HelloService.IHello" />
      

      sets up an EndPoint for the Hello service. All services must have at least one endpoint which specifies the "ABC's" of the service (Address, Binding, Contract). Note that the endpoint is representative of the service as a whole, not individual service methods.

      Endpoints are the "where" (address), "how" (binding) and "what" (contract) for a service:

      • Address. Every endpoint must have a unique address that specifies the location of service. In our example the address is not explicitly set. This is because the configuration file later specifies a baseAddress (see below).

        Example addresses include:

        http://myserver:8080/myservice
        http://localhost:999
        net.tcp://server:1001

      • Binding. The binding specifies the protocols to use (the transport and messaging protocol). Transport protocols include HTTP, TCP, named pipes and MSMQ. Messaging encoding/protocols include XML, binary, security, etc.
      • Contract. The contract, here in the form of a .NET CLR interface, specifies what the service provides to clients

    • <endpoint address="mex" binding="mexHttpBinding" ... />
      

      sets up another endpoint, this time the endpoint is for the exchange of metadata (Metadata EXchange) with clients
    • <baseAddresses><add baseAddress="http://localhost:9001/" /> 
      

      specifies the base address for the service. The (normally relative) address specified in the service's endpoint is concatenated with the base address to form an absolute address.

      In our example, because the endpoint address is empty, the full address of the service is http://localhost:9001. Notice that we're not specifying a service file.

      Note also that this address is arbitrary (we could have used for example http://localhost:999/anything/anywhere) as we're self-hosting the service (i.e. the path doesn't actually map to a physical or logical location).

      If the service is hosted in IIS/WAS then the address would be in a more standard format such as http://localhost/SimpleTextService/TextService.svc

  7. Modify the service interface (contract) IHello.cs as follows:
  8. using System.ServiceModel;
    
    namespace HelloService
    {
        [ServiceContract]
        public interface IHello
        {
            [OperationContract]
            string SayHello();
    
            [OperationContract]
            string Reverse(string text);
        }
    }
    

  9. Then modify the service implementation file Hello.cs:
  10. using System.ServiceModel;
    
    namespace HelloService
    {
        public class Hello : IHello
        {
            public string SayHello()
            {
                return "Hello WCF";
            }
    
    
            public string Reverse(string text)
            {
                StringBuilder sb = new StringBuilder();
                for (int i = text.Length - 1; i > -1; i--)
                    sb.Append(text[i]);
    
                return sb.ToString();                        
            }
        }
    }
    

  11. Our service is now complete

Creating the Host

  1. Add a new project to the solution and name it Host
  2. Add a reference to System.ServiceModel
  3. Add a reference to the HelloService project
  4. Copy the app.config file from the HelloService project into the Host project. Then remove (delete) it from the HelloService project
  5. Open the app.config file and modify the endpoint address from the default value to something like http://localhost:n, where n is some arbitrary (but unused) port - i.e. not port 80, etc.
  6. Open Program.cs and modify it as follows:
  7. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    
    namespace Host
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (ServiceHost host = new ServiceHost(typeof(HelloService.Hello)))
                {
                    host.Open();
    
                    Console.WriteLine("Hello service running. [Enter] to close");
                    Console.ReadLine();
    
                    host.Close();
                }
            }
        }
    }
    

  8. Important points to note from Program.cs are as follows
    • ServiceHost host = new ServiceHost(typeof(HelloService.Hello))
      

      The ServiceHost class provides all the necessary code infrastructure to host a WCF service. If you host your service using IIS/WAS then they make use of ServiceHost and configure the necessary properties. If self-hosting, always make use of ServiceHost to host your service.

      In our example all we need to do is provide the type of the service we wish to host, all other configuration details (endpoints, etc.) are setup in the app.config file. If required, configuration may be accomplished programmatically:

      HelloService.IHello proxy = ChannelFactory<HelloService.IHello>.CreateChannel(
          new BasicHttpBinding(), 
          new EndpointAddress("http://localhost:9001/ "));
      
    • The above code entirely replaces the configuration previously achieved through the app.config file. The static ChannelFactory method can be used to create a proxy object. However, this approach does require access to the source code for the service interface. Clearly, xml-based app.config files are much more flexible than hard-coded configuration
    • host.Open() sets the state of the service to be ready to start receiving messages. At this point you can no longer make configuration changes to the service (until you Close() it)

  9. We can now test the service. Right-click the Host project in Solution Explorer and select Set as StartUp Project
  10. Build and run the solution - depending on your environment, you may need to run Visual Studio 2010 with administrator rights
  11. If you see an error similar to the following, there are two additional one-off configuration steps required:
  12. System.ServiceModel.AddressAccessDeniedException: HTTP could not register URL http://+:9001l/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details). ---> System.Net.HttpListenerException: Access is denied

    • The exception is thrown because the URL has not been configured to allow the user to setup listening for incoming requests. This is easily configured from the command-line using (on Windows 7) the netsh.exe utility (full details are here)
    • Open a command-prompt (as an Administrator) and enter the following:
    • netsh http add urlacl url=http://+:9001/ user=machine\administrator

      where machine is the name of the host computer.

    • The above command configures the administrator account to setup services listening on port 9001 on machine. You should also configure the HTTP server API used by WCF to bind to the required port with the following command:
    • netsh http add iplisten ipaddress=0.0.0.0:9001

  13. If you still encounter errors are making the above changes, review your firewall settings (try turning it off temporarily to see if that is the source of the problem)

Creating the Client

  1. Add a new console project named Client to the solution
  2. Add a reference to System.ServiceModel
  3. Add a using System.ServiceModel; statement to Program.cs
  4. We now need to add a Service Reference to HelloService.

    To do that we need to have the service running. Build and run the solution without debugging (Ctrl+F5). The service host has been setup as the default start-up project so the service host should start the service correctly
  5. Once the service is running in the host, switch back to Visual Studio, right-click References in the Client project and select Add Service Reference
  6. Enter the address of the service (http://localhost:9001) and hit Go
  7. You should see that the service's metadata is successfully retrieved:
  8. Name the reference HelloService and hit OK
  9. Open the client Program.cs file and modify it as follows:
  10. namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    // Create an instance of the proxy with the appropriate 
                    // EndPoint configuration name (see app.Config)
                    HelloService.HelloClient proxy = 
                      new HelloService.HelloClient("WSHttpBinding_IHello");
    
                    Console.WriteLine("SayHello(): " + proxy.SayHello());
                    Console.WriteLine("Reverse(): " + proxy.Reverse("Hello"));
                    Console.WriteLine("Press [Enter] to close");
                    Console.ReadLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.ReadLine();
                }
            }
        }
    }
    

  11. Notice that when we create an instance of the service proxy we pass the endpoint configuration name ("WSHttpBinding_IHello") to the constructor.

    In fact, we could have just used the default constructor because the app.config file (created for us by Visual Studio when we added the service reference) only contains a single endpoint. However, it's good practice to explicitly reference a particular endpoint
  12. Now close the service host application window. We're now ready to run both the service host and client. The easiest way to do this is by right-clicking the solution in Solution Explorer and selecting Set StartUp Projects
  13. Set both the host and client projects to run, and make sure that the host is run first by moving it to the top of the list:
  14. Running the solution should now open both the host and client: