Chapter 9. Custom Behaviors

In This Chapter

One of the best things about Windows Communication Foundation is how easy it is to customize it. This chapter focuses on one way of customizing the Windows Communication Foundation: adding one’s own behaviors.

In the Windows Communication Foundation, behaviors are mechanisms internal to services and proxies that can be controlled. One example of a built-in behavior is the instancing behavior, which determines whether a Windows Communication Foundation server creates a new instance of a service type to process an incoming message or uses an existing one. The design of the Windows Communication Foundation makes it easy for developers to create custom behaviors by which administrators can control new internal mechanisms of Windows Communication Foundation proxies and services.

Understanding the Types of Behaviors

There are proxy behaviors and dispatch behaviors. Proxy behaviors, depicted in Figure 9.1, are the mechanisms by which proxies do their work of taking invocations of operations in the code for a client and translating those into messages to be transmitted to a service via the Channel Layer. Dispatch behaviors, depicted in Figure 9.2, are the mechanisms by which incoming messages, after having been processed by the Channel Layer, are translated into invocations of the methods of the service type by the Windows Communication Foundation’s Dispatcher.

Proxy behaviors.

Figure 9.1. Proxy behaviors.

Dispatch behaviors.

Figure 9.2. Dispatch behaviors.

There are five subtypes of proxy and dispatch behaviors:

  • Channel behaviors are proxy behaviors that apply to all of the invocations of the methods of a proxy, such as the built-in ViaUri behavior.

  • Service behaviors are dispatch behaviors that apply to all the messages to be processed by a service type, such as the built-in ServiceMetadata behavior.

  • Endpoint behaviors are dispatch behaviors that apply to the messages arriving via a given endpoint, such as the built-in ListenUri behavior.

  • Contract behaviors are proxy or dispatch behaviors specific to a given contract.

  • Operation behaviors are proxy or dispatch behaviors specific to a given operation.

Configuring Behaviors

For behaviors to be used, they must be configured for use on the service or proxy. There are three possibilities for configuring behaviors: with attributes, in code, or via a configuration file.

Configuring Behaviors with Attributes

When an interface with attributes is first added to either a ChannelFactory or a Service Host, the Windows Communication Foundation identifies the set of attributes on the interface using reflection.

The following text gives an example of how to create an attribute and then an example of that attribute being applied.

The following code creates an attribute named Audit.

[AttributeUsage(AttributeTargets.Interface)]
public class AuditAttribute: Attribute, IChannelBehavior
{
    public void ApplyBehavior(
                              ChannelDescription description,
                              ProxyBehavior behavior,
                              BindingParameterCollection parameters)
   {
       ChannelFactory<IAudit> fact = new ChannelFactory<IAudit>("ClientAudit");
       IAudit prt = fact.CreateChannel();
       ClientAuditMessageInspector insp = new ClientAuditMessageInspector(prt);
       behavior.MessageInspectors.Add(insp);

   }
}

The following shows the Audit attribute being applied to the IChat interface:

[System.ServiceModel.ServiceContractAttribute()]
[ClientAuditBehavior.Audit]
public interface IChat

Implementing behaviors as attributes is important for scenarios in which behaviors will always be required. It excludes the possibility of changes being made in the configuration that could lead to undesirable and/or unexpected results.

Configuring Behaviors in Code

You can configure behaviors in code by adding them to the appropriate Behaviors collection. The following example shows how to add a Service Behavior:

ServiceHost sh = new ServiceHost(typeof(Chat),baseServiceAddress);
  sh.Description.Behaviors.Add(new ServiceAuditBehavior.AuditAttribute());
sh.Open();

Configuring Behaviors in the Configuration File

To enable your own behaviors within the configuration file, you will need to add your behavior to the behaviorElementExtensions section. The following is an example of how this could be done with the behaviors created earlier in the chapter:

<system.serviceModel>
   <extensions>
       <behaviorElementExtensions>
           <add name="clientAuditBehavior" type="ClientAuditBehavior, Audit,
               Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
       </behaviorElementExtensions>
   </extensions>
</system.serviceModel>

After the element and its configuration type are defined, the extension can be used, as is illustrated here:

