Creating a dynamic locator service with WCF 4, WS–Discovery, your own standard endpoints and runtime meta data discovery

Hello everyone, welcome to my third blog post. This time I am preparing for a WCF 4 training and in that training a very cool subject will be explained. Namely the new WCF 4 support for WS – Discovery. This allows us to discover services at runtime. WCF services can query or announce a location by UDP broadcast, or by communicating with a known endpoint directly. The last method is the way to go, since UDP broadcasts everywhere will cloud your network and they perform bad when you are waiting for all the answers to return. Another disadvantage of UDP is it will not traverse outside a subnet, so your services can’t be discovered over the internet, should you wish so.

So what we need is a Proxy service to which services can announce their locations when they come online, this service will store the locations, and modify them when the services go offline. Now clients only need to know the location of this proxy, query it for a location of a service, and then communicate with the service, this proxy service could also be exposed over the internet should you want to, and it will minimize the UDP broadcasts over your network. WCF 4 calls communicating via a proxy service ‘Managed Discovery’, when services communicate directly its called ‘Ad Hoc discovery’.

WCF 4 does not have an implementation of a proxy service, but it does offer a base class named DiscoveryProxyBase. However a complete example about how to inherit from it can be found here http://msdn.microsoft.com/en-us/library/ee354381.aspx so I am not going that way. I also found that method to be tedious and time comsuming so I took a different direction towards implementing our proxy/locator service.

WCF 4 offers a new feature called standard endpoints. Standard endpoints are Endpoints for which the adress, the binding or/and the contract are pre configured for you. Some of the standard endpoints predefine only the contract and the binding like the mexEndpoint (although the binding can be changed on this one), others define only the address, like the dynamic endpoint.. The dynamic endpoint lets you configure the contract  and the binding, but  it will resolve the adress runtime using WS Discovery. So how do we use these standard endpoints? By specifying the ‘kind’ attribute on an endpointconfiguration like this:

 1: <endpoint address="mex" kind="mexEndpoint" endpointConfiguration="" />

Here we have defined an endpoint with contract IMetadataExchange, relative address mex and the default binding for the standard mexEndpoint BasicHttpBinding. As you can guess from the title of this blog post, it is also possible to create your own standard endpoints. More on that later Glimlach. Bare in mind that when you use a standard endpoint you will always need a service class to implement its contract, or you will need to add a service behavior for it to function. In the case of the mexEndpoint we do not want to implement the metadata contract ourselves so we also need to add the serviceMetadata behavior to the service for it to function properly.

The reason I explained standard endpoints, is because they play a big part in using the new WS Discovery features built in WCF 4. Let’s take a look at our Locator service. Other services should be able to announce themselves to it. We could do this by inheriting from the DiscoveryProxyBase, but that’s way too much work for what we want so we will use another class new in WCF 4. WCF 4 introduces the AnnouncementService class. It is a normal WCF service which implements the announcement contract, we really don’t care about the details of those WS-Discovery contracts. This service fires events when a service announces itself, You could host this service in a console app and use its events to do something when a service comes online. The following example is taken from msdn:

 1: public static void Main()
 2:     {
 3:         // Create an AnnouncementService instance
 4:         AnnouncementService announcementService = new AnnouncementService();
 5:         // Subscribe the announcement events
 6:         announcementService.OnlineAnnouncementReceived += OnOnlineEvent;
 7:         announcementService.OfflineAnnouncementReceived += OnOfflineEvent;
 8:         // Create ServiceHost for the AnnouncementService
 9:         using (ServiceHost announcementServiceHost =
 10:             new ServiceHost(announcementService))
 11:         {
 12:             // Listen for the announcements sent over UDP multicast
 13:             announcementServiceHost.AddServiceEndpoint(
 14:                 new UdpAnnouncementEndpoint());
 15:             announcementServiceHost.Open();
 16:             Console.WriteLine("Listening for service announcements.");
 17:             Console.WriteLine("Press <ENTER> to terminate.");
 18:             Console.ReadLine();
 19:         }
 20:     }
 21:     static void OnOnlineEvent(object sender, AnnouncementEventArgs e)
 22:     {
 23:         Console.WriteLine();
 24:         Console.WriteLine("Received an online announcement from {0}:",
 25:         e.EndpointDiscoveryMetadata.Address);
 26:         PrintEndpointDiscoveryMetadata(e.EndpointDiscoveryMetadata);
 27:     }

