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:
Collection<xmlqualifiedname> ContractTypeNames { get; }
and you can get the address using:
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.
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:
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();
endpointDiscoveryBehavior.Extensions.Add(
new XElement(
"root",
new XElement("BindingData", bindingInfo),
new XElement("LargeData", largeData)));
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:
[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:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DiscoveryProxyService : DiscoveryProxy
{
Dictionary<endpointaddress,> onlineServices;
public DiscoveryProxyService()
{
this.onlineServices = new Dictionary<endpointaddress,>();
}
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);
}
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);
}
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);
}
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);
}
void AddOnlineService(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
{
lock (this.onlineServices)
{
this.onlineServices[endpointDiscoveryMetadata.Address] =
endpointDiscoveryMetadata;
}
PrintDiscoveryMetadata(endpointDiscoveryMetadata, "Adding");
PrintBindingInformation(endpointDiscoveryMetadata);
}
private void PrintBindingInformation
(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
{
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:
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.