• Blog
  • Info Support
  • Career
  • Training
  • International Group
  • Info Support
  • Blog
  • Career
  • Training
  • International Group
  • Search
logo InfoSupport
  • Latest blogs
  • Popular blogs
  • Experts
      • All
      • Bloggers
      • Speakers
  • Meet us
  • About us
    • nl
    • en
    • .NET
    • 3D printing
    • Advanced Analytics
    • Agile
    • Akka
    • Alexa
    • Algorithms
    • Api's
    • Architectuur
    • Artificial Intelligence
    • ATDD
    • Augmented Reality
    • AWS
    • Azure
    • Big Data
    • Blockchain
    • Business Intelligence
    • Chatbots
    • Cloud
    • Code Combat
    • Cognitive Services
    • Communicatie
    • Containers
    • Continuous Delivery
    • CQRS
    • Cyber Security
    • Dapr
    • Data
    • Data & Analystics
    • Data Science
    • Data Warehousing
    • Databricks
    • DataOps
    • Developers life
    • DevOps
    • Digital Days
    • Digital Twin
    • Docker
    • eHealth
    • Enterprise Architecture
    • Event Sourcing
    • Hacking
    • Infrastructure & Hosting
    • Innovatie
    • Integration
    • Internet of Things
    • Java
    • Machine Learning
    • Microservices
    • Microsoft
    • Microsoft Bot Framework
    • Microsoft Data Platform
    • Mobile Development
    • Mutation Testing
    • Open source
    • Pepper
    • Power BI
    • Privacy & Ethiek
    • Python
    • Quality Assistance & Test
    • Quality Assurance & Test
    • Requirements Management
    • Scala
    • Scratch
    • Security
    • SharePoint
    • Software Architecture
    • Software development
    • Software Factory
    • SQL Server
    • SSL
    • Start-up
    • Startup thinking
    • Stryker
    • Test Quality
    • Testing
    • TLS
    • TypeScript
    • Various
    • Web Development
    • Web-scale IT
    • Xamarin
    • All
    • Bloggers
    • Speakers
