Multiple Service Contracts and Indigo

In this ninth part of a ten-part series focusing on the Windows Communication Foundation (WCF), aka Indigo, you'll learn about endpoints, implementing multiple contracts on a service, hosting two services with multiple contracts, and more. This article is excerpted from chapter 1 of the book Learning WCF A Hands-on Guide, written by Michele Leroux Bustamante (O'Reilly, 2007; ISBN: 0596101627). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 4
May 08, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Exposing Multiple Service Endpoints

So far in this chapter, I have shown you different ways to create services, how to expose a service endpoint and metadata exchange endpoint, how to generate client proxies, how to work with metadata, and how to configure service behaviors. In this section, I'll place the emphasis on endpoints, binding configuration, and allocation of assemblies for a more complex solution.

WCF includes a number of standard bindings that allow you to quickly configure a service for a particular set of protocols. Clients must use compatible protocols when they communicate with each endpoint. Services can expose multiple endpoints for the same service contract in order to expose functionality over different protocols. For example, a service may be called by internal clients over TCP, but by external clients over HTTP. In addition to supporting different protocols, internal and external clients may not have access to the same service contracts. Some operations may be allowed only by clients within the domain, while others are publicly available to remote clients on the Internet.

In this lab you will configure multiple endpoints for a service to support different endpoint and binding configurations. In the process you'll explore the following concepts:

  1. Hosting multiple services
  2. Configuring multiple endpoints for a service
  3. Accessing a service from a Windows client application
  4. Initializing proxies from multiple endpoint and binding configurations
  5. Comparing proxy generation to sharing types between services and clients

Lab: Hosting Multiple Services and Sharing Types


In this lab, you'll modify an existing solution to implement a service contract and an administrative contract on two distinct services. You'll then host each service in the same host process, a console application. An internal client presumed to be behind the firewall will consume each service using network protocols such as TCP and named pipes. This client will have access to service operations exposed by the service contract and administrative contract. An external client, presumed to be accessing the service over the Internet will have access only to operations exposed by the service contract over HTTP. The internal client will share class libraries to access service contracts, while the external client will use traditional methods for generating service proxies.

Implementing multiple contracts on a service

In this section, you're going to implement the predefined service contracts on two distinct services. Each service will expose two contracts: one for business functionality core to the service, the other for administrative functionality. Both services will implement the same administrative contract. This illustrates an example of contract factoring for reuse.

  1. Start by opening the solution <YourLearningWCFPath>\Labs\Chapter1\ MultiContractService\MultiContractService.sln. This solution contains several shell projects, including a service library, a host, and two Windows client applications as follows:

    BusinessServiceContracts
      
    A class library containing three contracts: 
       IAdmin,IServiceA, andIServiceB. 
       IAdmin
    defines administrative operations.
       IServiceA
    andIServiceBrespectively
          describe functionality to be exposed by
       ServiceA
    andServiceB.

    BusinessServices
      
    A class library that will contain two services:
          ServiceAandServiceB.

    Host
          A console application that will host
       ServiceA
    andServiceB.

    InternalClient
      
    A windows client application that will access
          services behind the firewall.

    ExternalClient 
          A windows client application that will access
          services over the Internet.

    Putting service contracts into a separate class library facilitates sharing metadata with client applications when you own both sides of the development effort.
  2. The first thing you'll do is provide an implementation forServiceA, which is located in theBusinessServicesclass library. First, take a look at the contracts you will implement. Go to theBusinessServiceContractsproject and open IServiceA.cs; you'll see a contract with two operations. Now open IAdmin.cs and you'll see another contract with two different operations.

    To implement these contracts, go to theBusinessServicesproject. First, add a reference to theBusinessServiceContractsproject so you can access the contracts it defines. Then open ServiceA.cs and add ausing statement for theBusinessServiceContracts namespace, as shown here:

      using BusinessServiceContracts;

    Modify the definition ofServiceAso that it derives fromIServiceAandIAdmin as follows:

      public class ServiceA : IServiceA, IAdmin

    Implement both contracts implicitly. You can use a shortcut by hovering your mouse overIServiceAand using the smart tag to select "Implement interface IServiceA," as shown in Figure 1-29.


    Figure 1-29.
      Using smart tags to implement an interface


    Complete the implementation by adding the code shown in Example 1-17.

    Example 1-17. Implementation for ServiceA

    public class ServiceA : IServiceA, IAdmin
    {
      string IServiceA.Operation1()
      {
        return "IServiceA.Operation1() invoked.";
      }
      string IServiceA.Operation2()
      {
        return "IServiceA.Operation2() invoked.";
      }
      string IAdmin.AdminOperation1()
      {
        return "IAdmin.AdminOperation1 invoked.";
      }
      string IAdmin.AdminOperation2()
      {
        return "IAdmin.AdminOperation2 invoked.";
      }
    }

  3. Follow a similar set of steps to implementServiceB. Open ServiceB.cs and derive the class fromIServiceB andIAdmin. Implement both interfaces implicitly so that the result looks like the code in Example 1-18. Don't forget to add theusing statement for theBusinessServiceContractsnamespace.

    Example 1-18. Implementation for ServiceB

    using BusinessServiceContracts;

    public class ServiceB: IServiceB, IAdmin
    {
      string IServiceB.Operation3()
      {
        return "IServiceB.Operation3() invoked.";
      }
      string IAdmin.AdminOperation1()
      {
        return "IAdmin.AdminOperation1 invoked.";
      }
      string IAdmin.AdminOperation2()
      {
        return "IAdmin.AdminOperation2 invoked.";
      }
    }

  4. Verify that theBusinessServicesproject compiles without error.