This is quite a useless way to use this service but a few interesting things happen here, I am mainly talking about line 14. Here a new UdpAnnouncementEndpoint is instantiated, this is another one of WCF 4’s new standard endpoints, here you can see that standard endpoints are nothing more than plain classes, they inherit from ServiceEndpoint, The UDPAnnouncement standard endpoint defines a contract, which the AnnouncementService class implements, it defines a Udp broadcastbinding and it defines an empty address, you cant configure an address because it accepts requests via udp, clients broadcast their requests, no address is involved. Without adding this endpoint hosting the annoucement service would result in nothing really, what is a service without endpoints?

For our locator service we want the announcement functionality, but I do not want to open my own host as I want my locator service to be hosted in WAS.  I want this functionality..and I want to add my own functionality on top of it, so lets inherit our own locator service from the annoucement service! The code below shows the involved code of the locator service.

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Runtime.Serialization;
 5: using System.ServiceModel;
 6: using System.ServiceModel.Web;
 7: using System.Text;
 8: using System.ServiceModel.Discovery;
 9: using System.Xml;
 10: using System.ServiceModel.Description;
 11:
 12: namespace ServiceLocatorService
 13: {
 14:     [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple,InstanceContextMode=InstanceContextMode.Single,IncludeExceptionDetailInFaults=true)]
 15:     public class LocatorService :AnnouncementService, IServiceLocator
 16:     {
 17:         private System.Collections.Concurrent.ConcurrentDictionary<XmlQualifiedName, EndpointDiscoveryMetadata> _services= new System.Collections.Concurrent.ConcurrentDictionary<XmlQualifiedName,EndpointDiscoveryMetadata>();
 18:         public LocatorService()
 19:         {
 20:             this.OnlineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(anService_OnlineAnnouncementReceived);
 21:             this.OfflineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(anService_OfflineAnnouncementReceived);
 22:         }
 23:
 24:         void anService_OfflineAnnouncementReceived(object sender, AnnouncementEventArgs e)
 25:         {
 26:             EndpointDiscoveryMetadata ed;
 27:             _services.TryRemove(e.EndpointDiscoveryMetadata.ContractTypeNames[0], out ed);
 28:         }
 29:
 30:         void anService_OnlineAnnouncementReceived(object sender, AnnouncementEventArgs e)
 31:         {
 32:             // Note this will be called for every endpoint and every endpoint will have 1 contract, a service has more contracts
 33:             if (!_services.ContainsKey(e.EndpointDiscoveryMetadata.ContractTypeNames[0]))
 34:             {
 35:                 _services[e.EndpointDiscoveryMetadata.ContractTypeNames[0]]= e.EndpointDiscoveryMetadata;
 36:             }
 37:         }
 38:
 39:         public string GetAddress(XmlQualifiedName contractname)
 40:         {
 41:             string address = null;
 42:             EndpointDiscoveryMetadata meta=_services[contractname];
 43:             if (meta!=null)
 44:             {
 45:                 address= meta.Address.ToString();
 46:             }
 47:             return address;
 48:         }
 49:     }
 50: }

This code shows a WCF service class which inherits from Announcement service, so it implements the necessary contracts and it implements our own contract which defines one method: GetAddress. Clients will call this method, pass it the XmlQualified name of a service contract and our service will lookup the address based on the contract. My goal is to create a very loose coupling between client and services, the client only needs to know the contract, no addresses and no bindings. The XmlQualified name is what WCF uses to identify a contract, when you generate a WCF proxy usually a contract is also generated on the client side, it’s a newly generated interface, but the XmlQualified name of the contract is the same between client and service. The XmlQualified name of the contract is also the name that you will see in a WSDL document. For simplicity this is the only thing we use to identify a service. Our locator is configured as a singleton service, every client talks to the same instance, this is necessary because we have state in our service, normally you would propably use a database to store the service locations, for simplicity I use a dictionary, Multiple threads can enter this service instance so we must handle our own synchronization to shared resources. Fortunately .Net 4 offers a lot of new collection classes which synchronize for you. In the eventhandlers I simply add and remove the addresses from the dictionary. Are we done with our service? Almost, but.. What is a service without Endpoints? Our service will expose 4 endpoints.

  1. Endpoint with our own locator contract
  2. Endpoint that exposes the discovery contract, this endpoint will be used by services to announce themselves when they come online. You can also have an UdpAnnoucement endpoint, services then will not have to know the location to which they can announce themselves. But to show all the options and for performance reasons I choose the normal Announcement Endpoint, in which we can configure our own address.
  3. UdpDiscoveryEndpoint, this endpoint will be used by a client to discover the locator service, this is the only broadcast we use. You could also choose to give the locator service a well known location, but I want to demonstrate the power of discovery Glimlach.
  4. mexEndpoint, this service will expose its metadata so a client can dynamically query its binding, I want the client to have no knowledge in advance about the binding.