<behaviors>
    <behavior configurationName="ClientBehavior">
        <clientAuditBehavior />
    </behavior>
</behaviors>

Creating a Behavior Extension

Placing behaviors in the configuration file provides more flexibility. This allows for adaptability and changes in deployment.

The following creates a behavior extension called ServiceAuditBehavior and adds the capability to configure it using a property named Enabled:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Configuration;

namespace ServiceAuditBehavior
{
    class ServiceAuditBehaviorExtension: BehaviorExtensionSection
    {
        public override string ConfiguredSectionName
        {
            get { return "ServiceAuditBehavior"; }
        }
        [ConfigurationProperty("Enabled")]
        public string Enabled
        {
            get { return (string)base["Enabled"]; }
            set { base["Enabled"] = value; }
        }
        public override object CreateBehavior()
        {
            ServiceAuditBehavior behavior = new ServiceAuditBehavior();
            behavior.Enabled = Enabled;
            return behavior;
        }
    }
}

Creating a Behavior to Inspect Messages on the Server

The following sections contain several exercises that enable the creation and use of behaviors on both the client and the service side. These examples use what is likely the most common type of behavior, a type of behavior that inspects and interacts with the messages passing between client and service.

There are various scenarios in which it may be important for the content of a message to be inspected and/or altered. In these examples, there is a simple communication program in use, and the business requirements dictate that messages from both the client and the server must be audited by another service for storage.

Here we’re using a simple chat/echo service, but the real world presents numerous places where auditing would be required. Those include the ever-growing auditing requirements of government/industry compliance in the private sector and the need to monitor the online interactions of children out of safety concerns in the household. And that is just in the context of monitoring the content of the messages for auditing; as you can imagine, with the capability to access the message itself, there are a number of other situations for which to use these custom behaviors.

In these exercises, you will also be introduced to the two interfaces used to incorporate the MessageInspector functionality. These are IStubMessageInspector on the service side and IProxyMessageInspector on the client side.

Creating the Auditing Service