Hosting two services with multiple contracts

Now you will host both services in a single console application. This will require you to create two ServiceHost instances and provide two <service> configuration sections, one for each service type.

  1. First, make sure theHost project can access the service contracts and service types. Go to theHostproject and add assembly references to two projects:BusinessServiceContractsandBusinessServices.
  2. In the application configuration file provided for theHost, provide configuration settings for both services. Open the app.config file and add the<system.serviceModel>section shown in Example 1-19. This section belongs inside the<configuration>section of the file.

    The configuration section forServiceAexposes two endpoints for the service contractIServiceA: one for Internet access over HTTP, another for TCP access behind the firewall.ServiceBalso exposes two endpoints for the service contractIServiceB: one for Internet access and another for named pipe access restricting communications to the same machine.

    Both services expose theIAdmincontract over TCP and named pipes, respectively, allowing callers on the same machine, or on remote machines behind the firewall. They also expose mex endpoints and enable the service metadata behavior for proxy generation.

    Each service configuration also provides the appropriate base addresses for the protocols they support across all endpoints.

    Example 1-19. Service model configuration for ServiceA and ServiceB

    <system.serviceModel>
      <services>
        <service name="BusinessServices.ServiceA" behaviorConfiguration="serviceBehavior">
          <host>
            <baseAddresses>
              <add baseAddress="http://localhost:8000"/>
              <add baseAddress="net.tcp://localhost:9000"/>
            </baseAddresses>
          </host>
          <endpoint address="Admin" contract="BusinessServiceContracts.IAdmin"
    binding="netTcpBinding" />
          <endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA"
    binding="basicHttpBinding" />
          <endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA"
    binding="netTcpBinding" />
        </service>
        <service name="BusinessServices.ServiceB" behaviorConfiguration="serviceBehavior">
          <host>
            <baseAddresses>
              <add baseAddress="http://localhost:8001"/>
              <add baseAddress="net.pipe://localhost"/>
            </baseAddresses>
          </host>
          <endpoint address="Admin" contract="BusinessServiceContracts.IAdmin"
    binding="netNamedPipeBinding" />
          <endpoint address="ServiceB" contract="BusinessServiceContracts.IServiceB"
    binding="basicHttpBinding" />
          <endpoint address="ServiceB" contract="BusinessServiceContracts.IServiceB"
    binding="netNamedPipeBinding" />
        </service>
      </services>
      <behaviors>
        <serviceBehaviors>
          <behavior name="serviceBehavior">
            <serviceMetadata httpGetEnabled="true"/>
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>


    Each<service>section holds configuration settings for its own base addresses and endpoints. Recall that the configuration for a particular service is used to initialize aServiceHostinstance for that service type. Be mindful that base addresses across all sections must have unique ports since a port can be opened only once per machine.
  3. Now that service model configuration has been provided for each service, you will write code to initialize and open aServiceHostinstance for both. Go to theHostproject and open Program.cs. Modify theMain()entry point so that itincludes the code shown in Example 1-20. You will also need to add ausing statement forSystem.ServiceModel.

    This code creates two distinctServiceHost instances, one for each service. They are both constructed and opened within a try...finally block to ensure thatClose()is called for each when the host shuts down or if a fatal exception occurs.

    Example 1-20. Initializing the ServiceHost for ServiceA and ServiceB

    using System.ServiceModel;

    static void Main(string[] args)
    {
      ServiceHost hostA = null;
      ServiceHost hostB = null;

      try
      {
        hostA = new ServiceHost(typeof(BusinessServices.ServiceA));
        hostB = new ServiceHost(typeof(BusinessServices.ServiceB));

        hostA.Open();
        hostB.Open();

        Console.WriteLine();
        Console.WriteLine("Press <ENTER> to terminate Host");
        Console.ReadLine();
      }
      finally
      {
        hostA.Close();
        hostB.Close();
      }
    }

  4. Compile and run theHostproject once to verify that no errors occur.