The config is listed below

 1: <?xml version="1.0"?>
 2: <configuration>
 3:
 4:   <system.web>
 5:     <compilation debug="true" targetFramework="4.0" />
 6:   </system.web>
 7:   <system.serviceModel>
 8:     <services>
 9:       <service name="ServiceLocatorService.LocatorService">
 10:         <endpoint address="Locate" binding="wsHttpBinding" bindingConfiguration=""
 11:           contract="ServiceLocatorService.IServiceLocator" />
 12:         <endpoint address="Announce" binding="wsHttpBinding" bindingConfiguration=""
 13:           kind="announcementEndpoint" endpointConfiguration="" />
 14:         <endpoint kind="udpDiscoveryEndpoint" endpointConfiguration="" />
 15:         <endpoint address="mex" kind="mexEndpoint" endpointConfiguration="" />
 16:       </service>
 17:     </services>
 18:     <behaviors>
 19:       <serviceBehaviors>
 20:         <behavior>
 21:           <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
 22:           <serviceMetadata/>
 23:           <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
 24:           <serviceDebug includeExceptionDetailInFaults="false"/>
 25:           <serviceDiscovery></serviceDiscovery>
 26:         </behavior>
 27:       </serviceBehaviors>
 28:     </behaviors>
 29:     <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
 30:   </system.serviceModel>
 31:  <system.webServer>
 32:     <modules runAllManagedModulesForAllRequests="true"/>
 33:   </system.webServer>
 34:
 35: </configuration>

In the config you also see the ServiceDiscovery behavior, this is required for the udpDisco endpoint to function as our service does not implement it’s defined contract, it only implements our own contract and announcement contracts. it is the same principle as the serviceMetadata behavior.

Now that we have a locator service, we must also have an example service which clients can communicate with. This service will announce itself to our locator service. I created a very simple calculator service. It’s code and it’s config are listed below,

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Runtime.Serialization;
 5: using System.ServiceModel;
 6: using System.ServiceModel.Web;
 7: using System.Text;
 8: using Contracts;
 9:
 10: namespace CalculatorService
 11: {
 12:     public class CalculatorService : ICalculator,ICalculatorMaintenance
 13:     {
 14:         public int Add(int i, int i2)
 15:         {
 16:             return i + i2; ;
 17:         }
 18:
 19:         public bool StatusOk()
 20:         {
 21:             //perform checks, everything ok
 22:             return true;
 23:         }
 24:     }
 25: }
 1: <?xml version="1.0"?>
 2: <configuration>
 3:
 4:   <system.web>
 5:     <compilation debug="true" targetFramework="4.0" />
 6:   </system.web>
 7:   <system.serviceModel>
 8:     <services>
 9:       <service name="CalculatorService.CalculatorService">
 10:         <endpoint address="Calculate" binding="wsHttpBinding" bindingConfiguration=""
 11:           contract="Contracts.ICalculator" />
 12:         <endpoint address="Maintenance" binding="wsHttpBinding" bindingConfiguration=""
 13:           contract="Contracts.ICalculatorMaintenance" />
 14:       </service>
 15:     </services>
 16:     <behaviors>
 17:       <serviceBehaviors>
 18:         <behavior name="">
 19:           <serviceMetadata httpGetEnabled="true"/>
 20:           <serviceDebug includeExceptionDetailInFaults="false" />
 21:           <serviceDiscovery>
 22:             <announcementEndpoints>
 23:               <endpoint address="http://localhost/ServiceLocatorService/Service1.svc/Announce"
 24:                 binding="wsHttpBinding" bindingConfiguration="" kind="announcementEndpoint"
 25:                 endpointConfiguration="" />
 26:             </announcementEndpoints>
 27:           </serviceDiscovery>
 28:         </behavior>
 29:       </serviceBehaviors>
 30:     </behaviors>
 31:     <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
 32:   </system.serviceModel>
 33:  <system.webServer>
 34:     <modules runAllManagedModulesForAllRequests="true"/>
 35:   </system.webServer>
 36:
 37: </configuration>

