Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

WCF 4.0 Service Discovery - Adding the Binding to the Discovery

4.91/5 (4 votes)
2 Jul 2010CPOL3 min read 28.8K  
How to include binding information into WCF 4.0 Discovery

Introduction

With WCF 4.0, a wonderful new feature is added. WCF 4.0 Service Discovery allows us to see what services are running and where they are. However, when I started to look into this feature in detail, I noticed the information you get on services only contains the A (Address) and C (Contract) of the ABC - the address and the contract, but not the binding. So in this article, I will show how to add the binding to the discovery information with little ease. Note: All the code is developed on Visual Studio 2010 RC.

The EndpointDiscoveryMetadata Class

Within the discovery, this class is probably one of the most important ones. This is the information we get when services go up and down. Below you can see that there is no binding information about the service. Notice you can get the contact names, using:

C#
Collection<xmlqualifiedname> ContractTypeNames { get; } 

and you can get the address using:

C#
public EndpointAddress Address { get; set; } 

But, no binding information! When I posted on the forum asking why was it done this way, they told me that the binding is not part of the discovery standard (WS Discovery), they also told me that you can get the binding without problems by simply discovering the Mex endpoint and then issuing a call to get the metadata of the service. But, that is not ideal, you would need to discover the service, and then get its metadata just to know the full ABC of an endpoint (2 network trips). (http://social.msdn.microsoft.com/Forums/en/wcfprerelease/thread/ca6bc4be-bc46-4740-ba1e-dca8cf39aa5f) One key field that EndpointDiscoveryMetadata has is Extensions. So right away, I decided to see how I can fill up these extensions and add my own custom information in there. In this case, it would be binding information.

The Code

The example below is based on the WCF / WF code examples from Microsoft. The code has been modified to add binding information to the discovery metadata. I have used the Service Proxy example for this prototype. You can get the Microsoft sample code from here.

Adding Extensions to the EndpointDiscoveryMetadata

To add an extension to the Discovery Metadata, you need to create a new Endpoint Behavior and add it to the Endpoint.Behaviors collection. The behavior is a special behavior that belongs to the Discovery sub-system, it is called EndpointDiscoveryBehavior. Once you create this behavior, you can add extensions to it using the Extension collection.

C#
endpointDiscoveryBehavior.Extensions.Add 

To make sure that the binding information is attached to the discovery metadata of each endpoint, the best approach was to create a new ServiceBehavior, and add a EndpointDiscoveryBehavior to each endpoint. Below is the code of the Service Behavior:

C#
public class BindingDiscoveryServiceBehavior : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, 
	ServiceHostBase serviceHostBase, 
	System.Collections.ObjectModel.Collection<serviceendpoint> endpoints, 
	BindingParameterCollection bindingParameters)
    {
    
    }
    
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
	ServiceHostBase serviceHostBase)
    {
        var endpoints = serviceDescription.Endpoints;
        
        foreach (ServiceEndpoint endpoint in endpoints)
        {
            var endpointDiscoveryBehavior = new EndpointDiscoveryBehavior();
            
            StringBuilder sb = new StringBuilder();
            
            sb.Append(endpoint.Address); 
            sb.Append(Environment.NewLine);
            sb.Append(endpoint.Binding.Scheme);
            sb.Append(Environment.NewLine);
            sb.Append(endpoint.Binding.Name);
            
            string bindingInfo = sb.ToString();
            string largeData = String.Empty;
            
            StringBuilder sb2 = new StringBuilder();
            for (int i = 0; i < 3000000; i++)
                sb2.Append("Lots of data " + i.ToString() + Environment.NewLine);
                
            largeData = sb2.ToString();
            
            // add the binding information to the endpoint
            endpointDiscoveryBehavior.Extensions.Add(
                new XElement(
                    "root",
                    new XElement("BindingData", bindingInfo),
                    new XElement("LargeData", largeData)));
                    
            // add the extension
            endpoint.Behaviors.Add(endpointDiscoveryBehavior);
        }
    }
    
    public void Validate(ServiceDescription serviceDescription, 
	ServiceHostBase serviceHostBase)
    {
        
    }
}

