2

I've got two completely separate services (along with their contracts) which have completely different dependencies as well as completely different responsibilities. However, things they got in common are:

  • They need to be opened/closed together
  • They share the same base address
  • They have the same binding / transport

Dummy contracts:

public class IFoo {
    void Foo();
}

public class IBar {
    void Bar();
}

Now, what I'd like to do is to host them both in the same service host. I am aware that it's possible to expose both services as endpoints and implement them in the same service type like this:

public class FooBar : IFoo, IBar { }

var host = new ServiceHost(typeof(FooBar));

However I'm looking for a way to do something like this:

public class FooImpl : IFoo { }

public class BarImpl : IBar { }

var host = new ServiceHost();
host.AddEndpoint(typeof(FooImpl);
host.AddEndpoint(typeof(BarImpl);
host.Open();

So I can keep my service implementations nice and tidy, each with their own dependencies instead of a god object for everything.

Anyone has an idea on how to accomplish this?

artganify
  • 683
  • 1
  • 8
  • 23
  • Do you have to host both services in the same ServiceHost, or would you accept having multiple ServiceHost instances each with their own service and endpoint? – Wicher Visser Apr 07 '16 at 10:34
  • @WicherVisser Unfortunately not because all services need to run on the same port. – artganify Apr 07 '16 at 11:22
  • Possible duplicate of [Run WCF ServiceHost with multiple contracts](http://stackoverflow.com/questions/334472/run-wcf-servicehost-with-multiple-contracts) – tom redfern Apr 07 '16 at 11:52
  • @artganify you can have multiple ServiceHosts run on the same port. At least that is what I do for my exposed net.tcp endpoints. – Wicher Visser Apr 07 '16 at 12:16

2 Answers2

4

You can host multiple ServiceHosts, each with their own service and endpoint, all sharing the same base address and port. Here is my implementation, encapsulated into a ServiceHosting class:

public class ServiceHosting<T1, T2>
{
    //Declaration
    protected ServiceHost SelfHost;
    protected string BaseUrlString;
    protected int Port;
    protected string HostUrlString = "";
    protected bool ExtendedBinding;

    //Constructor
    public ServiceHosting(string url, int port, bool extendedBinding = false)
    {
        BaseUrlString = url;
        Port = port;
        ExtendedBinding = extendedBinding;
    }

    //Properties
    protected int Max => int.MaxValue;

    public virtual bool StartService(int port)
    {
        try
        {
            var hostName = System.Net.Dns.GetHostName();

            HostUrlString = $@"net.tcp://{hostName}:{port}{BaseUrlString}"; //GM 10.09.2012: 

            try
            {
                SelfHost = new ServiceHost(typeof(T1), new Uri(HostUrlString));

                var smb = SelfHost.Description.Behaviors.Find<ServiceMetadataBehavior>() ??
                          new ServiceMetadataBehavior() { };
                smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;

                SelfHost.Description.Behaviors.Add(smb);

                var throttleBehavior = new ServiceThrottlingBehavior();
                SelfHost.Description.Behaviors.Add(throttleBehavior);

                var mexUrlString = String.Format(@"net.tcp://{0}:{1}{2}/mex", hostName, port, BaseUrlString);

                // Add MEX endpoint
                SelfHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexTcpBinding(), new Uri(mexUrlString));

                // Add binding
                var binding = ConfigureBinding();

                // Add application endpoint
                SelfHost.AddServiceEndpoint(typeof(T2), binding, "");

                if (ExtendedBinding)
                {
                    foreach (ServiceEndpoint ep in SelfHost.Description.Endpoints)
                    {
                        foreach (OperationDescription op in ep.Contract.Operations)
                        {
                            var dataContractBehavior = op.Behaviors[typeof(DataContractSerializerOperationBehavior)] as DataContractSerializerOperationBehavior;

                            if (dataContractBehavior != null)
                            {
                                dataContractBehavior.MaxItemsInObjectGraph = Max;
                            }
                        }
                    }
                }

                // Open the service host to accept incoming calls
                SelfHost.Open();
            }
            catch (CommunicationException)
            {
                // log
                SelfHost.Abort();
                return false;
            }
            catch (Exception)
            {
                // log
                SelfHost.Abort();
                return false;
            }

        }
        catch (Exception)
        {
            // log
            return false;
        }
        return true;
    }

    private NetTcpBinding BaseConfigureBinding()
    {
        return new NetTcpBinding
        { Security = { Mode = SecurityMode.None }, CloseTimeout = new TimeSpan(0, 0, 0, 5) };
    }

    protected virtual NetTcpBinding ConfigureBinding()
    {
        var binding = BaseConfigureBinding();

        if (ExtendedBinding)
        {
            binding.MaxBufferPoolSize = Max;
            binding.MaxReceivedMessageSize = Max;
            binding.MaxBufferSize = Max;
            binding.MaxConnections = 200; //rdoerig 12-03-2013 default value is 10:
            binding.ListenBacklog = 200; //rdoerig 12-03-2013 default value is 10 : buffer of pending connections 

            binding.ReaderQuotas.MaxDepth = Max;
            binding.ReaderQuotas.MaxStringContentLength = Max;
            binding.ReaderQuotas.MaxArrayLength = Max;
            binding.ReaderQuotas.MaxBytesPerRead = Max;
            binding.ReaderQuotas.MaxNameTableCharCount = Max;

            binding.CloseTimeout = new TimeSpan(0, 0, 10, 0);
            binding.OpenTimeout = new TimeSpan(0, 0, 10, 0);
            binding.ReceiveTimeout = new TimeSpan(0, 0, 10, 0);
            binding.SendTimeout = new TimeSpan(0, 0, 10, 0);

        }

        return binding;
    }

    public bool StopService()
    {
        try
        {
            SelfHost?.Close();
        }
        catch (Exception)
        {
            // log
            return false;
        }
        return true;
    }
}

This can be instantiated like so:

     private readonly ServiceHosting<LoginService, ILoginService> _serviceHostLogin = new ServiceHosting<LoginService, ILoginService>(LoginUrl, true);

And started/stopped like so:

            _serviceHostLogin.StartService();
            _serviceHostLogin.StopService();

To make sure you won't get an error when hosting multiple services, you should configure the URIs for services to be different, e.g.

new ServiceHosting<LoginService, ILoginService>("/Services/LoginService", true);
new ServiceHosting<ConfigService, IConfigService>("/Services/ConfigService", true);
Wicher Visser
  • 1,513
  • 12
  • 21
  • Not sure how this addresses the OPs question - there is still only one service host – tom redfern Apr 07 '16 at 13:21
  • It does not - directly. From his comments I understood that the OP he thought he could not host multiple ServiceHosts for the same address + port. My example shows that is actually possible. – Wicher Visser Apr 07 '16 at 14:36
  • Won't you just get an error when you try to open the second service host on the same physical address and port? I'd be very surpised if this works – tom redfern Apr 07 '16 at 14:58
-1

You can implement both interfaces in same service class and have one endpoint, but with separate contracts:

[ServiceBehavior]
public partial class IntegratedService
{
    // You can implement "base" methods here
}

Then, implement each interface:

public partial class IntegratedService : IFoo
{
   // Implement IFoo interface
}

public partial class IntegratedService : IBar
{
   // Implement IBar interface
}

Hope it helps.

Ricardo Pontual
  • 3,749
  • 3
  • 28
  • 43
  • This is exactly what I don't want, as stated in my question. In your example, you are basically just 'splitting' the classes, but in the end it's still just one class. I need to have different types because of different dependencies I need to inject for each service. – artganify Apr 07 '16 at 11:24
  • @artganify only one service per endpoint is allowed, what you want is like have two different sites with same dns and same port within IIS, you can't. I just suggested that implementation because in "base" class you can provide control for both services as you want, like start some code simultaneously , sorry if is not you're looking for. – Ricardo Pontual Apr 07 '16 at 11:37
  • @artganify I would have to agree with RicardoPontual on this one. Given your requirements, sharing a single implementation is the only supported route WCF offers you. You state that these services are completely ideologically separate, and yet you are imposing artificial constraints on them, such as they need to be open/closed together etc. Regardless of your hosting mechanism, these are separate services and should be treated as such. You can host them separately and then address your peripheral requirements as separate concerns. – tom redfern Apr 07 '16 at 11:49