It’s a simple service which exposes two endpoints, one for calling and one for maintenance and it exposes metadata via http get. The reason for the latter will be explained shortly. The interesting stuff is in the config file. We add a serviceDiscovery behavior to the service. We also did this in the locator service to enable UdpDiscovery but here we see another use of this behavior. In this behavior we can configure where we announce ourselves when we come on and offline! We can configure endpoints here which adhere to a certain contract, the most simple and best option is to use WCF 4 standard endpoints for this. I used the announcementEndpoint, with the same configuration as I did when I configured it in the locator service. If the kind attribute here was set to UdpAnnouncement endpoint and also in in the locator service, none of them would have to configure the address, but we want to minimize the ammount of Udp broadcasts. When the service comes online, it will call the configured endpoint on our locator service, the locator service will receive the online event and will store it in its concurrent dictionary.

Now that we have our services, lets take a look at the client side of things. I created a separate dll that clients can use to communicate with the locator service. This dll defines two main entry points for clients to use:

  1. DynamicCannelfactory.
  2. DynamicBindingEndpoint

First we will take a look at the dynamic channel factory. Code listed below:

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Text;
 5: using System.ServiceModel.Channels;
 6: using System.ServiceModel;
 7: using System.ServiceModel.Discovery;
 8: using System.ServiceModel.Description;
 9: using System.Xml;
 10: using System.Text.RegularExpressions;
 11:
 12: namespace DynamicServiceFactory
 13: {
 14:     public static class DynamicChannelFactory
 15:     {
 16:         public const int MAXRETRIES = 1;
 17:         // this will only happen once during te application,or more often when something goes wrong
 18:         private static ServiceLocator.IServiceLocator _locatorProxy=CreateLocatorProxy();
 19:
 20:         public static TContract CreateChannel<TContract>()
 21:         {
 22:             ServiceEndpoint se = GetFullEndpointMetaData<TContract>();
 23:             if (se !=null)
 24:             {
 25:                 return ChannelFactory<TContract>.CreateChannel(se.Binding, se.Address);
 26:             }
 27:             return default(TContract);
 28:
 29:         }
 30:
 31:         public static ServiceEndpoint GetFullEndpointMetaData<TContract>()
 32:         {
 33:             return GetFullEndpointMetaData(typeof(TContract));
 34:         }
 35:         public static ServiceEndpoint GetFullEndpointMetaData(Type contract)
 36:         {
 37:             // next we need an addres, we ask it from the locatorservice, there services will have announced themselves
 38:             string address = GetAddress(contract);
 39:             if (!string.IsNullOrWhiteSpace(address))
 40:             {
 41:                 Match m = Regex.Match(address, @"(.+.svc)");
 42:                 address = m.Groups[0].Value + "?wsdl";
 43:                 //with the address we need to get Full endpoint information, including MetaData about the service such as the binding.
 44:                 ServiceEndpointCollection endpoints = MetadataResolver.Resolve(contract, new Uri(address), MetadataExchangeClientMode.HttpGet);
 45:                 //the resolve filtered endpoints based on contract type so we expect only one endpoint, maybe later we can build 
 46:                 //a preference for a certain binding?
 47:                 return endpoints[0];
 48:             }
 49:             return null;
 50:         }
 51:         private static string GetAddress(Type contract)
 52:         {
 53:             bool error = false;
 54:             int nrRetries = 0;
 55:             string addres = null;
 56:             ContractDescription cd = ContractDescription.GetContract(contract);
 57:             do
 58:             {
 59:                 try
 60:                 {
 61:
 62:                     addres = _locatorProxy.GetAddress(new XmlQualifiedName(cd.Name, cd.Namespace));
 63:                     error = false;
 64:                 }
 65:                 catch (FaultException)
 66:                 {
 67:                     // service was not found
 68:                     return null;
 69:                 }
 70:                 catch (CommunicationException)
 71:                 {
 72:                     // something infra structural went wrong, maybe the locator service moved? Lets discover it again and try again
 73:                     CreateLocatorProxy();
 74:                     error = true;
 75:                     nrRetries++;
 76:                 }
 77:
 78:             } while (error && nrRetries < MAXRETRIES);
 79:             return addres;
 80:         }
 81:
 82:         /// <summary>
 83:         /// Finding via udp is still relatively slow, this will happen only the first time, if there is a well known address of this service
 84:         /// its better to use that instead of locating it dynamically, its also better to define a well known binding for its endpoint.
 85:         /// </summary>
 86:         /// <returns></returns>
 87:         private static ServiceLocator.IServiceLocator CreateLocatorProxy()
 88:         {
 89:             Type locatorContract = typeof(ServiceLocator.IServiceLocator);
 90:             DiscoveryClient dc = new DiscoveryClient(new UdpDiscoveryEndpoint());
 91:             FindCriteria criteria = FindCriteria.CreateMetadataExchangeEndpointCriteria(locatorContract);
 92:             //dont forget to set the max results to one, this is a huge performance improvement
 93:             criteria.MaxResults = 1;
 94:             FindResponse response = dc.Find(criteria);
 95:             if (response.Endpoints.Count > 0)
 96:             {
 97:                 var metaExchangeEndpointData = response.Endpoints[0];
 98:                 ServiceEndpointCollection endpoints = MetadataResolver.Resolve(locatorContract, metaExchangeEndpointData.Address);
 99:                 if (endpoints.Count > 0)
 100:                 {
 101:                     // The Locator service only has one endpoint which support locating
 102:                     return new ServiceLocator.ServiceLocatorClient(endpoints[0].Binding, endpoints[0].Address);
 103:                 }
 104:             }
 105:             return null;
 106:         }
 107:     }
 108: }

