Monday, April 8, 2013

Creating Discovery Proxy part 1

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:
Extensions allow us to send the username in discovery metadata
Extensions allow us to send the username in discovery metadata
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.

  1. Open the starting solution Begin.sln from last article Announcements or download it from here
  2. 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”
    Adding a new Console Application Project named “DiscoveryProxy” C# 
    Adding a new Console Application Project named “DiscoveryProxy” (Visual Basic)
    Adding a new Console Application Project named “DiscoveryProxy” (Visual Basic)
      1. 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.
        1. For C# projects, click the Application tab and select the .NET Framework 4 option in the Target Framework dropdown list.
        2. Changing the target framework in C# projects
          Changing the target framework in C# projects
          1. 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.
          2. Changing the target framework in Visual Basic projects
            Changing the target framework in Visual Basic projects
            Click Yes in the Target Framework Change message box to confirm that you want to change the target framework.
            Confirmation dialog
            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.

          3. 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.
          4. Press Ctrl+Shift+B to build the solution.
          5. (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.Net
            b.      System.ServiceModel
            c.       System.ServiceModel.Discovery
            d.      System.Collections.ObjectModel
            e.      Microsoft.Samples.Discovery.Contracts
            Note: For C# users, specific using directives will be added to each file when needed.
          6. 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” (C#)
            Adding a new Class called  “ChatDiscoveryProxy” (Visual Basic)
            Adding a new Class called  “ChatDiscoveryProxy” (Visual Basic)
              1. (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;
              2. Make your class inherit from System.ServiceModel.Discovery.DiscoveryProxy base class.
                C#
                public class ChatDiscoveryProxy : System.ServiceModel.Discovery.DiscoveryProxy
                Visual Basic
                Public Class ChatDiscoveryProxy
                    Inherits Discovery.DiscoveryProxy
              3. 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 attributes
                C#
                [ServiceBehavior(
                    InstanceContextMode = InstanceContextMode.Single,
                    ConcurrencyMode = ConcurrencyMode.Multiple)]
                public class ChatDiscoveryProxy : System.ServiceModel.Discovery.DiscoveryProxy
                Visual Basic
                <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>
                Public Class ChatDiscoveryProxy
                    Inherits Discovery.DiscoveryProxy
                Note: 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.
              4. 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).
              5. (For C# users only) Add the following using directives for the new class.
                C#
                using System.ServiceModel.Discovery;
              6. 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 Basic
                Friend Class ChatServiceCollection
                    Inherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)
              7. 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 Basic
                Friend Class ChatServiceCollection
                    Inherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)

                    Protected Overrides Function GetKeyForItem(ByVal item As EndpointDiscoveryMetadata) As Uri
                        If item Is Nothing Then Throw New ArgumentNullException("item")

                        Return item.Address.Uri
                    End Function

                End Class
              8. 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 Basic
                Public Class ChatDiscoveryProxy
                    Inherits Discovery.DiscoveryProxy

                    Private Shared _cache As New ChatServiceCollection()

                    Friend Shared ReadOnly Property Cache As ChatServiceCollection
                        Get
                            Return _cache
                        End Get
                    End Property
                End Class
              9. 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)
              10.  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 services
                    FindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));

                    if (criteria.IsMatch(endpointDiscoveryMetadata))
                    {
                        endpointDiscoveryMetadata.WriteLine("Adding");
                        Cache.Add(endpointDiscoveryMetadata);
                    }

                    return new CompletedAsyncResult(callback, state);
                }

              11. 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 services
                        FindCriteria 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 Basic
                Protected Overrides Function OnBeginOfflineAnnouncement(ByVal messageSequence As DiscoveryMessageSequence, ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
                    Try
                        If endpointDiscoveryMetadata Is Nothing Then Throw New ArgumentNullException("endpointDiscoveryMetadata")

                        ' We care only about ISimpleChatService services
                        Dim criteria As New FindCriteria(GetType(ISimpleChatService))

                        If criteria.IsMatch(endpointDiscoveryMetadata) Then
                            endpointDiscoveryMetadata.WriteLine("Removing")
                            Cache.Remove(endpointDiscoveryMetadata.Address.Uri)
                        End If
                    Catch e1 As KeyNotFoundException
                        ' No problem if it does not exist in the cache
                    End Try

                    Return New CompletedAsyncResult(callback, state)
                End Function
              12. 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 endpoints
                    var query = from service in Cache
                                where findRequestContext.Criteria.IsMatch(service)
                                select service;

                    // Collection to contain the results of the query
                    var matchingEndpoints = new Collection<EndpointDiscoveryMetadata>();

                    // Execute the query and add the matching endpoints
                    foreach (EndpointDiscoveryMetadata metadata in query)
                    {
                        metadata.WriteLine("\tFound");
                        matchingEndpoints.Add(metadata);
                        findRequestContext.AddMatchingEndpoint(metadata);
                    }

                    return new FindAsyncResult(matchingEndpoints, callback, state);
                }

                Visual Basic
                Protected Overrides Function OnBeginFind(ByVal findRequestContext As FindRequestContext, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
                    If findRequestContext Is Nothing Then Throw New ArgumentNullException("findRequestContext")

                    Console.WriteLine("Find request for contract {0}", findRequestContext.Criteria.ContractTypeNames.FirstOrDefault())

                    ' Query to find the matching endpoints
                    Dim query = From service In Cache
                                Where findRequestContext.Criteria.IsMatch(service)
                                Select service

                    ' Collection to contain the results of the query
                    Dim matchingEndpoints = New Collection(Of EndpointDiscoveryMetadata)()

                    ' Execute the query and add the matching endpoints
                    For Each metadata As EndpointDiscoveryMetadata In query
                        metadata.WriteLine(Constants.vbTab & "Found")
                        matchingEndpoints.Add(metadata)
                        findRequestContext.AddMatchingEndpoint(metadata)
                    Next metadata

                    Return New FindAsyncResult(matchingEndpoints, callback, state)
                End Function

              13. Override the OnEndFind method to complete the find operation.
                C#
                protected override void OnEndFind(IAsyncResult result)
                {
                    FindAsyncResult.End(result);
                }

                Visual Basic
                Protected Overrides Sub OnEndFind(ByVal result As IAsyncResult)
                    FindAsyncResult.End(result)
                End Sub
              14. 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 Basic
                Protected Overrides Function OnBeginResolve(ByVal resolveCriteria As ResolveCriteria, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
                    Return New CompletedAsyncResult(callback, state)
                End Function

                Protected Overrides Function OnEndResolve(ByVal result As IAsyncResult) As EndpointDiscoveryMetadata
                    Return CompletedAsyncResult(Of EndpointDiscoveryMetadata).End(result)
                End Function

                Protected Overrides Sub OnEndOfflineAnnouncement(ByVal result As IAsyncResult)
                    CompletedAsyncResult.End(result)
                End Sub

                Protected Overrides Sub OnEndOnlineAnnouncement(ByVal result As IAsyncResult)
                    CompletedAsyncResult.End(result)
                End Sub
              15. 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.
              16. (For C# users only) Add the following using directives to Program.cs.
                C#
                using System.Net;
                using System.ServiceModel;
                using System.ServiceModel.Discovery;
              17. 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).
                Note
                In 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 Proxy
                    ServiceHost myProxyHost = new
                        ServiceHost(new ChatDiscoveryProxy());

                    string proxyAddress = "net.tcp://" +
                        hostName + ":8001/discoveryproxy";

                    // Create the discovery endpoint
                    DiscoveryEndpoint discoveryEndpoint =
                        new DiscoveryEndpoint(
                            new NetTcpBinding(),
                            new EndpointAddress(proxyAddress));

                    discoveryEndpoint.IsSystemEndpoint = false;

                    // Add UDP Annoucement endpoint
                    myProxyHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());

                    // Add the discovery endpoint
                    myProxyHost.AddServiceEndpoint(discoveryEndpoint);

                    myProxyHost.Open();
                    Console.WriteLine("Discovery Proxy {0}",
                        proxyAddress);

                    return myProxyHost;
                }

                Visual Basic
                Private Function HostDiscoveryEndpoint(ByVal hostName As String) As ServiceHost
                    ' Create a new ServiceHost with a singleton ChatDiscovery Proxy
                    Dim myProxyHost As New ServiceHost(New ChatDiscoveryProxy())

                    Dim proxyAddress = "net.tcp://" & hostName & ":8001/discoveryproxy"

                    ' Create the discovery endpoint
                    Dim discoveryEndpoint As New DiscoveryEndpoint(New NetTcpBinding(), New EndpointAddress(proxyAddress))

                    discoveryEndpoint.IsSystemEndpoint = False

                    ' Add UDP Annoucement endpoint
                    myProxyHost.AddServiceEndpoint(New UdpAnnouncementEndpoint())

                    ' Add the discovery endpoint
                    myProxyHost.AddServiceEndpoint(discoveryEndpoint)

                    myProxyHost.Open()
                    Console.WriteLine("Discovery Proxy {0}", proxyAddress)

                    Return myProxyHost
                End Function
              18. 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 Basic
                Sub Main()
                    Console.Title = "DiscoveryProxy Service"
                    Console.WriteLine("DiscoveryProxy Console Host")
                    Dim hostName = Dns.GetHostName()

                    Using proxyHost As ServiceHost = HostDiscoveryEndpoint(hostName)
                        Console.WriteLine("Press to exit")
                        Console.ReadLine()
                        proxyHost.Close()
                    End Using
                End Sub
              19. Press Ctrl+Shift+B to build the solution.


              No comments:

              Post a Comment

              YallaTech Facebook page