2

I’m working on designing an SOA structure to consolidate numerous WCF webservices. In my experience whenever using web services we are limited to a very flat structure:

WebService - https://services.site.com/service.svc

  • Operation1
  • Operation2
  • etc.

So, in the example above we have a service which has a collection of operations available.

However, we would like a much more hierarchical structure to the operations available. For example:

WebService - https://services.site.com/service.svc

enter image description here

The reason for wanting this is so that we can use dot notation at the client side when coding calls to the service, for example:

  • myService.Clients.CreateClient(params)
  • myService.Clients.Documents.GetDocumentsForClient(params)
  • myService.Products.CreateProduct(params)

Could anyone suggest whether this is possible at all?

DavidReid
  • 449
  • 1
  • 5
  • 21
  • 1
    Yes, this is possible, but I don't think you can do it in a single service. You can however have `Clients` service and a `Products` service, which would strike me as keeping much more in line with the SOA concept. You could possibly use the WCF framework and implement roll your own, but that would be a rather ugly hack, IMO. – Tim Jun 27 '15 at 17:34

1 Answers1

2

I think Tim is correct in his comment. What you are trying to do is highly unorthodox.

Creating a service which exposes a hierarchical structure of unrelated operations is not what services are for. In the same way you wouldn't create a god-object which does 10 different things, to be consumed in-process by binary reference, you shouldn't try to do it out of process.

If the dot notation is important for you on the client side, then you can kind of get what you're looking for by doing the following.

Compose your service endpoints

namespace Clients
{
    [ServiceContract]
    interface IService
    {
        [OperationContract]
        void CreateClient(params p);
    }
}

namespace Clients.Documents
{
    [ServiceContract]
    interface IService
    {
        [OperationContract]
        List<Document> GetDocumentsForClient(Guid clientKey);
    }
}

namespace Products
{
    [ServiceContract]
    interface IService
    {
        [OperationContract]
        void CreateProduct(params p);
    }
}

Consume your service endpoints

var service = new ChannelFactory<Clients.IService>().CreateChannel();

var service = new ChannelFactory<Clients.Documents.IService>().CreateChannel();

var service = new ChannelFactory<Products.IService>().CreateChannel();

As you can see, you are able to consume the relevant service operation by using the fully qualified name of the service contract. This gives you the dot delimited format you require.

With this approach you will have to have your client reference the assemblies containing the service and operation contracts, rather than via a service reference, which will strip out the service namespacing and replace it with locally generated ones. To enable this you should build the service and data contracts into a separate assembly to your service implementation so it can be shared.

...how (to) route their calls to my service(s) - as they wont have used the 'Add Service Reference' functionality of visual studio to wire up the URLs / Endpoints

Further to Tim's comments below, as an alternative you can inject the name of your service's config element from your config file into the ChannelFactory contstructor, as described here.

Community
  • 1
  • 1
tom redfern
  • 30,562
  • 14
  • 91
  • 126
  • Hi Tom, thanks for this. It looks like what I am after. I understand your point re: splitting the service and data contracts into a separate assembly to the service implementation so that it can be shared. My only confusion now is once the client has referenced the shared contracts assembly, how do they route their calls to my service(s) - as they wont have used the 'Add Service Reference' functionality of visual studio to wire up the URLs / Endpoints. – DavidReid Jun 27 '15 at 22:44
  • @user266632 - When you create the `ChannelFactory` instance, you can use the [overloaded constructor](https://msdn.microsoft.com/en-us/library/aa344343%28v=vs.110%29.aspx) that takes a binding and a string for the remote address. It would look something like this: `ChannelFactory() factory = new ChannelFactory(new BasicHttpBinding(), "someaddress");`. Of course, you can create any binding you want and specify the values for that binding before passing the binding into the constructor. – Tim Jun 28 '15 at 01:55
  • Alternatively, in keeping with Tom's answer: `var service = new ChannelFactory(new BasicHttpBinding(), "some address").CreateChannel();`. The advantage of doing it the first way I gave is that you can then cache that `ChannelFactory` instance and create new channels as needed. – Tim Jun 28 '15 at 01:56
  • @user266632 please see addition to answer. – tom redfern Jun 28 '15 at 08:48