The DynamicChannelFactory is not really a factory class, its more a utility class with helper methods to dynamically generate proxies by using our locator service. The first thing that happens is on line 18, as soon as the class loads it will create a wcf proxy to the locator service. Lets hop to line 87. Here the method which creates the locator is defined. Lets walk through it together.
First it gets the Type of the locator service contract, I generated the proxy class using SvcUtil. Then it instantiates the DiscoveryClient class. This class is new in WCF 4 and it is the main class to use when you are querying for services using WS-Discovery. Recall that our locator service defined an UdpDiscoveryEndpoint for client to use? This is where this comes into play. We will use discovery via Udp so we pass the UdpDiscovery  standard endpoint. This is just to show dynamic discovery, its probably better for performance reasons to give the locator service a well known address but this is way cooler Glimlach. Recall also that our locator service exposed a mex endpoint. By using a static method of the FindCriteria class we can create FindCriteria that corresponds with a service that exposes a MexEndpoint and an endpoint which uses our locator contract. We then pass the critera to the find method. The Find method will use a UDP broadcast and our locator service will answer. The returned collection will contain 1 EndpointDiscoveryMetadata instance which will descripe our MexEndpoint. The class in which this data is returned is called EndpointDiscoveryMetaData, but it does not contain all the metadata of an endpoint, only its contract and address, not its binding. So now we have the address of the mex endpoint of our locator service, this is cool because we can use this to query our locator service for its locator endpoint and the binding its currently using. We can do this by using the MetadataResolver class. We resolve the metadata by specifying our locator contract, and with the returned ServiceEndpoint collection we create a new proxy to the locator services locator endpoint.

This is only the first step. This step occurs only once if everything goes ok. We need to have a locator proxy at the start of our application, and then only a new proxy if the locator service moved somewhere else or its binding changed or something.. When we have the locator proxy we can begin resolving service addresses! This starts on line 20. The CreateChannel method is the method for clients to use. This methods gets the full metadata of an endpoint based on contract type only, and then delegates work to the usual wcf channel factory. Lets have look at how the GetFullEndpointMetaData method works. It starts on line 35. It will first need to query our servicelocator service for an address that corresponds with this contract. The GetAddress method does this for us, it has a very simple retry mechanism that regenerates the locator service proxy if anything infrastructural goes wrong. Recall that we need a XmlQualifiedName for a contract to resolve it. We can use the ContractDescription class for this. It’s GetContractMethod generates a name and namespace from which we can create our XmlQualifiedName. With this name we query the locator service and return the address.

