Some months ago I created a custom WCF protocol channel to transparently inject client-side caching capabilities into an existing WCF-enabled application just changing the its configuration file. Since I posted my first post on this subject I received positive feedbacks on the client-side caching pattern, hence I decided to create a version of my component for Windows Azure. The new implementation is almost identical to the original version and introduces some minor changes. The most relevant addition is the ability to cache response messages in Windows Azure AppFabric Caching Service. The latter enables to easily create a cache in the cloud that can be used by Windows Azure roles to store reference and lookup data. Caching can dramatically increase performance by temporarily storing information from backend stores and sources. My component allows to boost the performance of existing WCF-enabled applications running in the Azure environment by injecting caching capabilities in the WCF channel stack that transparently exploit the functionality offered by Windows Azure AppFabric Caching to avoid redundant calls against remote WCF and ASMX services. The new version of the WCF custom channel provides the possibility to choose among three caching providers:
As already noted in the previous article, client-side caching and server-side caching are two powerful and complimentary techniques to improve the performance of WCF-enabled applications. Client-caching is particularly indicated for those applications, like a web site, that frequently invoke one or multiple back-end systems to retrieve reference and lookup data, that is data that is typically static, like the catalog of an online store and that changes quite infrequently. By using client-side caching you can avoid making redundant calls to retrieve the same data over time, especially when these calls take a significant amount of time to complete.
For more information on Windows Azure AppFabric, you can review the following articles:
The problem statement that the cloud version of my component intends to solve can be formulated as follows:
To solve this problem, I created a custom protocol channel that you can explicitly or implicitly use inside a CustomBinding when specifying client endpoints within the configuration file or by code using the WCF API.
The design pattern implemented by my component can be described as follows: the caching channel allows to extend existing WCF-enabled cloud applications and services with client-caching capabilities without the need to explicitly change their code to exploit the functionality supplied by Windows Azure AppFabric Caching. To achieve this goal, I created a class library that contains a set of components to configure, instantiate and use this caching channel at runtime. My custom channel is configured to checks the presence of the response message in the cache and behaves accordingly:
You can exploit the capabilities of the caching channel in 2 distinct ways:
This section describes 2 scenarios where you can use the caching channel in a cloud application.
The following picture depicts the architecture of the first scenario that uses Windows Azure Connect to establish a protected connection between a web role in the cloud and local WCF service and uses Windows Azure AppFabric Caching provider to cache response messages in the local and distributed cache. The diagram below shows an ASP.NET application running in a Web Role that invokes a local, on-premises WCF service running in a corporate data center. In particular, when the Service.aspx page first loads, it populates a drop-down list with the name of writers from the Authors table of the notorious pubs database which runs on-premise in the corporate domain. The page offers the user the possibility to retrieve the list of books written by the author selected in the drop-down list by invoking the AuthorsWebService WCF service located in the organization’s network that retrieves data for from the Titles tables of the pubs database.
Let’s analyze what happens when the user selects the third option to use the caching channel with Windows Azure Connect.
Message Flow:
The following picture depicts the architecture of the second scenario where the web role uses the Windows Azure AppFabric Service Bus to invoke the AuthorsWebService and Windows Azure AppFabric Caching provider to cache response messages in the local and distributed cache.
Let’s analyze what happens when the user selects the fifth option to use the caching channel with the Service Bus.
NOTE In the context of a cloud application, the use of the caching channel not only improves performance, but allows to decrease the traffic on Windows Azure Connect and Service Bus and therefore the cost due to operations performed and network used.
In order to establish an IPsec protected IPv6 connection between the Web Role running in the Windows Azure data center and the local WCF service running in the organization’s network, the solution exploits Windows Azure Connect that is main component of the networking functionality that will be offered under the Windows Azure Virtual Network name. Windows Azure Connect enables customers of the Windows Azure platform to easily build and deploy a new class of hybrid, distributed applications that span the cloud and on-premises environments. From a functionality standpoint, Windows Azure Connect provides a network-level bridge between applications and services running in the cloud and on-premises data centers. Windows Azure Connect makes it easier for an organization to migrate their existing applications to the cloud by enabling direct IP-based network connectivity with their existing on-premises infrastructure. For example, a company can build and deploy a hybrid solution where a Windows Azure application connects to an on-premises SQL Server database, a local file server or an LOB applications running the corporate network.
For more information on Windows Azure Connect, you can review the following resources:
The Windows Server AppFabric Service Bus is an Internet-scale Service Bus that offers scalable and highly available connection points for application communication. This technology allows to create a new range of hybrid and distributed applications that span the cloud and corporate environments. The AppFabric Service Bus is designed to provide connectivity, queuing, and routing capabilities not only for the cloud applications but also for on-premises applications. The Service Bus and in particular Relay Services support the WCF programming model and provide a rich set of bindings to cover a complete spectrum of design patterns:
The Relay Service is a service residing in the cloud, whose job is to assist in the connectivity, relaying the client calls to the service. Both the client and service can indifferently reside on-premises or in the cloud.
For more information on the Service Bus, you can review the following resources:
We are now ready to delve into the code.
The solution code has been implemented in C# using Visual Studio 2010 and the .NET Framework 4.0. The following picture shows the projects that comprise the WCFClientCachingChannel solution.
A brief description of the individual projects is indicated below:
As I mentioned in the introduction of this article, the 3 caching providers have been modified to be used in a cloud application. In particular, the AppFabricCache project has been modified to use the Windows Azure AppFabric Caching API in place of their on-premises counterpart. Windows Azure AppFabric uses the same cache client programming model as the on-premise solution of Windows Server AppFabric. However, the 2 API are not identical and there are relevant differences when developing a Windows Azure AppFabric Caching solution compared to developing an application that leverages Windows Server AppFabric Caching . For more information on this topic, you can review the following articles:
NOTE The Client API of Windows Server AppFabric Caching and Windows Azure AppFabric Caching have the same fully-qualified name. So, what happens when you install the Windows Azure AppFabric SDK on a development machine where Windows Server AppFabric Caching is installed? The setup process of Windows Server AppFabric installs the Cache Client API assemblies in the GAC whereas the Windows Azure AppFabric SDK copies the assemblies in the installation folder, but it doesn’t register them in the GAC. Therefore, if you create an Azure application on a development machine hosting both the on-premises and cloud version of the Cache Client API, even if you reference the Azure version in your web or worker role project, when you debug the application within the Windows Azure Compute Emulator, your role will load the on-premises version, that is, the wrong version of the Cache Client API. Fortunately, the 2 on-premises and cloud versions of the API have the same fully-qualified name but different version number, hence you can include the following snippet in the web.config configuration file of your role to refer the right version of the API.
<!-- Assembly Redirection --> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Client" publicKeyToken="31bf3856ad364e35" culture="Neutral" /> <bindingRedirect oldVersion="1.0.0.0" newVersion="101.0.0.0"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Core" publicKeyToken="31bf3856ad364e35" culture="Neutral" /> <bindingRedirect oldVersion="1.0.0.0" newVersion="101.0.0.0"/> </dependentAssembly> </assemblyBinding> </runtime>
The following table shows the web.config configuration file of the PubsWebRole project.
1: <?xml version="1.0"?> 2: <configuration> 3: <configSections> 4: <!-- Append below entry to configSections. Do not overwrite the full section. --> 5: <section name="dataCacheClients" 6: type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, 7: Microsoft.ApplicationServer.Caching.Core" 8: allowLocation="true" 9: allowDefinition="Everywhere"/> 10: </configSections> 11: 12: <!-- Diagnostics --> 13: <system.diagnostics> 14: <trace autoflush="true"> 15: <listeners> 16: <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, 17: Microsoft.WindowsAzure.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 18: name="AzureDiagnostics"> 19: <filter type="" /> 20: </add> 21: </listeners> 22: </trace> 23: </system.diagnostics> 24: 25: <!-- Caching --> 26: <!-- Cache exposes two endpoints: one simple and other SSL endpoint. 27: Choose the appropriate endpoint depending on your security needs. --> 28: <dataCacheClients> 29: 30: <dataCacheClient name="default"> 31: <localCache isEnabled="true" sync="TimeoutBased" ttlValue="300" /> 32: <hosts> 33: <host name="babocache.cache.appfabriclabs.com" cachePort="22233" /> 34: </hosts> 35: 36: <securityProperties mode="Message"> 37: <messageSecurity 38: authorizationInfo="AUTHENTICATION TOKEN"> 39: </messageSecurity> 40: </securityProperties> 41: </dataCacheClient> 42: 43: <dataCacheClient name="SslEndpoint"> 44: <localCache isEnabled="true" sync="TimeoutBased" ttlValue="300" /> 45: <hosts> 46: <host name="babocache.cache.appfabriclabs.com" cachePort="22243" /> 47: </hosts> 48: 49: <securityProperties mode="Message" sslEnabled="true"> 50: <messageSecurity 51: authorizationInfo="AUTHENTICATION TOKEN"> 52: </messageSecurity> 53: </securityProperties> 54: </dataCacheClient> 55: 56: </dataCacheClients> 57: 58: <!-- Connection Strings --> 59: <connectionStrings> 60: <add name="PubsDatabase" 61: connectionString="Data Source=UPPY,1433;Initial Catalog=Pubs;Persist Security Info=True;…" 62: providerName="System.Data.SqlClient" /> 63: </connectionStrings> 64: 65: <!-- Web --> 66: <system.web> 67: 68: <customErrors mode="Off"/> 69: <compilation debug="true" targetFramework="4.0" /> 70: 71: <authentication mode="Forms"> 72: <forms loginUrl="~/Account/Login.aspx" timeout="2880" /> 73: </authentication> 74: 75: <membership> 76: <providers> 77: <clear/> 78: <add name="AspNetSqlMembershipProvider" 79: type="System.Web.Security.SqlMembershipProvider" 80: connectionStringName="ApplicationServices" 81: enablePasswordRetrieval="false" 82: enablePasswordReset="true" 83: requiresQuestionAndAnswer="false" 84: requiresUniqueEmail="false" 85: maxInvalidPasswordAttempts="5" 86: minRequiredPasswordLength="6" 87: minRequiredNonalphanumericCharacters="0" 88: passwordAttemptWindow="10" 89: applicationName="/" /> 90: </providers> 91: </membership> 92: 93: <profile> 94: <providers> 95: <clear/> 96: <add name="AspNetSqlProfileProvider" 97: type="System.Web.Profile.SqlProfileProvider" 98: connectionStringName="ApplicationServices" 99: applicationName="/"/> 100: </providers> 101: </profile> 102: 103: <roleManager enabled="false"> 104: <providers> 105: <clear/> 106: <add name="AspNetSqlRoleProvider" 107: type="System.Web.Security.SqlRoleProvider" 108: connectionStringName="ApplicationServices" 109: applicationName="/" /> 110: <add name="AspNetWindowsTokenRoleProvider" 111: type="System.Web.Security.WindowsTokenRoleProvider" a 112: pplicationName="/" /> 113: </providers> 114: </roleManager> 115: 116: </system.web> 117: 118: <system.webServer> 119: <modules runAllManagedModulesForAllRequests="true"> 120: <remove name="RewriteModule" /> 121: </modules> 122: 123: <!-- enabling failed request tracing to illustrate diagnostics in Windows Azure 124: 125: For more information on failed request logging configuration, see: 126: http://www.iis.net/ConfigReference/system.webServer/tracing/traceFailedRequests/add --> 127: <tracing> 128: <traceFailedRequests> 129: <add path="Default.aspx"> 130: <traceAreas> 131: <add provider="ASP" verbosity="Verbose" /> 132: <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" /> 133: <add provider="ISAPI Extension" verbosity="Verbose" /> 134: <add provider="WWW Server" 135: areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module" 136: verbosity="Verbose" /> 137: </traceAreas> 138: <failureDefinitions statusCodes="400-599" /> 139: </add> 140: </traceFailedRequests> 141: </tracing> 142: </system.webServer> 143: 144: <!-- WCF --> 145: <system.serviceModel> 146: 147: <!-- WCF Client Endpoints --> 148: <client> 149: <!-- Always invoke Pubs WCF service directly via Windows Azure Connect --> 150: <!-- NOTE: uppy is my machine name! --> 151: <endpoint name="DirectCallNoCache" 152: address="http://uppy/Pubs/Service.svc" 153: binding="basicHttpBinding" 154: bindingConfiguration="basicHttpBinding" 155: contract="PubsLocalService.IAuthors" /> 156: <!-- Uses the caching channel to transparently retrieve data from Windows Azure AppFabric Caching --> 157: <endpoint name="UseWCFCachingChannel" 158: address="http://uppy/Pubs/Service.svc" 159: binding="customBinding" 160: bindingConfiguration="customBinding" 161: contract="PubsLocalService.IAuthors" /> 162: <!-- Invoke Pubs WCF service using a basicHttpRelayBinding and no caching --> 163: <endpoint name="UseSBWithoutCaching" 164: address="http://baboland.servicebus.appfabriclabs.com/pubs/basichttp" 165: binding="basicHttpRelayBinding" 166: bindingConfiguration="basicHttpRelayBinding" 167: contract="PubsLocalService.IAuthors"/> 168: <!-- Invoke Pubs WCF service using a basicHttpRelayBinding with caching enabled --> 169: <endpoint name="UseSBWithCaching" 170: address="http://baboland.servicebus.appfabriclabs.com/pubs/basichttp" 171: binding="basicHttpRelayBinding" 172: bindingConfiguration="basicHttpRelayBinding" 173: behaviorConfiguration="cachingEndpointBehavior" 174: contract="PubsLocalService.IAuthors"/> 175: </client> 176: 177: <!-- WCF Bindings --> 178: <bindings> 179: 180: <!-- customBinding --> 181: <customBinding> 182: <binding name="customBinding" 183: closeTimeout="00:10:00" 184: openTimeout="00:10:00" 185: receiveTimeout="00:10:00" 186: sendTimeout="00:10:00"> 187: <clientCaching enabled="true" 188: header="true" 189: timeout="01:00:00" 190: cacheType="AppFabricCache" 191: maxBufferSize="65536" 192: keyCreationMethod="MessageBody"> 193: <operations> 194: <operation action="GetTitles" 195: keyCreationMethod="MessageBody" 196: cacheType="AppFabricCache" 197: timeout="08:00:00" /> 198: </operations> 199: </clientCaching> 200: <textMessageEncoding messageVersion="Soap11" /> 201: <httpTransport /> 202: </binding> 203: </customBinding> 204: 205: <!-- basicHttpBinding --> 206: <basicHttpBinding> 207: <binding name="basicHttpBinding" 208: closeTimeout="00:10:00" 209: openTimeout="00:10:00" 210: receiveTimeout="00:10:00" 211: sendTimeout="00:10:00" 212: allowCookies="false" 213: bypassProxyOnLocal="false" 214: hostNameComparisonMode="StrongWildcard" 215: maxBufferSize="10485760" 216: maxBufferPoolSize="1048576" 217: maxReceivedMessageSize="10485760" 218: messageEncoding="Text" 219: textEncoding="utf-8" 220: transferMode="Buffered" 221: useDefaultWebProxy="true"> 222: <security mode="None"> 223: <transport clientCredentialType="None" proxyCredentialType="None" 224: realm="" /> 225: <message clientCredentialType="UserName" algorithmSuite="Default" /> 226: </security> 227: </binding> 228: </basicHttpBinding> 229: 230: <!-- basicHttpRelayBinding --> 231: <basicHttpRelayBinding> 232: <binding name="basicHttpRelayBinding"> 233: <security mode="None" relayClientAuthenticationType="None"/> 234: </binding> 235: </basicHttpRelayBinding> 236: </bindings> 237: 238: <!-- WCF Behaviors --> 239: <behaviors> 240: <!-- WCF Endpoint Behaviors --> 241: <endpointBehaviors> 242: <!-- basicHttpRelayBinding --> 243: <behavior name="cachingEndpointBehavior"> 244: <!-- The attributes of the cachingBehavior element define default values for the caching channel --> 245: <cachingBehavior enabled="true" 246: header="true" 247: timeout="08:00:00" 248: cacheType="AppFabricCache" 249: maxBufferSize="65536" 250: keyCreationMethod="Simple"> 251: <operations> 252: <!-- The attributes of an operation element define the 253: caching channel behavior for a given service action --> 254: <operation action="GetTitles" 255: keyCreationMethod="Simple" 256: cacheType="AppFabricCache" 257: timeout="00:05:00" /> 258: </operations> 259: </cachingBehavior> 260: </behavior> 261: </endpointBehaviors> 262: </behaviors> 263: 264: <!-- Register WCF Extensions --> 265: <extensions> 266: <behaviorExtensions> 267: <!-- This item is required to register the caching endpoint behavior --> 268: <add name="cachingBehavior" 269: type="Microsoft.AppFabric.CAT.Samples.WCF.ClientCache.ExtensionLibrary.ClientCacheBehaviorExtensionElement, 270: Microsoft.AppFabric.CAT.Samples.WCF.ClientCache.ExtensionLibrary, 271: Version=1.0.0.0, Culture=neutral, PublicKeyToken=8f6257ebc688af7c"/> 272: <!-- Thes following items are required to register the Service Bus behaviors --> 273: <add name="connectionStatusBehavior" 274: type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, 275: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 276: <add name="transportClientEndpointBehavior" 277: type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, 278: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 279: <add name="serviceRegistrySettings" 280: type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, 281: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 282: </behaviorExtensions> 283: <bindingElementExtensions> 284: <!-- This item is required to register the caching binding element --> 285: <add name="clientCaching" 286: type="Microsoft.AppFabric.CAT.Samples.WCF.ClientCache.ExtensionLibrary.ClientCacheBindingExtensionElement, 287: Microsoft.AppFabric.CAT.Samples.WCF.ClientCache.ExtensionLibrary, 288: Version=1.0.0.0, Culture=neutral, PublicKeyToken=8f6257ebc688af7c"/> 289: <!-- Thes following items are required to register the Service Bus binding elements --> 290: <add name="tcpRelayTransport" 291: type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, 292: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 293: <add name="httpRelayTransport" 294: type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, 295: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 296: <add name="httpsRelayTransport" 297: type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, 298: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 299: <add name="onewayRelayTransport" 300: type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, 301: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 302: </bindingElementExtensions> 303: <!-- The following items are required to register the Service Bus bindings --> 304: <bindingExtensions> 305: <add name="basicHttpRelayBinding" 306: type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, 307: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 308: <add name="webHttpRelayBinding" 309: type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, 310: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 311: <add name="ws2007HttpRelayBinding" 312: type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, 313: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 314: <add name="netTcpRelayBinding" 315: type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, 316: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 317: <add name="netOnewayRelayBinding" 318: type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, 319: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 320: <add name="netEventRelayBinding" 321: type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, 322: Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 323: </bindingExtensions> 324: </extensions> 325: 326: </system.serviceModel> 327: 328: <!-- Assembly Redirection --> 329: <runtime> 330: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 331: <dependentAssembly> 332: <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Client" 333: publicKeyToken="31bf3856ad364e35" 334: culture="Neutral" /> 335: <bindingRedirect oldVersion="1.0.0.0" 336: newVersion="101.0.0.0"/> 337: </dependentAssembly> 338: <dependentAssembly> 339: <assemblyIdentity name="Microsoft.ApplicationServer.Caching.Core" 340: publicKeyToken="31bf3856ad364e35" 341: culture="Neutral" /> 342: <bindingRedirect oldVersion="1.0.0.0" 343: newVersion="101.0.0.0"/> 344: </dependentAssembly> 345: </assemblyBinding> 346: </runtime> 347: </configuration>
Please find below a brief description of the main elements and sections of the configuration file:
As you can easily notice, both the cachingBehavior and clientCaching components share the same configuration that is defined as follows:
cachingBehavior and clientCaching elements:
operation element:
NOTE When you install the Windows Azure AppFabric SDK on your development machine, the setup process registers the Service Bus relay bindings, binding elements and behaviors as WCF extensions in the machine.config. However, these extensions are not installed by default in a Windows Azure VM when you deploy an Windows Azure application to the cloud. The documentation on MSDN suggests to perform the following steps when you develop a Windows Azure-Hosted application that uses the Service Bus to invoke a remote service:
In Solution Explorer, under the WorkerRole or WebRole node (depending on where you have your code), add the Microsoft.ServiceBus assembly to your Windows Azure project as a reference. This step is the standard process for adding a reference to an assembly.
In the Reference folder, right-click Microsoft.ServiceBus. Then click Properties.
In the Properties dialog, set Copy Local to True. Doing so makes sure that the Microsoft.ServiceBus assembly will be available to your application when it runs on Windows Azure.
In your ServiceDefinition.csdef file, set the enableNativeCodeExecution field to true.
However, the above steps are not sufficient. In order to use the relay bindings in a Web Role or a Worker Role running in the cloud, you need to make sure to properly register them as WCF extensions in the application configuration file or use a Startup Task to register them in the machine.config. In this case, make sure to add the following XML snippet to the configuration/system.serviceModel section of the configuration file.
<!-- Register WCF Extensions --> <extensions> <behaviorExtensions> <!-- Thes following items are required to register the Service Bus behaviors --> <add name="connectionStatusBehavior" type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </behaviorExtensions> <bindingElementExtensions> <!-- Thes following items are required to register the Service Bus binding elements --> <add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="httpRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="httpsRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="onewayRelayTransport" type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </bindingElementExtensions> <!-- The following items are required to register the Service Bus bindings --> <bindingExtensions> <add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="webHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="ws2007HttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="netOnewayRelayBinding" type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="netEventRelayBinding" type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </bindingExtensions> </extensions>
This section contains the code of the main classes of the solution. You can find the rest of the components in the source code that accompanies this article. The following table contains the source code for the Service.aspx page running in the web role.
namespace PubsWebRole { public enum CallMode { DirectCall, UseCachingAPIExplicitly, UseCachingAPIViaWCFChannel, UseSBWithoutCaching, UseSBWithCaching } public partial class Service : System.Web.UI.Page { #region Static Fields private static DataCacheFactory dataCacheFactory = new DataCacheFactory(); #endregion #region Protected Methods protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { TitlesGridView.DataSource = new List<Book>(); TitlesGridView.DataBind(); } } protected void AuthorsDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e) { e.Command.CommandTimeout = 300; } protected void GetTitles_Click(object sender, EventArgs e) { CallMode callMode; if (DirectCall.Checked) { callMode = CallMode.DirectCall; } else if (UseCachingAPIExplicitly.Checked) { callMode = CallMode.UseCachingAPIExplicitly; } else if (UseCachingAPIViaWCFChannel.Checked) { callMode = CallMode.UseCachingAPIViaWCFChannel; } else if (UseSBWithoutCaching.Checked) { callMode = CallMode.UseSBWithoutCaching; } else { callMode = CallMode.UseSBWithCaching; } TitlesGridView.DataSource = GetTitles(Authors.SelectedValue, callMode); TitlesGridView.DataBind(); } #endregion #region Private Methods private List<Book> GetTitles(string authorId, CallMode callMode) { if (string.IsNullOrEmpty(authorId)) { return new List<Book>(); } bool faulted = false; AuthorsClient proxy = null; List<Book> bookList = null; Book[] books = null; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); try { switch (callMode) { case CallMode.UseCachingAPIExplicitly: // Use Caching API Explicitly and via Windows Azure Connect. // CACHE ASIDE PATTERN: the application will first // check to see if the data is in the cache. // If not, it will invoke the underlying WCf service, // write the data in the cache, then return the value. DataCache dataCache = null; string key = string.Format("GetTitles_{0}", authorId); // The dataCacheFactory is created once in the static // constructor for performance reasons if (dataCacheFactory != null) { dataCache = dataCacheFactory.GetDefaultCache(); if (dataCache != null) { bookList = dataCache.Get(key) as List<Book>; } } if (bookList != null) { return bookList; } else { proxy = new AuthorsClient("DirectCallNoCache"); books = proxy.GetTitles(authorId); if (books != null) { bookList = new List<Book>(books); if (dataCache != null) { dataCache.Put(key, bookList); } return bookList; } } break; case CallMode.UseCachingAPIViaWCFChannel: // UUse Caching API via WCF channel and Windows Azure Connect. proxy = new AuthorsClient("UseWCFCachingChannel"); books = proxy.GetTitles(authorId); if (books != null) { return new List<Book>(books); } break; case CallMode.DirectCall: // Directly invoke the WCF service via Windows Azure Connect. proxy = new AuthorsClient("DirectCallNoCache"); books = proxy.GetTitles(authorId); if (books != null) { return new List<Book>(books); } break; case CallMode.UseSBWithCaching: // Use Caching API via WCF channel and Service Bus. proxy = new AuthorsClient("UseSBWithCaching"); books = proxy.GetTitles(authorId); if (books != null) { return new List<Book>(books); } break; default: // Directly invoke the WCF service via Service Bus. proxy = new AuthorsClient("UseSBWithoutCaching"); books = proxy.GetTitles(authorId); if (books != null) { return new List<Book>(books); } break; } } catch (FaultException ex) { faulted = true; MessageLiteral.Text = string.Format("<b>Exception</b>: {0}", ex.Message); if (proxy != null) { proxy.Abort(); } } catch (CommunicationException ex) { faulted = true; MessageLiteral.Text = string.Format("<b>Exception</b>: {0}", ex.Message); if (proxy != null) { proxy.Abort(); } } catch (TimeoutException ex) { faulted = true; MessageLiteral.Text = string.Format("<b>Exception</b>: {0}", ex.Message); if (proxy != null) { proxy.Abort(); } } catch (Exception ex) { faulted = true; MessageLiteral.Text = string.Format("<b>Exception</b>: {0}", ex.Message); if (proxy != null) { proxy.Abort(); } } finally { stopwatch.Stop(); if (!faulted) { MessageLiteral.Text = string.Format(
"<b>CallMode</b>: {0}</br><b>Elapsed Time (milliseconds)</b>: {1}", callMode, stopwatch.ElapsedMilliseconds); } } return new List<Book>(); } #endregion } }
The following table contains the source code for the Cache class in the AppFabricCache project. This class implements the Windows Azure AppFabric Caching caching provider used by the caching channel.
namespace Microsoft.AppFabric.CAT.Samples.WCF.ClientCache.AppFabricCache { public static class Cache { #region Private Static Fields private static DataCache dataCache; #endregion #region Static Constructor static Cache() { try { DataCacheFactory dataCacheFactory = new DataCacheFactory(); dataCache = dataCacheFactory.GetDefaultCache(); } catch (Exception ex) { ExceptionHelper.HandleException(ex); } } #endregion #region Public Static Methods /// <summary> /// Adds an object to the cache. /// </summary> /// <typeparam name="T">Type of item being added.</typeparam> /// <param name="key">The unique value that is used to identify the object in the cache.</param> /// <param name="value">The object saved to the cache cluster.</param> /// <param name="timeout">The amount of time the object should reside in the cache before expiration.</param> public static void Add<T>(string key, T value, TimeSpan timeout) { if (value == null) { return; } dataCache.Put(key, value, timeout); } /// <summary> /// Used to retrieve an item from the cache. /// </summary> /// <typeparam name="T">Type of item being added.</typeparam> /// <param name="key">The unique value that is used to identify the object in the cache.</param> /// <returns>An item of type T if found in the cache using the key provided, null otherwise.</returns> public static T Get<T>(string key) { return (T)dataCache.Get(key); } #endregion } }
The following performance results have been collected under the following condition:
NOTE According to my tests, invoking a WCF service running on-premises in the organization's network from a Web Role using Windows Azure Connect and the BasicHttpBinding is slightly faster than invoking the same service via the Service Bus and the BasicHttpRelayBinding. Nevertheless, more accurate and exhaustive performance tests should be conducted to confirm this result.
The caching channel shown in this article can be used to transparently inject client-side caching capabilities into an existing Windows Azure application that uses WCF to invoke one or multiple back-end services. The use of client-side caching techniques allows to dramatically increase performance and reduce the costs due to the use of Windows Azure Connect and Service Bus. The source code that accompanies the article can be downloaded here. As always, any feedback is more than welcome!
For more information on the topic discussed in this blog post, please refer to the following: