WCF: WS-* Dual HTTP Binding (WSDualHttpBinding)
WSDualHttpBinding is similar to WSHttpBinding but provides for -- you guessed it -- duplex communications.
As with most things, there's more than one approach you can take to achieve nearly the same results. For instance, you don't have to use duplex communications between a service and a client to provide near-real-time updates -- you could have the client poll the service. But, the big drawbacks to polling are:
The Client's Configuration File
Below is a copy of the client's App.config file prior to any changes. The important parts are highlighted:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IMyService">
<security mode="None"/>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost:2374/MyService.svc/BASIC"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IMyService"
contract="IMyService"
name="WSHttpBinding_IMyService" />
</client>
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="Log4NetApplicationLog.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
After changing the type of binding, we see that the first thing that sets the configuration for dual binding apart from the basic binding and the WS-* binding is the inclusion of a client base address.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding
name="WSDualBinding_IMyService"
clientBaseAddress="http://localhost:8001/myClient/">
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost:2374/MyService.svc/BASIC"
binding="wsDualHttpBinding"
bindingConfiguration="WSDualBinding_IMyService"
contract="IMyService"
name="WSHttpBinding_IMyService" />
</client>
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="Log4NetApplicationLog.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
Before we can test this configuration, we need to update the service's configuration.
The Service's Configuration File
The changes to the service's App.config file are just as simple as those we did above and have done before. Here's our original configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="MyWcfService.MyService" behaviorConfiguration="BASIC">
<host>
<baseAddresses>
<add baseAddress="http://localhost:2374/MyService.svc"/>
</baseAddresses>
</host>
<endpoint
address="BASIC"
binding="wsHttpBinding"
bindingConfiguration="wsBindingConfiguration"
contract="MyWcfService.IMyService"/>
<endpoint
address="MEX"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicBindingConfiguration">
<security mode="None"/>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="wsBindingConfiguration">
<security mode="None"/>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="BASIC">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="MyWcfService-Host.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
And here's our changes:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="MyWcfService.MyService" behaviorConfiguration="BASIC">
<host>
<baseAddresses>
<add baseAddress="http://localhost:2374/MyService.svc"/>
</baseAddresses>
</host>
<endpoint
address="BASIC"
binding="wsDualHttpBinding"
bindingConfiguration="dualBindingConfiguration"
contract="MyWcfService.IMyService"/>
<endpoint
address="MEX"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicBindingConfiguration">
<security mode="None"/>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="wsBindingConfiguration">
<security mode="None"/>
</binding>
</wsHttpBinding>
<wsDualHttpBinding>
<binding name="dualBindingConfiguration">
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="BASIC">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="MyWcfService-Host.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
As we've done before, we've added a new binding to the bindings subsection and then directed the endpoint to use the new binding.
Running the Service & Client with Dual Binding
With those changes out of the way, we should be able to launch the service, run the client, and see the results.
Firing up the service, we get the following:
And then we start the client:
When we press the key to get data, we get this on the client:
which means success. And, if we go back and see what happened on the service, we see:
So, we pushed a button over here (on the client) and something happened over there (on the service).
This makes me happy.
Where to Go from Here
What we've done so far doesn't really take advantage of the duplex nature of this binding. But, it does show, once again, that changing from one binding to another is relatively simple and doesn't require you to update the code per se that's doing the actual work within your service or client.
In subsequent posts, we'll continue walking through the system-provided bindings just to make sure we can implement each one. After that, we'll look at why you would choose one binding over another, and modify our code to show the strengths and weaknesses of various bindings.
Finally, we'll look in depth at security.
As with most things, there's more than one approach you can take to achieve nearly the same results. For instance, you don't have to use duplex communications between a service and a client to provide near-real-time updates -- you could have the client poll the service. But, the big drawbacks to polling are:
- The client is only as up-to-date as its last poll. If something occurs on the service side of things between polls, then the client won't know about it until its next poll.
- Polling consumes resources -- even when the service has nothing new to report. Each request takes network resources and creates work for both the client and server. If you poll frequently, then you rack up a lot of expenses. And, if there are periods where nothing happens then you are expending resources for no reason.
With long polling, the client requests information from the server exactly as in normal polling, except it issues its HTTP/S requests (polls) at a much slower frequency. If the server does not have any information available for the client when the poll is received, instead of sending an empty response, the server holds the request open and waits for response information to become available. Once it does, the server immediately sends an HTTP/S response to the client, completing the open HTTP/S Request. In this way the usual response latency (the time between when the information first becomes available and the next client request) otherwise associated with polling clients is eliminated.WSDualHttpBinding is another workaround. With WSDualHttpBinding, both the service and the client expose endpoints and can receive messages. Of course, this means that if you have a firewall sitting between the client and the service, then you'll have to open the firewall for traffic to both the service and the client.
The Client's Configuration File
Below is a copy of the client's App.config file prior to any changes. The important parts are highlighted:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IMyService">
<security mode="None"/>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost:2374/MyService.svc/BASIC"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IMyService"
contract="IMyService"
name="WSHttpBinding_IMyService" />
</client>
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="Log4NetApplicationLog.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding
name="WSDualBinding_IMyService"
clientBaseAddress="http://localhost:8001/myClient/">
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost:2374/MyService.svc/BASIC"
binding="wsDualHttpBinding"
bindingConfiguration="WSDualBinding_IMyService"
contract="IMyService"
name="WSHttpBinding_IMyService" />
</client>
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="Log4NetApplicationLog.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
The Service's Configuration File
The changes to the service's App.config file are just as simple as those we did above and have done before. Here's our original configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="MyWcfService.MyService" behaviorConfiguration="BASIC">
<host>
<baseAddresses>
<add baseAddress="http://localhost:2374/MyService.svc"/>
</baseAddresses>
</host>
<endpoint
address="BASIC"
binding="wsHttpBinding"
bindingConfiguration="wsBindingConfiguration"
contract="MyWcfService.IMyService"/>
<endpoint
address="MEX"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicBindingConfiguration">
<security mode="None"/>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="wsBindingConfiguration">
<security mode="None"/>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="BASIC">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="MyWcfService-Host.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
And here's our changes:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service name="MyWcfService.MyService" behaviorConfiguration="BASIC">
<host>
<baseAddresses>
<add baseAddress="http://localhost:2374/MyService.svc"/>
</baseAddresses>
</host>
<endpoint
address="BASIC"
binding="wsDualHttpBinding"
bindingConfiguration="dualBindingConfiguration"
contract="MyWcfService.IMyService"/>
<endpoint
address="MEX"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="basicBindingConfiguration">
<security mode="None"/>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="wsBindingConfiguration">
<security mode="None"/>
</binding>
</wsHttpBinding>
<wsDualHttpBinding>
<binding name="dualBindingConfiguration">
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="BASIC">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="MyWcfService-Host.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="FileAppender" />
<appender-ref ref="ConsoleAppender"/>
</root>
</log4net>
</configuration>
As we've done before, we've added a new binding to the bindings subsection and then directed the endpoint to use the new binding.
Running the Service & Client with Dual Binding
With those changes out of the way, we should be able to launch the service, run the client, and see the results.
Firing up the service, we get the following:
When we press the key to get data, we get this on the client:
which means success. And, if we go back and see what happened on the service, we see:
So, we pushed a button over here (on the client) and something happened over there (on the service).
This makes me happy.
Where to Go from Here
What we've done so far doesn't really take advantage of the duplex nature of this binding. But, it does show, once again, that changing from one binding to another is relatively simple and doesn't require you to update the code per se that's doing the actual work within your service or client.
In subsequent posts, we'll continue walking through the system-provided bindings just to make sure we can implement each one. After that, we'll look at why you would choose one binding over another, and modify our code to show the strengths and weaknesses of various bindings.
Finally, we'll look in depth at security.
Comments
Post a Comment