After we have the address, we need to determine the binding our service uses. We can only determine this if our services expose metadata, I choose to expose metadata via http get here instead of mex because when you use mex the address of the endpoint can be anything. If we used mex on our regular services those services would also need to have a discoveryendpoint which we could query for the address of their mex endpoints. I don’t want regular services to have discovery endpoints. So http get is used instead. The reason the locator service uses a mex endpoint is this endpoint can be discovered using udp, http get can’t be discovered using udp because it is not an endpoint. We determine the binding by transforming the address to a http get address and again use the MetaDataResolver class to get the full metadata. This part is very dependent on the standard http get address. With the full metadata, we can use the regular channelfactory to create our proxy!

Lets take a look at a simple test client

 1: static void Main(string[] args)
 2:         {
 3:             ICalculator calcService = DynamicChannelFactory.CreateChannel<ICalculator>();
 4:             if (calcService !=null)
 5:             {
 6:                 Console.WriteLine(calcService.Add(1, 2));
 7:                 Console.WriteLine(calcService.Add(1, 3));
 8:                 Console.WriteLine(calcService.Add(1, 4));
 9:                 Console.WriteLine(calcService.Add(1, 5));
 10:                 ICalculatorMaintenance calcMainService = DynamicChannelFactory.CreateChannel<ICalculatorMaintenance>();
 11:                 Console.WriteLine("Service is running {0}",calcMainService.StatusOk()?"Ok":"Not ok");
 12:             }
 13:             else
 14:             {
 15:                 Console.WriteLine("service not found");
 16:             }
 17:         }