Because metadata browsing is enabled in the configuration section for each service type, you can browse to the WSDL document for each service by providing the HTTP base address for each service. Note that each service has its own distinct WSDL document, but for each individual service, all endpoints for the service are included in its WSDL document.

Consuming internal services using shared contracts

In this part of the lab, you will implement the internal client application and invoke service operations over TCP and named pipe protocols. The purpose of this exercise is to illustrate how you might share service metadata when you own both sides of the development effort for intranet clients. Sharing contract libraries ensures both sides are compiling against the latest contract versions throughout the development cycle. In addition, this exercise will illustrate the use of different standard bindings for TCP and named pipes.

  1. First, go to theInternalClientproject and add a reference to theBusinessServiceContractsproject. This will give the client application direct access to the service contract necessary to invoke the service.
  2. The client application requires prior knowledge of the service endpoints it can reach before it can configure a proxy. Under the assumption that you own both sides, client developers will manually configure client endpoints to reach each service endpoint. In this case, you'll consumeServiceAover TCP andServiceBover named pipes.

    Open the app.config file and add the<system.serviceModel>section shown in Example 1-21. This includes two endpoints for each service: one for the main service contract, the other for the administrative contract. The binding for each endpoint matches the binding configuration for the same endpoint at the service. In this case, TCP endpoints usenetTcpBinding, and named pipe endpoints usenetNamedPipeBinding.

    Example 1-21. Service model configuration for the InternalClient

    <system.serviceModel>
      <client>
        <endpoint address="net.tcp://localhost:9000/ServiceA"
    contract="BusinessServiceContracts.IServiceA" binding="netTcpBinding" />
        <endpoint address="net.tcp://localhost:9000/Admin" contract="BusinessServiceContracts.IAdmin" binding="netTcpBinding" name="TCP_IAdmin" />
        <endpoint address="net.pipe://localhost/ServiceB" contract="BusinessServiceContracts.IServiceB" binding="netNamedPipeBinding" />
        <endpoint address="net.pipe://localhost/Admin" contract="BusinessServiceContracts.IAdmin" binding="netNamedPipeBinding" name="IPC_IAdmin"/>
      </client>
    </system.serviceModel>


    Remember that when you select a binding, you are selecting a transport protocol, a message encoding format, and possibly other messaging protocols for security and reliability, for example. The details of what goes into a binding will be discussed in Chapter 3. The important thing is that if you use the defaults on both sides, the communication channel at each end will be compatible.
  3. Now you will write some code to invoke each service endpoint on both services. To do this, you will need four proxy references: one for each service and contract.

    Open Form1.cs and add ausingstatement forBusinessServiceContracts. In addition, declare a proxy for each contract, scoped to the lifetime of the application, adding the following definitions as members ofForm1:

      using BusinessServiceContracts;

      public partial class Form1 : Form 
      {
        
    IServiceA m_proxyA;
        IAdmin m_adminProxyA;

        IServiceB m_proxyB;
        IAdmin m_adminProxyB;

        // more code
      }

  4. In the form constructor, initialize each proxy
    usingChannelFactory<T>. The proxies forIServiceAandIServiceBcan use the default client endpoint for each contract. Because there is only one endpoint for each contract type, you don't have to specify which<endpoint>section will initialize the proxy.

    SinceIAdminhas two endpoints defined, one for TCP and another for named pipes, you must specify which endpoint will initialize the proxy when you constructChannelFactory<T>. Do this by passing the appropriate endpoint name from Example1-21.

    You'll see the resulting code for the form constructor in Example 1-22. You must also add a reference toSystem.ServiceModel, as shown.

    Example 1-22. Code to initialize ServiceA and ServiceB proxies

    using System.ServiceModel;

    public Form1()
    {
      InitializeComponent();

     
    ChannelFactory<IServiceA> factoryA = new ChannelFactory<IServiceA>("");
      m_proxyA = factoryA.CreateChannel();

      ChannelFactory<IAdmin> adminFactoryA = new ChannelFactory<IAdmin>("TCP_IAdmin");
      m_adminProxyA = adminFactoryA.CreateChannel();

      ChannelFactory<IServiceB> factoryB = new ChannelFactory<IServiceB>("");
      m_proxyB = factoryB.CreateChannel();

      ChannelFactory<IAdmin> adminFactoryB = new ChannelFactory<IAdmin>("IPC_IAdmin");
      m_adminProxyB = adminFactoryB.CreateChannel();
    }
  5. The user interface for the client has already been created. If you look at Form1.cs in design view, you'll see a button to test each service and administrative operation. Now you'll add code to invoke each operation using the appropriate proxy. For each button on the form, add a handler for theClick event; you can do this by double-clicking each button from design view. Inside eachClick event handler, add code to invoke the appropriate operation and show the result in a message box. The resulting code for each of these handlers is shown in Example 1-23.

    Just adding the code from Example 1-23 will not be sufficient to hook up the event handlers. When you double-click each button in design view, this generates designer code to hook up each event handler to its Button control.

    Example 1-23. Code to invoke all service operations

    private void button1_Click(object sender, EventArgs e)
    {
      string s = m_adminProxyA.AdminOperation1();
      MessageBox.Show(s);
    }
    private void button2_Click(object sender, EventArgs e)
    {
      string s = m_adminProxyA.AdminOperation2();
      MessageBox.Show(s);
    }
    private void button3_Click(object sender, EventArgs e)
    {
      string s = m_adminProxyB.AdminOperation1();
      MessageBox.Show(s);
    }
    private void button4_Click(object sender, EventArgs e)
    {
      string s = m_adminProxyB.AdminOperation2();
      MessageBox.Show(s);
    }
    private void button5_Click(object sender, EventArgs e)
    {
      string s = m_proxyA.Operation1();
      MessageBox.Show(s);
    }
    private void button6_Click(object sender, EventArgs e)
    {
      string s = m_proxyA.Operation2();
      MessageBox.Show(s);
    }
    private void button7_Click(object sender, EventArgs e)
    {
      string s = m_proxyB.Operation3();
      MessageBox.Show(s);
    }

  6. It is always good practice to release resources when you are finished with them. To make sure that each of the channels are properly disposed of when the application exits, add code to explicitly close each proxy. First, add a new private function namedCloseProxy()that will cast each proxy toICommunicationObjectand safely invoke itsClose()operation, swallowing any errors that may occur to prevent an exception during exit. Then, add an event handler for theFormClosingevent ofForm1 and add code to callCloseProxy()for each proxy. The code to add is shown in Example1-24.

    Example 1-24. Code to close each proxy

    private void CloseProxy(ICommunicationObject proxy)
    {
      try
      {
        if (proxy != null)
          proxy.Close();
      }
      catch { }
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)

    {
      CloseProxy(m_proxyA as ICommunicationObject);
      CloseProxy(m_proxyB as ICommunicationObject);
      CloseProxy(m_adminProxyA as ICommunicationObject);
      CloseProxy(m_adminProxyB as ICommunicationObject);
    }

  7. Compile the solution and test the internal client. Run theHostproject first, and then runInternalClient. Click each button to invoke operations exposed byServiceA andServiceB.