Both the service and the client applications that will be created will report the messages they transmit to an auditing service. That auditing service is a simple one with a single operation, ProcessMessage. Follow these steps to create the auditing service:

  1. Create a blank solution in Visual Studio called Custom Behaviors.

  2. Add a C# Windows console project named AuditingService to the solution.

  3. Add references to the System.ServiceModel and System.Configuration assemblies.

  4. Add a new class module named Contracts.cs.

  5. Define the contract for the chat application by replacing the code in the module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace AuditingService
    {
       [ServiceContract]
        public interface IAudit
        {
            [OperationContract(IsOneWay = true)]
            void ProcessMessage(
                                string reportedFrom,
                                string messageType,
                                string message);
        }
    }
  6. Create the service type by adding a new class module named Audit.cs to the project and entering this code into that module:

    namespace AuditingService
    {
        public class Auditor : IAudit
        {
            public void ProcessMessage(
                                       string reportedFrom,
                                       string messageType,
                                       string message)
            {
                Console.WriteLine("Received Message From:" + reportedFrom);
                Console.WriteLine("Processing Message of Type:" + messageType);
                Console.WriteLine("Message:");
                Console.WriteLine(message);
                Console.WriteLine("--------------------------------");
                return;
            }
        }
    }
  7. Provide a host for the service by replacing the code in the Program.cs module with this code:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Text;
    using System.ServiceModel;
    
    namespace AuditingService
    {
        class Program
        {
          static void Main(string[] args)
          {
            Uri baseServiceAddress =
              new Uri(
                System.Configuration.ConfigurationManager.
                AppSettings.Get("baseAddress"));
            using (ServiceHost sh = new ServiceHost(
              typeof(AuditingService.Auditor),
              new Uri[]{baseServiceAddress}))
          {
           sh.Open();
           Console.WriteLine("Auditing Service is now running.");
           Console.ReadLine();
          }
       }
    }
  8. Configure the auditing service by adding an Application Configuration file named App.Config to the project, and replacing its contents with the following:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <appSettings>
        <add key="baseAddress" value="http://localhost:8000/Auditor" />
      </appSettings>
      <system.serviceModel>
      <services>
        <service
                               type="AuditingService.Auditor" >
                       <endpoint behaviorConfiguration="ServiceBehavior"
                                      address=""
                                      binding="basicHttpBinding"
                                      contract ="AuditingService.IAudit" />
    
       </service>
     </services>
     <bindings>
       <basicHttpBinding>
       </basicHttpBinding>
     </bindings>
     <behaviors>
    
          <behavior
            name="ServiceBehavior"
            returnUnknownExceptionsAsFaults="True">
            <metadataPublishing
              enableGetWsdl="True"
              enableHelpPage="True"
              enableMetadataExchange="True"
                  />
          </behavior>
      </behaviors>
     </system.serviceModel>
    </configuration>

Creating the Dispatch Service Behavior

Follow these next steps to create the dispatch service behavior that will send copies of all messages received by a service to the auditing service:

  1. Add a new C# class library project to the solution and name it ServiceAuditBehavior.

  2. Add references to the System.ServiceModel and System.Runtime.Serialization assemblies.

  3. Add a version of the auditing service’s contract to the service behavior project by adding a new class module called Contracts.cs, and replacing the code therein with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace AuditingService
    {
       [ServiceContract]
        public interface IAudit
        {
            [OperationContract(IsOneWay = true)]
            void ProcessMessage(
                                string reportedFrom,
                                string messageType,
                                string message);
        }
    }
  4. Define an attribute by which the service behavior can be attached to a service by adding a new class module called AuditAttribute.cs to the project, and replacing the default code in that module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    using System.ServiceModel.Configuration;
    using System.Collections.ObjectModel;
    
    namespace ServiceAuditBehavior
    {
    
        [AttributeUsage(AttributeTargets.Class)]
        public class AuditAttribute : Attribute, IServiceBehavior
        {
            public void ApplyBehavior(
                              ServiceDescription description,
                              ServiceHostBase shBase,
                         Collection<DispatchBehavior> behaviors,
                         Collection<BindingParameterCollection> parameters)
        {
           foreach (DispatchBehavior behavior in behaviors)
           {
               ChannelFactory<IAudit> fact =
                  new ChannelFactory<IAudit>("ServiceAudit");
               IAudit prt = fact.CreateChannel();
               ServiceAuditMessageInspector insp =
                  new ServiceAuditMessageInspector(prt);
               behavior.MessageInspectors.Add(insp);
           }
        }
      }
    }

    There are several points to note from this code. The class implements both the Attribute and the ServiceBehavior interfaces. The implementation of the ApplyBehavior() method of the ServiceBehavior interface is where the mechanism of the custom behavior gets inserted into the dispatch operations of the service, adding the custom behavior as an inspector of each incoming message that is to be dispatched.

  5. Begin creating the actual custom behavior by adding a new class named AuditMessageInspector.cs to the project, and replacing the default code in that module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using System.IO;
    using System.ServiceModel;
    
    namespace ServiceAuditBehavior
    {
        class ServiceAuditMessageInspector : IStubMessageInspector
        {
    
            private IAudit _proxy;
    
            public ServiceAuditMessageInspector(IAudit prx)
            {
                _proxy = prx;
            }
                 public object AfterReceiveRequest(
                     ref Message request,
                     IClientChannel channel,
                     InstanceContext instanceContext)
                 {
    
                    _proxy.ProcessMessage("Server", request.Headers.Action, request.ToString());
    
                     return null;
    
                     }
    
                     public void BeforeSendReply(ref Message request, object correlationState)
                 {
                      System.Console.WriteLine(request.Headers.MessageVersion.Envelope);
                 }
           }
    }

    This code defines a class that implements the IStubMessageInspector interface. That interface has two methods, AfterReceiveRequest() and BeforeSendReply(), which provide the opportunity to inspect and interact with incoming and outgoing messages. In this case, incoming messages are sent to the auditing service, and outgoing messages are simply registered in output to the console.

Creating the Service

Now the service behavior has been programmed. Follow these next few steps to create a simple chat/echo service to which that behavior will be applied:

  1. Add a new C# console application project called Service to the solution.

  2. Add references to the System.Configuration, System.ServiceModel, and System.Runtime.Serialization assemblies.

  3. Add a reference to the ServiceAuditBehavior project.

  4. Add a new class module, Contracts.cs, to the solution and replace the default code in that class with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace Service
    {
        [ServiceContract]
        public interface IChat
        {
             [OperationContract]
             string Talk(string chatText);
        }
    }
  5. Replace the code in the Program.cs module with code for a service that implements the service contract defined in the preceding step:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace Service
    {
        [ServiceAuditBehavior.Audit]
        public class Chat : IChat
        {
            static void Main(string[] args)
            {
                Uri baseServiceAddress = new Uri(
                  System.Configuration.ConfigurationManager.AppSettings.Get(
                    "baseAddress"));
                ServiceHost sh = new ServiceHost(typeof(Chat),baseServiceAddress);
                sh.Open();
                Console.WriteLine("Service is now running.");
    
                Console.ReadLine();
    
            }
    
    
            public string Talk(string chatText)
            {
                Console.WriteLine("Client said:" + chatText);
                return ("Did you say " + chatText + " ?");
            }
        }
    }

    This code defines a service type that implements the service contract defined in the preceding step. The custom audit behavior is added to the service type as an attribute.

  6. Configure the service by adding an application configuration file to the project with this content. Replace the code in the Program.cs module with code for a service that implements the service contract defined in the preceding step:

    <configuration>
      <appSettings>
        <add key="baseAddress" value="http://localhost:8000/Behaviors" />
      </appSettings>
      <system.serviceModel>
        <client>
          <endpoint
               name="ServiceAudit"
               address="http://localhost:8000/Auditor"
               binding="basicHttpBinding"
               contract ="ServiceAuditBehavior.IAudit" />
        </client>
        <services>
          <service type="Service.Chat">
            <endpoint behaviorConfiguration="ServiceBehavior"
              address=""
              contract="IChat"
              binding ="basicHttpBinding"/>
          </service>
        </services>
      </system.serviceModel>
    </configuration>

    This configuration includes the configuration of a client for the auditing service, because the custom behavior that has been added to the chat/echo service sends messages to the auditing service.

Creating a Behavior to Inspect Messages on the Client

What has been accomplished thus far is the creation of a dispatch service behavior to operate on messages directed at the chat/echo service. Now a proxy channel behavior will be constructed to operate on messages sent by a chat/echo client.

Creating the Proxy Channel Behavior

Execute these steps to create a proxy channel behavior that will send copies of all messages sent by a client to the auditing service:

  1. Right-click on the solution and create a new C# class library project named ClientAuditBehavior.

  2. Add references to the System.Runtime.Serialization and System.ServiceModel assemblies.

  3. Provide a copy of the Auditing service’s contract by creating a new class module named Contract.cs and replacing the default code in that module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace AuditingService
    {
       [ServiceContract]
        public interface IAudit
        {
            [OperationContract(IsOneWay = true)]
            void ProcessMessage(
                                string reportedFrom,
                                string messageType,
                                string message);
        }
    }
  4. Now provide the code for the proxy channel behavior by adding a new class module called AuditMessageInspector.cs to the ClientAuditBehavior project, and replacing the default code in that module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    using System.Xml;
    using System.IO;
    
    namespace ClientAuditBehavior
    {
          class ClientAuditMessageInspector : IProxyMessageInspector
          {
            private IAudit _proxy;
    
            public ClientAuditMessageInspector(IAudit prx)
            {
              proxy = prx;
            }
            public object BeforeSendRequest(
              ref Message request,
              IClientChannel channel)
            {
              return null;
            }
    
            public void AfterReceiveReply(
              ref Message reply,
              object correlationState)
            {
              MessageBuffer buf = reply.CreateBufferedCopy(1000000);
              Message msg = buf.CreateMessage();
    
              TextWriter strWrt = new StringWriter();
              XmlTextWriter wrt = new XmlTextWriter(strWrt);
              msg.WriteBody(wrt);
              wrt.Flush();
              strWrt.Flush();
              String msgStr = strWrt.ToString();
              wrt.Close();
              strWrt.Close();
    
              _proxy.ProcessMessage("Client",msg.Headers.Action, msgStr);
              msg.Close();
              reply = buf.CreateMessage();
            }
          }
    }

    Note that the class defined in this code implements the IProxyMessageInspector interface of the Windows Communication Foundation. That interface defines a method called BeforeSendRequest() for operating on outgoing messages, and another called AfterReceiveReply() for operating on responses.

  5. The next step is to define the attribute class by which the proxy channel behavior will be attached to a channel. Do so by adding a new class module called AuditAttribute.cs to the ClientAuditBehavior project, and replacing the default code in that module with this code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    
    namespace ClientAuditBehavior
    {
           [AttributeUsage(AttributeTargets.Class)]
           public class AuditAttribute: Attribute, IChannelBehavior
           {
                   public void ApplyBehavior(
                     ChannelDescription description,
                     ProxyBehavior behavior,
                     BindingParameterCollection parameters)
                   {
                     ChannelFactory<IAudit> fact =
                       new ChannelFactory<IAudit>("ClientAudit");
                     IAudit prt = fact.CreateChannel();
    
                     ClientAuditMessageInspector insp =
                       new ClientAuditMessageInspector(prt);
    
                     behavior.MessageInspectors.Add(insp);
                  }
           }
    }

Creating the Client

Follow these steps to create a client for the chat/echo service to which the proxy channel behavior will be applied:

  1. Add a new C# Windows console project named Client to the solution.

  2. Add a reference to the System.ServiceModel assembly to that new project.

  3. Also add a reference to the ClientAuditBehavior project.

  4. Add a reference to the System.ServiceModel assembly to that new project.

  5. Right-click on the Service project in the solution and select Debug, and then Start New Instance from the content menu.

  6. Open the Microsoft Windows Vista DEBUG Build Environment prompt, make the directory containing the Client project file the current directory, and enter this command:

    svcutil http://localhost:8000/Behaviors /out:proxy.cs /config:app.config
  7. Stop debugging the solution and add the files proxy.cs and app.config output by the command in the preceding step to the Client project.

  8. Replace the default code in the Program.cs module with this code that uses the generated proxy class in the Proxy.cs module, adding the proxy channel behavior to it first:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ServiceModel;
    namespace Client
    {
        class Program
        {
           static void Main(string[] args)
           {
             ChatProxy c = new ChatProxy();
             c.ChannelFactory.Description.Behaviors.Add(
               new ClientAuditBehavior.AuditAttribute());
             System.Console.WriteLine("Welcome to Chat");
             System.Console.WriteLine();
             System.Console.WriteLine("Enter text below and press ENTER to send");
             string cont = Console.ReadLine();
             while (cont != "q")
             {
               Console.WriteLine("Server Said:" + c.Talk(cont));
               cont = Console.ReadLine();
             }
           }
        }
    }
  9. Modify the app.config file of the Client project by adding this second endpoint subelement to the client element to define how the proxy channel auditing behavior is to communicate with the auditing service:

    <endpoint
      name="ClientAudit"
      address="http://localhost:8000/Auditor"
      binding="basicHttpBinding"
      contract ="ClientAuditBehavior.IAudit" />

Testing the Solution

The solution is now complete. Execute these steps to see it in action:

  1. Right-click on the solution and select Set Startup Projects from the menu.

  2. Configure the Startup project properties as shown in Figure 9.3.

    Startup project configuration.

    Figure 9.3. Startup project configuration.

  3. Start debugging the solution. Three console windows will open: one for the audit service, one for the chat/echo service, and one for the client.

  4. When there is activity in all three console windows, type some text into the client window and press the Enter key. The expected result is shown in Figure 9.4: The client sends a message to the service, the dispatch behavior on the service passes a copy of the message to the auditing service, and, when the service’s response is received by the client, the proxy channel behavior on the client passes a copy of that response to the auditing service as well.

    The solution in action.

    Figure 9.4. The solution in action.

Summary

This chapter introduced the various categories of behaviors in the Windows Communication Foundation. It also showed how to extend the Windows Communication Foundation’s Service Model layer with the addition of custom behaviors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.172.132