Very simple right? The client only needs to know its contract type. I use both endpoints in this code , to show that the first call takes a lot longer than the second one, because during the first one the locator proxy is being resolved using Udp. As simple and cool as this is, would it not be even cooler if we could do this in configuration? Guess what… We can Glimlach. By defining our own standard endpoint. Defining your own standard endpoint involves a couple of steps:

  1. Create a class that inherits from ServiceEndpoint. Give it a constructor which accepts the properties someone can configure on your binding. I created a DynamicBindingEndpoint, on which someone can only configure the contract. The binding and the address will be resolved at runtime by using a method of our DynamicChannelFactory class. Here is how it looks:
     1: public  class DynamicBindingEndpoint:ServiceEndpoint
     2:     {
     3:         // only the contract kan vary, the binding and address will determined at runtime
     4:         public DynamicBindingEndpoint(ContractDescription contract):base(contract)
     5:         {
     6:             ServiceEndpoint ep = DynamicChannelFactory.GetFullEndpointMetaData(contract.ContractType);
     7:             this.Address = ep.Address;
     8:             this.Binding = ep.Binding;
     9:             this.IsSystemEndpoint = false;
     10:         }
     11:
     12:     }
  2. Create a class that inherits from StandardEndpointElement so your standard endpoint can be configured in code. Besides the binding and contract and address some standard endpoints have more properties that can be configured, we have not, but we still need to supply a subclass of StandardEndpointElement. In it I only override two methods, one that will create our endpoint and one that returns the type of our endpoint. The rest I don’t need because we don’t have additional configuration on our endpoint. Code is listed below:
     1: public class DynamicBindingEndpointElement:StandardEndpointElement
     2:    {
     3:        protected override System.ServiceModel.Description.ServiceEndpoint CreateServiceEndpoint(System.ServiceModel.Description.ContractDescription contractDescription)
     4:        {
     5:            return new DynamicBindingEndpoint(contractDescription); ;
     6:        }
     7:
     8:        protected override Type EndpointType
     9:        {
     10:            get { return typeof(DynamicBindingEndpoint); }
     11:        }
     12:
     13:        protected override void OnApplyConfiguration(System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceEndpointElement serviceEndpointElement)
     14:        {
     15:        }
     16:
     17:        protected override void OnApplyConfiguration(System.ServiceModel.Description.ServiceEndpoint endpoint, ChannelEndpointElement channelEndpointElement)
     18:        {
     19:        }
     20:
     21:        protected override void OnInitializeAndValidate(ServiceEndpointElement serviceEndpointElement)
     22:        {
     23:        }
     24:
     25:        protected override void OnInitializeAndValidate(ChannelEndpointElement channelEndpointElement)
     26:        {
     27:        }
     28:    }
  3. Create a class that couples this newly inherited StandardEndpoint element to our DynamicBindingEndpoint by inherting a class from StandardEndpointCollectionElement. This is a generic class which has two type parameters, the standard endpoint type and the element type. Code listed below, it is not very exciting..
     1: public  class DynamicBindingEndpointCollectionElement:StandardEndpointCollectionElement<DynamicBindingEndpoint,DynamicBindingEndpointElement>
     2:    {
     3:    }
  4. Add your assembly to the GAC. I think we all know how to do this Glimlach. Don’t forget the strong name!
  5. Register your standard endpoint in the machine.config like this:
     1: <endpointExtensions>
     2:                 <add name="dynamicEndpoint" type="System.ServiceModel.Discovery.Configuration.DynamicEndpointCollectionElement, System.ServiceModel.Discovery, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     3:                 <add name="discoveryEndpoint" type="System.ServiceModel.Discovery.Configuration.DiscoveryEndpointCollectionElement, System.ServiceModel.Discovery, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     4:                 <add name="udpDiscoveryEndpoint" type="System.ServiceModel.Discovery.Configuration.UdpDiscoveryEndpointCollectionElement, System.ServiceModel.Discovery, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     5:                 <add name="announcementEndpoint" type="System.ServiceModel.Discovery.Configuration.AnnouncementEndpointCollectionElement, System.ServiceModel.Discovery, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     6:                 <add name="udpAnnouncementEndpoint" type="System.ServiceModel.Discovery.Configuration.UdpAnnouncementEndpointCollectionElement, System.ServiceModel.Discovery, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     7:                 <add name="workflowControlEndpoint" type="System.ServiceModel.Activities.Configuration.WorkflowControlEndpointCollectionElement, System.ServiceModel.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     8:                 <add name="webHttpEndpoint" type="System.ServiceModel.Configuration.WebHttpEndpointCollectionElement, System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     9:                 <add name="webScriptEndpoint" type="System.ServiceModel.Configuration.WebScriptEndpointCollectionElement, System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     10:                 <add name="serviceManagementEndpoint" type="Microsoft.ApplicationServer.Hosting.Configuration.ServiceManagementEndpointCollectionElement, Microsoft.ApplicationServer.Hosting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
     11:         <add name="dynamicBindingEndpoint" type="DynamicServiceFactory.DynamicBindingEndpointCollectionElement, DynamicServiceFactory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=516775c6b048e756"/>
     12:             </endpointExtensions>

On the last line you can see our own cool endpoint! The name is what we will use with the kind attribute. The type is the type of the collection class that couples our element to our newly defined endpoint, We did this in step 3. With all this in place we can begin using our newly defined standard endpoint! Screenshots below Glimlach.

image

Our client config now looks like this:

image

Look! No binding or address on the endpoint configuration, only a kind. With this config in place we can use the channel factory to generate proxy at runtime or we can use a SvcUtil generated proxy. Because I have a contract that is shared in a well known dll I will use the default ChannelFactory to generate a proxy.

image

image

And there is the output! It is pretty cool! The only thing the client needs to is that it needs a component which implements a certain contract. No location is required, no binding is required. The client is really decoupled from the services. This also makes it very easy to mock the service in your unit tests!

Conclusion

WCF4 introduces a couple of new features which allow us to create very dynamic and loosely coupled clients and services. Using WS- Discovery and standard endpoints enables us to discover service locations at runtime. This is one step in creating a looser coupled client. Runtime metadata resolving was always in WCF, but together with the new discovery features this really enables us to create clients that don’t even know they are talking to a service, they only have to know that they are talking to a contract that is implemented by ‘something’. It could be a class, a service or even a mock object. The code for this post is attached in a .zip file: ServiceLocatorService