Note that I have also added a very large data to the extension, just to show that you can pass large amount of data in the Extensions (but you need to increase the limits on the discovery proxy TCP binding). The next step is simple, you just need to add your Service Behavior to your service. You can do this by configuration, or simply applying it as an attribute on the service:

C#
[BindingDiscoveryServiceBehavior]
public class CalculatorService : ICalculatorService

Getting the Information When a Service is "Discovered"

To see the binding information when a service is discovered, I have modified the Service Proxy from the WCF discovery example: When a service is discovered, the OnBeginOnlineAnnouncement is called. Below is the modified implementation:

C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
	ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DiscoveryProxyService : DiscoveryProxy
{
    // Repository to store EndpointDiscoveryMetadata. 
    // A database or a flat file could also be used instead.
    Dictionary<endpointaddress,> onlineServices;
    
    public DiscoveryProxyService()
    {
        this.onlineServices = new Dictionary<endpointaddress,>();
    }
    
    // OnBeginOnlineAnnouncement method is called when a Hello message 
    // is received by the Proxy
    protected override IAsyncResult OnBeginOnlineAnnouncement
	(DiscoveryMessageSequence messageSequence, 
	EndpointDiscoveryMetadata endpointDiscoveryMetadata, 
	AsyncCallback callback, object state)
    {        
        this.AddOnlineService(endpointDiscoveryMetadata);
        return new OnOnlineAnnouncementAsyncResult(callback, state);
    }
    
    protected override void OnEndOnlineAnnouncement(IAsyncResult result)
    {
        OnOnlineAnnouncementAsyncResult.End(result);
    }
    
    // OnBeginOfflineAnnouncement method is called when a 
    // Bye message is received by the Proxy
    protected override IAsyncResult OnBeginOfflineAnnouncement
	(DiscoveryMessageSequence messageSequence, 
	EndpointDiscoveryMetadata endpointDiscoveryMetadata, 
	AsyncCallback callback, object state)
    {            
        this.RemoveOnlineService(endpointDiscoveryMetadata);
        return new OnOfflineAnnouncementAsyncResult(callback, state);
    }
    
    protected override void OnEndOfflineAnnouncement(IAsyncResult result)
    {
        OnOfflineAnnouncementAsyncResult.End(result);
    }
    
    // OnBeginFind method is called when a Probe request message is received by the Proxy
    protected override IAsyncResult OnBeginFind(FindRequestContext findRequestContext, 
	AsyncCallback callback, object state)
    {
        this.MatchFromOnlineService(findRequestContext);
        return new OnFindAsyncResult(callback, state);
    }
    
    protected override void OnEndFind(IAsyncResult result)
    {
        OnFindAsyncResult.End(result);
    }
    
    // OnBeginFind method is called when a Resolve 
    // request message is received by the Proxy
    protected override IAsyncResult OnBeginResolve
	(ResolveCriteria resolveCriteria, AsyncCallback callback, object state)
    {
        return new OnResolveAsyncResult
	(this.MatchFromOnlineService(resolveCriteria), callback, state);
    }
    
    protected override EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result)
    {
        return OnResolveAsyncResult.End(result);
    }
    
    // The following are helper methods required by the Proxy implementation
    void AddOnlineService(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
    {
        lock (this.onlineServices)
        {
            this.onlineServices[endpointDiscoveryMetadata.Address] = 
			endpointDiscoveryMetadata;                
        }
        
        PrintDiscoveryMetadata(endpointDiscoveryMetadata, "Adding");
        // show the binding information
        PrintBindingInformation(endpointDiscoveryMetadata);
    }
    
    private void PrintBindingInformation
	(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
    {
        // Get the binding data
        XElement element = endpointDiscoveryMetadata.Extensions.Elements
			("BindingData").FirstOrDefault();
        string bindingInfo = element.Value;
        
        Console.WriteLine("Binding Data");
        Console.WriteLine(bindingInfo);          
    }
    
    void RemoveOnlineService(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
    {            
        if (endpointDiscoveryMetadata != null)
        {
            lock (this.onlineServices)
            {
                this.onlineServices.Remove(endpointDiscoveryMetadata.Address);         
            }
            
            PrintDiscoveryMetadata(endpointDiscoveryMetadata, "Removing");
        }    
    }
    
    void MatchFromOnlineService(FindRequestContext findRequestContext)
    {
        lock (this.onlineServices)
        {
            foreach (EndpointDiscoveryMetadata 
		endpointDiscoveryMetadata in this.onlineServices.Values)
            {
                if (findRequestContext.Criteria.IsMatch(endpointDiscoveryMetadata))
                {                        
                    findRequestContext.AddMatchingEndpoint(endpointDiscoveryMetadata);
                }
            }
        }
    }
    
    EndpointDiscoveryMetadata MatchFromOnlineService(ResolveCriteria criteria)
    {
        EndpointDiscoveryMetadata matchingEndpoint = null;
        lock (this.onlineServices)
        {
            foreach (EndpointDiscoveryMetadata 
		endpointDiscoveryMetadata in this.onlineServices.Values)
            {
                if (criteria.Address == endpointDiscoveryMetadata.Address)
                {
                    matchingEndpoint = endpointDiscoveryMetadata;
                }
            }
        }
        return matchingEndpoint;
    }
    
    void PrintDiscoveryMetadata(EndpointDiscoveryMetadata endpointDiscoveryMetadata, 
		string verb)
    {
        Console.WriteLine("\n**** " + verb + 
		" service of the following type from cache. ");
        foreach (XmlQualifiedName contractName in 
		endpointDiscoveryMetadata.ContractTypeNames)
        {
            Console.WriteLine("** " + contractName.ToString());
            break;
        }
        Console.WriteLine("**** Operation Completed");
    }
    
    sealed class OnOnlineAnnouncementAsyncResult : AsyncResult
    {
        public OnOnlineAnnouncementAsyncResult(AsyncCallback callback, object state)
            : base(callback, state)
        {
            this.Complete(true);
        }
        
        public static void End(IAsyncResult result)
        {
            AsyncResult.End<ononlineannouncementasyncresult>(result);
        }
    }
    
    sealed class OnOfflineAnnouncementAsyncResult : AsyncResult
    {
        public OnOfflineAnnouncementAsyncResult(AsyncCallback callback, object state)
            : base(callback, state)
        {
            this.Complete(true);
        }
        
        public static void End(IAsyncResult result)
        {
            AsyncResult.End<onofflineannouncementasyncresult>(result);
        }
    }
    
    sealed class OnFindAsyncResult : AsyncResult
    {
        public OnFindAsyncResult(AsyncCallback callback, object state)
            : base(callback, state)
        {
            this.Complete(true);
        }
        
        public static void End(IAsyncResult result)
        {
            AsyncResult.End<onfindasyncresult>(result);
        }
    }
    
    sealed class OnResolveAsyncResult : AsyncResult
    {
        EndpointDiscoveryMetadata matchingEndpoint;
        
        public OnResolveAsyncResult(EndpointDiscoveryMetadata matchingEndpoint, 
		AsyncCallback callback, object state)
            : base(callback, state)
        {
            this.matchingEndpoint = matchingEndpoint;
            this.Complete(true);
        }
        
        public static EndpointDiscoveryMetadata End(IAsyncResult result)
        {
            OnResolveAsyncResult thisPtr = AsyncResult.End<onresolveasyncresult>(result);
            return thisPtr.matchingEndpoint;
        }
    }
}

Notice that I am getting the binding information in the code below:

C#
XElement element = endpointDiscoveryMetadata.Extensions.Elements
		("BindingData").FirstOrDefault(); 
string bindingInfo = element.Value;

Conclusion

You should consider using WsdlExporter/WsdlImporter to serialize the information within an endpoint, instead of trying to serialize it into a custom class as I have done here. My point was to show how you can pass "data", and this data can be anything you want. In my case, I chose to pass binding information. Now I have the binding information, so when I ask for a specific contract, I can get the binding as well as the address back to the client that is requesting the endpoint information. Now we can discover the full A B C and not just the A and C.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)