Note: All the code is developed on Visual Studio 2010 RC
EndpointDiscoveryMetadata
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 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 are 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 discovery 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 here: http://www.microsoft.com/downloads/details.aspx?FamilyID=35ec8682-d5fd-4bc3-a51a-d8ad115a8792&displaylang=en
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.Collectionendpoints, 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:
[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
{
// Repository to store EndpointDiscoveryMetadata. A database or a flat file could also be used instead.
DictionaryonlineServices;
public DiscoveryProxyService()
{
this.onlineServices = new Dictionary();
}
// 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(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(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(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(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;
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.
3 comments:
Hello Peretz,,
I would like to know how do i modify probe match message at runtime.i have requirment that i have to added element to the probe match brefore sening to the client,
I have tryed this using message inpector but it not working during probe match the message null, not sure why this is happeing
you have pointer please let me know
Great idea!
I extended your solution to include the complete metadata of an endpoint in the Extensions by using WsdlExporter/WsdlImporter. This reduces the problems you run into when trying to instantiate the Binding on the client side.
Cheers
Sebastian
Great article Mike! Nice read :)
Post a Comment