Home » Creating a WCF 4 dynamic locator service part 2! Discovery extensions
  • Creating a WCF 4 dynamic locator service part 2! Discovery extensions

    • By Chris van Beek
    • .NET 11 years ago
    • .NET 0 comments
    • .NET .NET
    Creating a WCF 4 dynamic locator service part 2! Discovery extensions

    Welcome back again! It has been a while since my last post. Last time I blogged about creating a locator service with WCF 4 discovery features. That article now resides here https://blogs.infosupport.com/author/chrisb/. It will be shown right below this article. In that post I described how to use WCF 4 discovery to create a locator service that other services will use to announce themselves and a client can use to discover a address and binding based on a given contract.

    The only problem with that solution was that discovery doesn’t provide us with Binding information of a service. It will only provide us with an address and the xml qualified name of a contract. To get the binding information I used dynamic meta data resolving, but that meant that all the services that want to be discoverable also needed to provide meta data via http get.

    In this article I wan to explore another feature of discovery: Extensions. The discovery mechanism allows us to add extra data from endpoints to the discovery information. You can do this via the EndPointDiscoveryBehavior. This is an endpoint behavior which you can apply in your config file like below:

    image

    The endpointDiscovery behavior defines an extensions tag. Within this tag you are free to apply whatever extra information you want! Pretty cool right? What would be even cooler? If we could add this extra information dynamically, via code. This can be done by defining a new behavior. We could define a service behavior that in code applies this EndPointDiscovery behavior and fills the extension collection of this discovery behavior with runtime information. What kind of information you might wonder? Well how about the binding information! We can use this to place the assembly qualified typename of the binding that an endpoint is using, we can then use this information on the client side to dynamically instantiate the correct binding! First let’s define a service behavior that will add this binding information to the EndpointDiscoveryBehavior’s extensions collection. The code:

     1: [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]

     

     2:    public class DiscoveryBindingAttribute : Attribute,IServiceBehavior

     

     3:    {

     

     4:        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

    &nbsp;

     5:        {

    &nbsp;

     6:

    &nbsp;

     7:        }

    &nbsp;

     8:        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

    &nbsp;

     9:        {

    &nbsp;

     10:            foreach (var endpoint in serviceHostBase.Description.Endpoints)

    &nbsp;

     11:            {

    &nbsp;

     12:                EndpointDiscoveryBehavior discoBehavior= new EndpointDiscoveryBehavior();

    &nbsp;

     13:                discoBehavior.Extensions.Add(new System.Xml.Linq.XElement("Binding",endpoint.Binding.GetType().AssemblyQualifiedName));

    &nbsp;

     14:                endpoint.Behaviors.Add(discoBehavior);

    &nbsp;

     15:            }

    &nbsp;

     16:        }

    &nbsp;

     17:        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

    &nbsp;

     18:        {

    &nbsp;

     19:

    &nbsp;

     20:        }

    &nbsp;

     21:    }

    The interesting part begins on line 8. Here we iterate over all the endpoints in the service this behavior is applied to. We instantiate a new EndPointDiscoveryBehavior and we add a new XElement to its extensions collection. This XElement represent the custom information we saw earlier in our config file, this is the way to fill this information in code. This is all there is to it! to use this behavior we must apply it to a discoverable service. Like our Calculator service, and our Locator service, both will be discoverable, the Calculator via announcements, the Locator service via UDP.

    Here is the Calculator Service code:

     1: using System;

    &nbsp;

     2: using System.Collections.Generic;

    &nbsp;

     3: using System.Linq;

    &nbsp;

     4: using System.Runtime.Serialization;

    &nbsp;

     5: using System.ServiceModel;

    &nbsp;

     6: using System.ServiceModel.Web;

    &nbsp;

     7: using System.Text;

    &nbsp;

     8: using Contracts;

    &nbsp;

     9:

    &nbsp;

     10: namespace CalculatorService

    &nbsp;

     11: {

    &nbsp;

     12:     [DiscoveryExtensions.DiscoveryBinding]

    &nbsp;

     13:     public class CalculatorService : ICalculator,ICalculatorMaintenance

    &nbsp;

     14:     {

    &nbsp;

     15:

    &nbsp;

     16:         public int Add(int i, int i2)

    &nbsp;

     17:         {

    &nbsp;

     18:             return i + i2; ;

    &nbsp;

     19:         }

    &nbsp;

     20:

    &nbsp;

     21:         public bool StatusOk()

    &nbsp;

     22:         {

    &nbsp;

     23:             //perform checks, everything ok

    &nbsp;

     24:             return true;

    &nbsp;

     25:         }

    &nbsp;

     26:     }

    &nbsp;

     27: }

    Now we have the extra binding information exposed via discovery, so we don’t need meta data exchange anymore, here is the updated web.config file without meta data exposure!

    image

    Here we can see two endpoints. And we can see that this service announces itself to an endpoint. That endpoint is an endpoint on our locator service. When the locator service receives an announcement, it will store the data that comes with it. This data now included our extra binding information that we added in our extensions!

    The locator service now returns two types of info when queried for an contract. It will send back the address of a service that implements that contract, and it will send back the assembly qualified typestring of the binding that that service is using. Here is the code of the locator service:

     1: [DiscoveryBindingAttribute]

    &nbsp;

     2:    [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple,InstanceContextMode=InstanceContextMode.Single,IncludeExceptionDetailInFaults=true)]

    &nbsp;

     3:    public class LocatorService :AnnouncementService, IServiceLocator

    &nbsp;

     4:    {

    &nbsp;

     5:        private System.Collections.Concurrent.ConcurrentDictionary<XmlQualifiedName, EndpointDiscoveryMetadata> _services= new System.Collections.Concurrent.ConcurrentDictionary<XmlQualifiedName,EndpointDiscoveryMetadata>();

    &nbsp;

     6:        public LocatorService()

    &nbsp;

     7:        {

    &nbsp;

     8:            this.OnlineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(anService_OnlineAnnouncementReceived);

    &nbsp;

     9:            this.OfflineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(anService_OfflineAnnouncementReceived);

    &nbsp;

     10:        }

    &nbsp;

     11:

    &nbsp;

     12:        void anService_OfflineAnnouncementReceived(object sender, AnnouncementEventArgs e)

    &nbsp;

     13:        {

    &nbsp;

     14:            EndpointDiscoveryMetadata ed;

    &nbsp;

     15:            _services.TryRemove(e.EndpointDiscoveryMetadata.ContractTypeNames[0], out ed);

    &nbsp;

     16:        }

    &nbsp;

     17:

    &nbsp;

     18:        void anService_OnlineAnnouncementReceived(object sender, AnnouncementEventArgs e)

    &nbsp;

     19:        {

    &nbsp;

     20:            // Note this will be called for every endpoint and every endpoint will have 1 contract, a service has more contracts

    &nbsp;

     21:            if (!_services.ContainsKey(e.EndpointDiscoveryMetadata.ContractTypeNames[0]))

    &nbsp;

     22:            {

    &nbsp;

     23:                _services[e.EndpointDiscoveryMetadata.ContractTypeNames[0]]= e.EndpointDiscoveryMetadata;

    &nbsp;

     24:            }

    &nbsp;

     25:        }

    &nbsp;

     26:

    &nbsp;

     27:        public ServiceDiscoData Discover(XmlQualifiedName contractname)

    &nbsp;

     28:        {

    &nbsp;

     29:            ServiceDiscoData data = null;

    &nbsp;

     30:            EndpointDiscoveryMetadata meta=_services[contractname];

    &nbsp;

     31:            if (meta!=null)

    &nbsp;

     32:            {

    &nbsp;

     33:                data = new ServiceDiscoData() { Adres = meta.Address.ToString(), BindingTypeName = meta.Extensions[0].Value };

    &nbsp;

     34:            }

    &nbsp;

     35:            return data;

    &nbsp;

     36:        }

    &nbsp;

     37:    }

    The cool stuff begins on line 27. Here the service will retrieve its stored announcements information by using the contract name of a service. From this info he will get the address and the binding typestring, it will then wrap this in a response and send it back to the client.

    As in my earlier post, the client is still a dll, this dll will pack a class that will handle the queries to the locator service for us. All a client application needs to do, is to reference our dll and use the DynamicChannelFactory class to create a runtime proxy, all the client application needs to pass is a generic type parameter for the contract type. Our dll will discover the locator service, query it for the information it needs, and then use the WCF ChannelFactory class to instantiate a proxy. To the ChannelFactory class it will pass the binding, and the address. Here is the code of the DynamicChannelFactory class.

     1: public static class DynamicChannelFactory

    &nbsp;

     2:    {

    &nbsp;

     3:        public const int MAXRETRIES = 1;

    &nbsp;

     4:        // this will only happen once during te application,or more often when something goes wrong

    &nbsp;

     5:        private static ServiceLocator.IServiceLocator _locatorProxy = CreateLocatorProxy();

    &nbsp;

     6:

    &nbsp;

     7:        public static TContract CreateChannel<TContract>()

    &nbsp;

     8:        {

    &nbsp;

     9:            var result = GetDiscoData(typeof(TContract));

    &nbsp;

     10:            if (result != null)

    &nbsp;

     11:            {

    &nbsp;

     12:                return ChannelFactory<TContract>.CreateChannel(result.Item2, new EndpointAddress(result.Item1));

    &nbsp;

     13:            }

    &nbsp;

     14:            return default(TContract);

    &nbsp;

     15:

    &nbsp;

     16:        }

    &nbsp;

     17:

    &nbsp;

     18:

    &nbsp;

     19:        public static Tuple<string, Binding> GetDiscoData(Type contract)

    &nbsp;

     20:        {

    &nbsp;

     21:            Binding b = null;

    &nbsp;

     22:            bool error = false;

    &nbsp;

     23:            int nrRetries = 0;

    &nbsp;

     24:            ServiceDiscoData data = null;

    &nbsp;

     25:            ContractDescription cd = ContractDescription.GetContract(contract);

    &nbsp;

     26:            do

    &nbsp;

     27:            {

    &nbsp;

     28:                try

    &nbsp;

     29:                {

    &nbsp;

     30:

    &nbsp;

     31:                    data = _locatorProxy.Discover(new XmlQualifiedName(cd.Name, cd.Namespace));

    &nbsp;

     32:                    error = false;

    &nbsp;

     33:                }

    &nbsp;

     34:                catch (FaultException)

    &nbsp;

     35:                {

    &nbsp;

     36:                    // service was not found

    &nbsp;

     37:                    return null;

    &nbsp;

     38:                }

    &nbsp;

     39:                catch (CommunicationException)

    &nbsp;

     40:                {

    &nbsp;

     41:                    // something infra structural went wrong, maybe the locator service moved? Lets discover it again and try again

    &nbsp;

     42:                    CreateLocatorProxy();

    &nbsp;

     43:                    error = true;

    &nbsp;

     44:                    nrRetries++;

    &nbsp;

     45:                }

    &nbsp;

     46:

    &nbsp;

     47:            } while (error && nrRetries < MAXRETRIES);

    &nbsp;

     48:

    &nbsp;

     49:            if (data != null)

    &nbsp;

     50:            {

    &nbsp;

     51:                b = Activator.CreateInstance(Type.GetType(data.BindingTypeName)) as Binding;

    &nbsp;

     52:                return new Tuple<string, Binding>(data.Adres, b); ;

    &nbsp;

     53:            }

    &nbsp;

     54:            else

    &nbsp;

     55:            {

    &nbsp;

     56:                return null;

    &nbsp;

     57:            }

    &nbsp;

     58:

    &nbsp;

     59:        }

    &nbsp;

     60:

    &nbsp;

     61:        /// <summary>

    &nbsp;

     62:        /// Finding via udp is still relatively slow, this will happen only the first time, if there is a well known address of this service

    &nbsp;

     63:        /// its better to use that instead of locating it dynamically, its also better to define a well known binding for its endpoint.

    &nbsp;

     64:        /// </summary>

    &nbsp;

     65:        /// <returns></returns>

    &nbsp;

     66:        private static ServiceLocator.IServiceLocator CreateLocatorProxy()

    &nbsp;

     67:        {

    &nbsp;

     68:            Type locatorContract = typeof(ServiceLocator.IServiceLocator);

    &nbsp;

     69:            DiscoveryClient dc = new DiscoveryClient(new UdpDiscoveryEndpoint());

    &nbsp;

     70:            FindCriteria criteria = new FindCriteria(locatorContract);

    &nbsp;

     71:            //dont forget to set the max results to one, this is a huge performance improvement

    &nbsp;

     72:            criteria.MaxResults = 1;

    &nbsp;

     73:            FindResponse response = dc.Find(criteria);

    &nbsp;

     74:            if (response.Endpoints.Count > 0)

    &nbsp;

     75:            {

    &nbsp;

     76:                var metaExchangeEndpointData = response.Endpoints[0];

    &nbsp;

     77:

    &nbsp;

     78:                // The Locator service only has one endpoint which support locating

    &nbsp;

     79:                return new ServiceLocator.ServiceLocatorClient((Binding)Activator.CreateInstance(Type.GetType(metaExchangeEndpointData.Extensions[0].Value)), metaExchangeEndpointData.Address);

    &nbsp;

     80:

    &nbsp;

     81:            }

    &nbsp;

     82:            return null;

    &nbsp;

     83:        }

    &nbsp;

     84:    }

    Line 66 is first up. This method will create a locator proxy for us so that we can ask it for service addresses and bindings. The factory will create a locator proxy as soon as it is initialized. it will try to discover the locator service by using udp. Because we also annotated our locator service with our cool new extension attribute, a response by the locator service will also include our XElement with binding information. This information is used to instantiate a proxy that was earlier generated with svcUtil. No ChannelFactory here.

    Later on the GetDiscoData of our DynamicChannelfactory will use the locator proxy to query for service data, You can see this method from line 19. It will try to query, if it fails it will try to generate a new locator proxy and then query again. This method returns a tuple of an adress and a binding. The CreateChannel method is the method that a client should call to dynamically generate a proxy, this will use the WCF ChannelFactory to generate a proxy, by first querying our Locator service for address and binding data.

    That’s it! Pretty cool stuff! Now we only need to know contracts and the rest will be searched for by runtime! Ideal for mocking etc. By using the extension mechanism of Discovery we have now one less method call for every query, and the services don’t have to expose metadata anymore!

    Share this