Consuming external services with a generated proxy

Now you will implement the external client application and invoke service operations over HTTP. In this case, the client will rely on the WSDL document to generate a proxy and related configuration to call the service. This exercise will illustrate how the proxy generation process handles multiple contracts and endpoints.

  1. Start by running theHostproject so that you can generate proxies for each service.
  2. Go to theExternalClientproject in Solution Explorer and add a service reference forServiceA. Provide the base address http://localhost:8000 and name the referenceServiceA. This will generate a proxy for each contract exposed byServiceAand create an application configuration file with client endpoints.
  3. Now add a service reference forServiceB. This time, provide the base address http://localhost:8001 and name the referenceServiceB. This will generate a proxy for each contract exposed byServiceB, adding new client endpoints to the application configuration file.

    The application configuration file is not overwritten when you add new service references. A merge is performed to add to configuration settings. SvcUtil also supports merge through command-line options.
  4. This application will invoke each service using the proxies generated by SvcUtil. Like with theInternalClientapplication, you'll create a proxy reference for each service contract, initialize them in the form constructor, and then close them in theFormClosingevent. The code will be simplified somewhat since the generated proxy hides some of the complexity of creating the communication channel, and directly exposes close functionality. The resulting code is shown in Example 1-25.

    Example 1-25. Code to initialize and close generated proxies for ServiceA and ServiceB

    public partial class Form1 : Form
    {
      ServiceA.ServiceAClient m_proxyA;
      ServiceB.ServiceBClient m_proxyB; 
      public Form1()
      {
        InitializeComponent();

        m_proxyA = new ExternalClient.ServiceA.ServiceAClient("BasicHttpBinding_IServiceA");
        m_proxyB = new ExternalClient.ServiceB.ServiceBClient("BasicHttpBinding_IServiceB");
      }
      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
        this.m_proxyA.Close();
        this.m_proxyB.Close();
      }
    }

    You'll notice that each proxy is initialized by passing the name of a particular endpoint from the application configuration file. Each service exposes multiple endpoints, but because this is an Internet client, the assumption is that the
    client won't have permissions to call the TCP nor be able to invoke services over named pipes (named pipes requires same-machine calls). Still, add service reference generated configuration for all endpoints because the WSDL document includes all endpoints for a service.
  5. Now you can add code to invoke each operation. If you look at the form in design view, you'll see that only three buttons are present to invoke the collective operations of both service contracts. Create Click event handlers for each button and add code to invoke each operation through the appropriate proxy. Example 1-26 shows the resulting code.

    Example 1-26. Code to invoke each service operation

    private void button1_Click(object sender, EventArgs e)
    {
      string s = m_proxyA.Operation1();
      MessageBox.Show(s);
    }
    private void button2_Click(object sender, EventArgs e)
    {
      string s = m_proxyA.Operation2();
      MessageBox.Show(s);
    }
    private void button3_Click(object sender, EventArgs e)
    {
      string s = m_proxyB.Operation3();
      MessageBox.Show(s);
    }

  6. Compile and test the external client application. First run theHost, and then runExternalClient. Click each button to invoke the service operations exposed byServiceA andServiceB.

Let's look at the new concepts and tools introduced in this lab.

Please check back next week for the conclusion to this article.

blog comments powered by Disqus
BRAINDUMP ARTICLES

- Microsoft Windows 8 Committed to Cloud Compu...
- Independent Developers Favor Windows Phone 7
- Dell Introduces VMware-based Cloud
- Microsoft and Skype Agree to Acquisition Deal
- Transfer Contacts in Microsoft Outlook
- Zune`s Next Steps
- Safari Books Online Review
- Does Microsoft Get Touch Screens Now?
- Microsoft`s Record Quarterly Earnings Not En...
- Basic Operations and Registers in Assembly
- Assembly Coding within Visual C/C++ IDE
- New Microsoft Office Coming with a Twist
- Microsoft`s FUSE Labs Unveils Spindex Social...
- HP Slate with Windows 7: Dead or Alive?
- Windows Phone 7 Mobile OS to Rival Android a...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 6 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials