In my attempt to learn the basics of Windows Communication Foundation, the WCF support for WS-Discovery is one of the topics I had to attempt, to get a bit familiar with the topic.
There are 2 possible ways of discovery:
- Ad-hoc : UDP multicast discovery over the network. This only works for services that are on the same network as the client.
- Managed: A discovery proxy that works as an mediator to the local network and acts as a service repository for the clients outside of the network
1. Finding a service endpoint dynamically with Ad-Hoc Discovery and UdpDiscoveryEndpoint
For the dynamic service endpoint discovery we will need an udpDiscoveryEndpoint and a DynamicEndpoint
Our solution looks as following:
We have 2 console application projects, called “WCF.Discovery.Client” and “WCF.Discovery.Service”. Make sure both console applications are set on .NET Framework 4.0 and not the client profile framework.
You will need references to System.ServiceModel and System.ServiceModel.Discovery for both console applications.
Our Service project has 2 WCF Services named “Customers” and “Employees”.
The service contract for Customers Service:
The service contract for Employees service:
The Customers service implementation:
The Employees service implementation:
Our services configuration:
We defined our 2 Services, the Employees service and the Customers service. Both services have a normal wsHttpBinding endpoint exposed to the outer world. We want our clients to discover this endpoint dynamically because in the future our endpoint location will definitely change multiple times. If the client references the endpoint hardcoded by configuration, the client applications will have to update their endpoint for our service every time we change the location of our service.
By default, WCF Discovery is not enabled and clients can not just search for a particular service. To enable that clients can search your endpoint dynamically, you need to have a behavior on your service that has <serviceDiscovery> within the behavior specified. If the serviceDiscovery is not specified in the behavior, the discovery will fail, even if you define the udpDiscoveryEndpoints.
To allow clients to search for our services dynamically, we need to add an extra endpoint to our service. This endpoint is an udpDiscoveryEndpoint, which is standard endpoint. We do not have to specify this endpoint contract and location, how the multicast works and so forth, we can just specify and endpoint with the kind attribute set to the udpDiscoveryEndpoint (which is a standard endpoint).
You can configure this standard endpoint udpDiscoveryEndpoint like this, if you would want to change any setting other then the default value:
If you configure the udpDiscoveryEndpoint and the standardEndpoints, you will need to set the EndpointConfiguration at your udpDiscoveryEndpoint to the standardEndpoint name you specified in your standardEndpoints.
This udpDiscoveryEndpoint basically means that the service that has this endpoint specified, allows probe/resolve messages on this endpoint, meaning clients on the network can search for this endpoint dynamically and do not have to hardcore the service location.
The service console application that hosts the services look as following:
Our client console application has a reference to the project “WCF.Discovery.Service”, just for the IEmployees and ICustomers interface, instead of adding a service reference to save some time.
The client will start by creating a DynamicEndpoint, of which it has to pass a ContractDescription of the contract it has to search for. By searching the network for the IEmployees interface, it will be able to find the Employees service endpoint which is listening for Probe messages with the udpDiscoveryEndpoint. The UDP endpoint will return a Resolve that the specified contract has been found. So basically we create a DynamicEndpoint which has to look for a service endpoint with the IEmployees contract on the network. We create a IEmployees channelFactory and pass the dynamicEndpoint as parameter.
When we run the service console application:
And when we run the client console application next:
The dynamicEndpoint we are looking for in code can also be configured by configuration like this:
So we define an endpoint at our <client> named “EmployeesEndpoint” which has a kind attribute set to “dynamicEndpoint”, which is also one of the standard endpoints.
Our client console application code changed to this:
And this will work equally to the DynamicEndpoint we used by code:
Whether you set the dynamicEndpoint by configuration or code is up to preference.
2. Finding a service dynamically with DiscoveryClient and FindCriteria
For this 2nd section I will be using a basicHttpBinding instead of wsHttpBinding and have no custom binding configuration, just to get less configuration. You will see why soon.
Let’s assume our Customer Service is being upgraded. Our Customer Service will support the ICustomer as the IEmployee service contract:
This means that if our client would search the network for the service that has the IEmployees contract it would get 2 results, since the Customer and the Employees service both have an endpoint with the IEmployees contract. Our dynamic endpoint would take the first found endpoint, but perhaps we need the 2nd endpoint. We do not want our dynamic endpoint to just pick 1 of the 2 endpoints randomly, we want to be sure we have the correct endpoint we need. In our case now, we want the client also to invoke the GetEmployees operation from IEmployees contract, but from the endpoint of the Customer service, instead of the Employee service.
To make sure our client knows what endpoint it has to take from the multiple IEmployee endpoints it would find, we will add some extra parameter for the search, so we definitely get the corrent endpoint back, even if the contract we look for is found multiple times on the network
We change our Customer service configuration so it also supports the IEmployee contract at an endpoint:
Notice that both the endpoints have the same behaviorConfiguration, an endpoint behavior called “CustomerEndpointBehavior”:
We defined an endpoint behavior for our Employees service endpoint and for our Customers service endpoints. Our endpoint behavior has an EndpointDiscovery setting, which defines a scope for both of them.
The scope is an extra parameter we can use to use at the client to find an endpoint with the IEmployee contract (of which there are 2 running, one on the Customers service and one on the Employees service) and the endpoint needs to have a scope of “http://robbincremers.me/customers”. If we combine the IEmployee contract and the scope, we will only get 1 endpoint back matching these criteria, instead of 2. We will be sure we got the correct IEmployee contract endpoint of the Customers service.
All our service endpoints reference to the according endpoint behavior:
The complete service configuration looks like this now:
Now we need to change our client console application. We need to make a change that we look for an endpoint with the IEmployee contract and also look for a scope, so we can filter the correct IEmployee endpoint.
To find the needed endpoint dynamically by code:
We create a DiscoveryClient with an UdpDiscoveryEndpoint and define an FindCriteria with the type of the service contract we want to find an endpoint for. We also define a scope at the FindCriteria, which takes an uri with the url we chose at the service for the customer endpoint. After defining the criteria to search for an endpoint, you can execute the search by the Find method on the discovery client which returns a FindResponse, on which you can look if any endpoints are returned. If there are, it means an endpoint was found with the service contract IEmployees and the endpoint had a discovery scope of “http://robbincremers.me/customers”.
Might be interested to set the Duration property at the FindCriteria to not wait too long before a response is returned.
findCriteria.Duration = new TimeSpan(0, 0, 0, 3);
When running our service console application:
Notice we have a secondary endpoint running now for the Customers service which has as host address the localhost:8009. So our IEmployees service contract will be found at 2 endpoints within the network.
When we run our client console application:
I guess we can say this worked as intended. We wanted to invoke the GetEmployees operation from an endpoint where the service contract is IEmployees and with a scope of “http://robbincremers.me/customers”.
Now let’s change the scope in our FindCriteria to an Uri of “http://robbincremers.me/employees”. If our setup is working as it should, the GetEmployees operation should be invoked at the Employees service now and not the Customers Service:
Our Employees service implementations looks like this:
If we now run our console applications:
As intended, our proxy invoked the GetEmployees() operation of the Employees service endpoint, while before it invoked the operation of the Customers service endpoint.
It is also possible to configure the FindCriteria and the discovery client into configuration instead of code-behind:
Note we configure an endpoint configuration for our dynamicEndpoint, in which we can configure the <discoveryClientSettings> node which also allows us to set the FindCriteria, just as we did in the code-behind. We define a scope and we define the name of the service contract we are looking for with our FindCriteria. The client console code would look like this then:
When running the client with the configuration:
We now used the ScopeMatchBy by exact match. But a scenario where you have 10 services with an endpoint with the IEmployee service contract is possible aswell. All those 10 endpoints have an endpoint behavior where you define the scope as http://www.mycompany.com/Europe/… Then you could also use a ScopeMatchBy by prefix, which would get all the endpoints with IEmployee service contract and that have http://www.mycompany.com/Europe/ as part of their scope. Then you could iterate through the 10 endpoints and for each endpoint invoke the same operation. Could look similar to some sort of publish/subscribe scenario.
Any suggestions, remarks or improvements are always welcome.
If you found this information useful, make sure to support me by leaving a comment.
Cheers and have fun,