Chris van Beek

View profile

Related IT training

Go to training website

Related Consultancy solutions

Go to infosupport.com

Related blogs

  • Building a custom Kubernetes operator in C#

    Building a custom Kubernetes operator in C# Willem Meints - 2 months ago

  • Transform SpecFlow Table Column

    Transform SpecFlow Table Column Ronald Bosma - 5 months ago

  • Building a passwordless login flow with WebAuthn and th…

    Building a passwordless login flow with WebAuthn and th… Willem Meints - 7 months ago

Data Discovery Channel

  • Explainable AI - Break open the blackbox

  • Toekomstvaste microservice data architecturen

  • Modern Data Platform

Nieuwsbrief

* verplichte velden

Contact

  • Head office NL
  • Kruisboog 42
  • 3905 TG Veenendaal
  • T +31 318 552020
  • Call
  • Mail
  • Directions
  • Head office BE
  • Generaal De Wittelaan 17
  • bus 30 2800 Mechelen
  • T +32 15 286370
  • Call
  • Mail
  • Directions

Follow us

  • Twitter
  • Facebook
  • Linkedin
  • Youtube

Newsletter

Sign in

Extra

  • Media Library
  • Disclaimer
  • Algemene voorwaarden
  • ISHBS Webmail
  • Extranet
