Search code examples
c#wcfrestwebservicehost

WCF ServiceHosts, ServiceEndpoints and Bindings


I currently am running some WCF REST services in a Windows Service (not IIS), using the WebServiceHost. I have a separate interface and class defined for each service, but I'm having some issues understanding how WebServiceHost, ServiceEndpoint and ServiceContracts can be used together to create a selfhosted solution.

The way that I currently set things up is that I create a new WebServiceHost for each class which implements a service and use the name of the class as part of the URI but then define the rest of the URI in the interface.

[ServiceContract]
public interface IEventsService
{
    [System.ServiceModel.OperationContract]
    [System.ServiceModel.Web.WebGet(UriTemplate = "EventType", ResponseFormat=WebMessageFormat.Json)]
    List<EventType> GetEventTypes();

    [System.ServiceModel.OperationContract]
    [System.ServiceModel.Web.WebGet(UriTemplate = "Event")]
    System.IO.Stream GetEventsAsStream();
 }

public class EventsService: IEventsService
{
     public List<EventType> GetEventTypes() { //code in here }
     public System.IO.Stream GetEventsAsStream() { // code in here }
}

The code to create the services looks like this:

Type t = typeof(EventService);
Type interface = typeof(IEventService);

Uri newUri = new Uri(baseUri, "Events");
WebServicesHost host = new WebServiceHost(t, newUri);
Binding binding = New WebHttpBinding();
ServiceEndpoint ep = host.AddServiceEndpoint(interface, binding, newUri);

This works well and the service endpoint for each service is created at an appropriate url.

http://XXX.YYY.ZZZ:portnum/Events/EventType http://XXX.YYY.ZZZ:portnum/Events/Event

I then repeat for another service interface and service class. I would like to remove the Events in the Url though but if I do that and create multiple WebServiceHosts with the same base URL I get the error:

The ChannelDispatcher at 'http://localhost:8085/' with contract(s) '"IOtherService"' is unable to open its IChannelListener

with the internal Exception of:

"A registration already exists for URI 'http://localhost:8085/'."

I'm trying to understand how the WebServiceHost, ServiceEndpoint and ServiceContract work together to create the ChannelListener.

Do I need a separate WebServiceHost for each class which implements a service? I don't see a way to register multiple types with a single WebServiceHost

Secondly, I'm passing in the interface to the AddServceEndpoint method and I assume that method checks the object for all of the OperationContract members and adds them, the problem is how does the WebServiceHost know which class should map to which interface.

What I would love would be an example of creating a WCF self hosted service which runs multiple services while keeping the interface and the implementation classes separate.


Solution

  • Sounds to me like the problem that you are having is you are trying to register more than one service on the same service URI. This will not work, as you have noticed, each service must have a unique endpoint.

    Unique By

    • IP
    • Domain
    • Port Number
    • Full URL

    Examples

    http://someserver/foo  -> IFoo Service   
    http://someserver/bar  -> IBar Service
    
    http://somedomain  -> IFoo Service   
    http://someotherdomain  -> IBar Service 
    
    http://somedomain:1  -> IFoo Service  
    http://somedomain:2 -> IBar Service  
    

    You get the idea.

    So to directly address your question, if you want more than once service to be at the root url for you site, you will have to put them on different ports. So you could modify your code to be something like

    public class PortNumberAttribute : Attribute
    {
        public int PortNumber { get; set; }
        public PortNumberAttribute(int port)
        {
            PortNumber = port;
        }
    }
    
    [PortNumber(8085)]
    public interface IEventsService
    {
        //service methods etc
    }
    
    
    string baseUri = "http://foo.com:{0}";
    Type iface = typeof(IEventsService);
    PortNumberAttribute pNumber = (PortNumberAttribute)iface.GetCustomAttribute(typeof(PortNumberAttribute));
    Uri newUri = new Uri(string.Format(baseUri, pNumber.PortNumber));
    
    //create host and all that