All
the previous posts in WCF have relied on a well known UDP multicast endpoint for
discovery. The port and multicast address are specified by the WS-Discovery
protocol documentation. The utilization of this multicast discovery is referred
to as ad hoc discovery. Ad hoc discovery is limited to recognizing only
services on the same subnet. Managed discovery allows you to locate services no
matter where they are, as long as they are registered with a discovery proxy.
The following sequential diagram shows how a Discovery Proxy responds on behalf of the target services:
The System.ServiceModel.Discovery namespace
includes a base class to help us in building our Discovery Proxy. To
implement your proxy you will have to override a number of methods provided by
the System.ServiceModel.Discovery.DiscoveryProxy class. These methods
are all asynchronous to guarantee maximum scalability for the server. For this Hands-on
Lab, you will focus on supporting only Announcements in the proxy. When a chat
client goes online or offline the cache of metadata will be updated. Then you
will respond to find requests when a client wants to query the proxy for list
of chat clients.
The following sequential diagram shows how a Discovery Proxy responds on behalf of the target services:
Extensions
allow us to send the username in discovery metadata
|
- Open the starting solution Begin.sln from last article Announcements or download it from here
- Add
a new project of type Console
Application. To do this, in Solution
Explorer right-click the Begin
solution and point to Add and click New Project. Click Windows in either Visual C# or Visual Basic Installed Templates list. In the Templates pane, click Console Application, and make sure
that .NET Framework 4.0 is selected as the target runtime. Finally, set the project's name to DiscoveryProxy, and then
click OK.
Adding a new Console Application Project named “DiscoveryProxy” C# Adding a new Console Application Project named “DiscoveryProxy” (Visual Basic) - Change the target framework of the DiscoveryProxy project to .NET Framework 4. To do this in Solution Explorer, right-click the DiscoveryProxy project and select Properties.
- For C# projects, click the Application tab and select the .NET Framework 4 option in the Target Framework dropdown list.
- For Visual Basic projects, select the Compile tab and click the Advanced Compile Options button. In the Advanced Compiler Settings dialog box select the .NET Framework 4 option of the Target Framework dropdown list and click OK.
- Right-click DiscoveryProxy project and click Add Reference. Using the Projects tab, add a project reference to the DiscoveryChat project. Repeat these steps, using the .NET tab to add a reference to the System.ServiceModel and System.ServiceModel.Discovery libraries.
- Press Ctrl+Shift+B to build the solution.
- (For
Visual Basic users only) Add the following namespaces imports at project
level to be used during the project. To do this, right-click DiscoveryProxy project and select Properties.
Open the References page, and in the Imported namespaces section,
select the following namespaces:a. System.Netb. System.ServiceModelc. System.ServiceModel.Discoveryd. System.Collections.ObjectModele. Microsoft.Samples.Discovery.ContractsNote: For C# users, specific using directives will be added to each file when needed.
- Create
the ChatDiscoveryProxy class. In Solution Explorer, right-click DiscoveryProxy project, point to Add, and then click Class. Type ChatDiscoveryProxy in the Name
box.Adding a new Class called “ChatDiscoveryProxy” (C#)Adding a new Class called “ChatDiscoveryProxy” (Visual Basic)
- (For
C# users only) Add the following using directives for the new class :
C#using System.ServiceModel;using System.ServiceModel.Discovery;using System.Collections.ObjectModel;using Microsoft.Samples.Discovery.Contracts; - Make
your class inherit from System.ServiceModel.Discovery.DiscoveryProxy base class.C#public class ChatDiscoveryProxy : System.ServiceModel.Discovery.DiscoveryProxyVisual BasicPublic Class ChatDiscoveryProxyInherits Discovery.DiscoveryProxy
- In
this post, your class will maintain a thread-safe in-memory cache. For this
reason, you need to make your WCF service instance to be a singleton. And because it is thread safe and you want to
guarantee maximum scalability, you will also allow multiple concurrent
calls. Modify the ChatDiscoveryProxy class signature, to add the following attributesC#[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,ConcurrencyMode = ConcurrencyMode.Multiple)]public class ChatDiscoveryProxy : System.ServiceModel.Discovery.DiscoveryProxyVisual Basic<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>Public Class ChatDiscoveryProxyInherits Discovery.DiscoveryProxyNote: Using an in-memory cache means that if our discovery proxy host shutdowns, all information regarding services is lost. More robust implementations would use durable storage, such as a database; this will insure that service metadata is not lost even when the service is down.
- You
will need to create a thread safe collection class to hold instances of
services you have discovered. Right-click
DiscoveryProxy project, point to Add, and then click Class. Type ChatServiceCollection in the Name box .Note: For more information see, Collections and Synchronization (Thread Safety).
- (For
C# users only) Add the following using directives for the new class.C#using System.ServiceModel.Discovery;
- Mark
your class as internal (C#) or Friend (Visual Basic), and make it inherit
from the SynchronizedKeyedCollection base class.C#internal class ChatServiceCollection : SynchronizedKeyedCollection<Uri, EndpointDiscoveryMetadata>Visual BasicFriend Class ChatServiceCollectionInherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)
- Implement
the GetKeyForItem method of the SynchronizedKeyedCollection class as shown in following
code.C#internal class ChatServiceCollection :SynchronizedKeyedCollection<Uri, EndpointDiscoveryMetadata>{protected override Uri GetKeyForItem(EndpointDiscoveryMetadata item){if (item == null){throw new ArgumentNullException("item");}return item.Address.Uri;}}Visual BasicFriend Class ChatServiceCollectionInherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)Protected Overrides Function GetKeyForItem(ByVal item As EndpointDiscoveryMetadata) As UriIf item Is Nothing Then Throw New ArgumentNullException("item")Return item.Address.UriEnd FunctionEnd Class
- Switch
back to the ChatDiscoveryProxy class
implementation, and add a static ChatServiceCollection
property and its backing field as shown in following code.public class ChatDiscoveryProxy : DiscoveryProxy{private static ChatServiceCollection cache = new ChatServiceCollection();internal static ChatServiceCollection Cache{get { return cache; }}}Visual BasicPublic Class ChatDiscoveryProxyInherits Discovery.DiscoveryProxyPrivate Shared _cache As New ChatServiceCollection()Friend Shared ReadOnly Property Cache As ChatServiceCollectionGetReturn _cacheEnd GetEnd PropertyEnd Class
- There
are several helper classes that you will need to complete the service. These
classes are already developed for simplicity, and you will have to add them to
your solution. To do this, right-click on the DiscoveryProxy project, point to Add and click Existing Item.
Then, browse to the %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Assets\DiscoveryProxy folder, choosing the language
of your preference (C# or VB) and add the following files:a. AsyncResult.cs (C#) or AsyncResult.vb (Visual Basic)a. CompletedAsyncResult.cs (C#) or CompletedAsyncResult.vb (Visual Basic)b. FindAsyncResult.cs (C#) or FindAsyncResult.vb (Visual Basic)c. EndpointDiscoveryMetadataExtensions.cs (C#) or EndpointDiscoveryMetadataExtensions.vb (Visual Basic)
- When an online announcement is received, you need to determine if the service is one that you want to cache. If so, add it to our cache. Override the OnBeginOnlineAnnouncement method inside the ChatDiscoveryProxy class by adding the following code.C#protected override IAsyncResult OnBeginOnlineAnnouncement(DiscoveryMessageSequence messageSequence,EndpointDiscoveryMetadata endpointDiscoveryMetadata,AsyncCallback callback,object state){if (endpointDiscoveryMetadata == null){throw new ArgumentNullException("endpointDiscoveryMetadata");}// We care only about ISimpleChatService servicesFindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));if (criteria.IsMatch(endpointDiscoveryMetadata)){endpointDiscoveryMetadata.WriteLine("Adding");Cache.Add(endpointDiscoveryMetadata);}return new CompletedAsyncResult(callback, state);}
- When
an offline announcement message is received, you want to remove the metadata from
the cache if it is there. Override the OnBeginOfflineAnnouncement
method by adding the following code.C#protected override IAsyncResult OnBeginOfflineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata endpointDiscoveryMetadata, AsyncCallback callback, object state){try{if (endpointDiscoveryMetadata == null){throw new ArgumentNullException("endpointDiscoveryMetadata");}// We care only about ISimpleChatService servicesFindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));if (criteria.IsMatch(endpointDiscoveryMetadata)){endpointDiscoveryMetadata.WriteLine("Removing");Cache.Remove(endpointDiscoveryMetadata.Address.Uri);}}catch (KeyNotFoundException){// No problem if it does not exist in the cache}return new CompletedAsyncResult(callback, state);}Visual BasicProtected Overrides Function OnBeginOfflineAnnouncement(ByVal messageSequence As DiscoveryMessageSequence, ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResultTryIf endpointDiscoveryMetadata Is Nothing Then Throw New ArgumentNullException("endpointDiscoveryMetadata")' We care only about ISimpleChatService servicesDim criteria As New FindCriteria(GetType(ISimpleChatService))If criteria.IsMatch(endpointDiscoveryMetadata) ThenendpointDiscoveryMetadata.WriteLine("Removing")Cache.Remove(endpointDiscoveryMetadata.Address.Uri)End IfCatch e1 As KeyNotFoundException' No problem if it does not exist in the cacheEnd TryReturn New CompletedAsyncResult(callback, state)End Function
- Now
you can override the OnBeginFind
method, which is called when a client issues a Discovery Find request to the
proxy. Here is where you can search the cache of known service endpoints and
reply to the client Find request with any matching endpoints.C#protected override IAsyncResult OnBeginFind(FindRequestContext findRequestContext, AsyncCallback callback, object state){if (findRequestContext == null){throw new ArgumentNullException("findRequestContext");}Console.WriteLine("Find request for contract {0}",findRequestContext.Criteria.ContractTypeNames.FirstOrDefault());// Query to find the matching endpointsvar query = from service in Cachewhere findRequestContext.Criteria.IsMatch(service)select service;// Collection to contain the results of the queryvar matchingEndpoints = new Collection<EndpointDiscoveryMetadata>();// Execute the query and add the matching endpointsforeach (EndpointDiscoveryMetadata metadata in query){metadata.WriteLine("\tFound");matchingEndpoints.Add(metadata);findRequestContext.AddMatchingEndpoint(metadata);}return new FindAsyncResult(matchingEndpoints, callback, state);}Visual BasicProtected Overrides Function OnBeginFind(ByVal findRequestContext As FindRequestContext, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResultIf findRequestContext Is Nothing Then Throw New ArgumentNullException("findRequestContext")Console.WriteLine("Find request for contract {0}", findRequestContext.Criteria.ContractTypeNames.FirstOrDefault())' Query to find the matching endpointsDim query = From service In CacheWhere findRequestContext.Criteria.IsMatch(service)Select service' Collection to contain the results of the queryDim matchingEndpoints = New Collection(Of EndpointDiscoveryMetadata)()' Execute the query and add the matching endpointsFor Each metadata As EndpointDiscoveryMetadata In querymetadata.WriteLine(Constants.vbTab & "Found")matchingEndpoints.Add(metadata)findRequestContext.AddMatchingEndpoint(metadata)Next metadataReturn New FindAsyncResult(matchingEndpoints, callback, state)End Function
- Override
the OnEndFind method to complete the
find operation.C#protected override void OnEndFind(IAsyncResult result){FindAsyncResult.End(result);}Visual BasicProtected Overrides Sub OnEndFind(ByVal result As IAsyncResult)FindAsyncResult.End(result)End Sub
- Override the rest of
the required methods declared abstract in System.ServiceModel.Discovery.DiscoveryProxy class.C#protected override IAsyncResult OnBeginResolve(ResolveCriteria resolveCriteria, AsyncCallback callback, object state){return new CompletedAsyncResult(callback, state);}protected override EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result){return CompletedAsyncResult<EndpointDiscoveryMetadata>.End(result);}protected override void OnEndOfflineAnnouncement(IAsyncResult result){CompletedAsyncResult.End(result);}protected override void OnEndOnlineAnnouncement(IAsyncResult result){CompletedAsyncResult.End(result);}Visual BasicProtected Overrides Function OnBeginResolve(ByVal resolveCriteria As ResolveCriteria, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResultReturn New CompletedAsyncResult(callback, state)End FunctionProtected Overrides Function OnEndResolve(ByVal result As IAsyncResult) As EndpointDiscoveryMetadataReturn CompletedAsyncResult(Of EndpointDiscoveryMetadata).End(result)End FunctionProtected Overrides Sub OnEndOfflineAnnouncement(ByVal result As IAsyncResult)CompletedAsyncResult.End(result)End SubProtected Overrides Sub OnEndOnlineAnnouncement(ByVal result As IAsyncResult)CompletedAsyncResult.End(result)End Sub
- Now you need to modify the Main method to create a ServiceHost for our ChatDiscoveryProxy service. Open Program.cs (C#) or Module1.vb (Visual Basic) in the DiscoveryProxy project.
- (For
C# users only) Add
the following using directives to Program.cs.C#using System.Net;using System.ServiceModel;using System.ServiceModel.Discovery;
- For hosting the ChatDiscoveryProxy service you will create
the DiscoveryEndpoint endpoint for
the service host. Add the following method inside the Program class (C#) or Module1
module (Visual Basic).NoteIn this solution, you will be using TCP port 8001 for our proxy service.
C#private static ServiceHost HostDiscoveryEndpoint(string hostName){// Create a new ServiceHost with a singleton ChatDiscovery ProxyServiceHost myProxyHost = newServiceHost(new ChatDiscoveryProxy());string proxyAddress = "net.tcp://" +hostName + ":8001/discoveryproxy";// Create the discovery endpointDiscoveryEndpoint discoveryEndpoint =new DiscoveryEndpoint(new NetTcpBinding(),new EndpointAddress(proxyAddress));discoveryEndpoint.IsSystemEndpoint = false;// Add UDP Annoucement endpointmyProxyHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());// Add the discovery endpointmyProxyHost.AddServiceEndpoint(discoveryEndpoint);myProxyHost.Open();Console.WriteLine("Discovery Proxy {0}",proxyAddress);return myProxyHost;}Visual BasicPrivate Function HostDiscoveryEndpoint(ByVal hostName As String) As ServiceHost' Create a new ServiceHost with a singleton ChatDiscovery ProxyDim myProxyHost As New ServiceHost(New ChatDiscoveryProxy())Dim proxyAddress = "net.tcp://" & hostName & ":8001/discoveryproxy"' Create the discovery endpointDim discoveryEndpoint As New DiscoveryEndpoint(New NetTcpBinding(), New EndpointAddress(proxyAddress))discoveryEndpoint.IsSystemEndpoint = False' Add UDP Annoucement endpointmyProxyHost.AddServiceEndpoint(New UdpAnnouncementEndpoint())' Add the discovery endpointmyProxyHost.AddServiceEndpoint(discoveryEndpoint)myProxyHost.Open()Console.WriteLine("Discovery Proxy {0}", proxyAddress)Return myProxyHostEnd Function - Modify the Main method as shown in the following
code, to host the ChatDiscoveryProxy
service.C#static void Main(string[] args){Console.Title = "DiscoveryProxy Service";Console.WriteLine("DiscoveryProxy Console Host");string hostName = Dns.GetHostName();using (ServiceHost proxyHost = HostDiscoveryEndpoint(hostName)){Console.WriteLine("Press
to exit" );Console.ReadLine();proxyHost.Close();}}Visual BasicSub Main()Console.Title = "DiscoveryProxy Service"Console.WriteLine("DiscoveryProxy Console Host")Dim hostName = Dns.GetHostName()Using proxyHost As ServiceHost = HostDiscoveryEndpoint(hostName)Console.WriteLine("Pressto exit" )Console.ReadLine()proxyHost.Close()End UsingEnd Sub - Press Ctrl+Shift+B to build the solution.
Changing the target framework in C# projects |
Changing the target framework in Visual Basic projects
|
Confirmation
dialog
|
Note: By default the Console
Application project template shipped with Visual Studio 2010 creates a project
that targets the .NET Framework Client Profile 4. The solution will not compile
if you have projects that target different .NET Framework profiles.
No comments:
Post a Comment