Beheer cookie toestemming
Deze website maakt gebruik van Functionele en Analytische cookies voor website optimalisatie en statistieken.
Functioneel Always active
De technische opslag of toegang is strikt noodzakelijk voor het legitieme doel het gebruik mogelijk te maken van een specifieke dienst waarom de abonnee of gebruiker uitdrukkelijk heeft gevraagd, of met als enig doel de uitvoering van de transmissie van een communicatie over een elektronisch communicatienetwerk.
Voorkeuren
De technische opslag of toegang is noodzakelijk voor het legitieme doel voorkeuren op te slaan die niet door de abonnee of gebruiker zijn aangevraagd.
Statistieken
De technische opslag of toegang die uitsluitend voor statistische doeleinden wordt gebruikt. De technische opslag of toegang die uitsluitend wordt gebruikt voor anonieme statistische doeleinden. Zonder dagvaarding, vrijwillige naleving door uw Internet Service Provider, of aanvullende gegevens van een derde partij, kan informatie die alleen voor dit doel wordt opgeslagen of opgehaald gewoonlijk niet worden gebruikt om je te identificeren.
Marketing
De technische opslag of toegang is nodig om gebruikersprofielen op te stellen voor het verzenden van reclame, of om de gebruiker op een website of over verschillende websites te volgen voor soortgelijke marketingdoeleinden.
Manage options Manage services Manage vendors Read more about these purposes
Voorkeuren
{title} {title} {title}