ASP.NET的輸出緩存(Output Caching)機制允許我們針對整個Web頁面或者頁面的某個部分(主要針對用戶控件)最終呈現的HTML進行緩存。對於后續針對相同資源的請求,只需要直接將緩存的HTML予以回復而無須按照頁面處理生命周期對每次請求進行重復處理。WCF通過操作行為AspNetCacheProfileAttribute利用ASP.NET的輸出緩存提供一種針對於某個操作的聲明式緩存機制。[源代碼從這里下載]
一、AspNetCacheProfileAttribute
WCF對ASP.NET緩存的支持是通過AspNetCacheProfileAttribute特性來實現的。通過如下的代碼我們不難看出AspNetCacheProfileAttribute是實現了IOperationBehavior接口的操作行為,我們可以直接將其應用到契約接口/類中的某個具有緩存需要的操作方法上。
1: [AttributeUsage(AttributeTargets.Method)]
2: public sealed class AspNetCacheProfileAttribute : Attribute, IOperationBehavior
3: {
4: //其他成員
5: public AspNetCacheProfileAttribute(string cacheProfileName);
6: public string CacheProfileName { get; }
7: }
AspNetCacheProfileAttribute構造函數參數cacheProfileName表示的CacheProfile的配置名稱,目標操作按照定義在相應CacheProfile的緩存策略實施緩存。CacheProfile配置在<system.web>/<caching>/<outputCacheSettings>/<outputCacheProfiles>節點下。
1: <configuration>
2: <connectionStrings>
3: <add name="localDb"
4: connectionString="Server=.; Database=TestDb; Uid=sa; Pwd=password"
5: providerName="System.Data.SqlClient"/>
6: </connectionStrings>
7: <system.web>
8: <caching>
9: <outputCacheSettings>
10: <outputCacheProfiles>
11: <add name="default"
12: duration="60"
13: varyByParam="none"
14: sqlDependency="TestDb: TestTable"/>
15: </outputCacheProfiles>
16: </outputCacheSettings>
17: <sqlCacheDependency>
18: <databases>
19: <add name="TestDb" connectionStringName="localDb"/>
20: </databases>
21: </sqlCacheDependency>
22: </caching>
23: </system.web>
24: </configuration>
在如上所示的配置片斷中,我們定義了一個名稱為default的CacheProfile。代表緩存時間的duration屬性被設置為60,意味着緩存項在被存儲之后1分鍾之后實失效;屬性varyByParam被設置為none表示緩存項與請求的查詢字符串無關。此外,該CacheProfile還設置針對某個本地數據庫中的TestTable表的SQL依賴(SQL Dependency)。關於CacheProfile的配置屬於ASP.NET的范疇,在這里我們不會作過多的討論。
既然是采用ASP.NET輸出緩存,WCF服務自然需要采用IIS寄宿並采用ASP.NET 兼容模式。值得一提的是,基於AspNetCacheProfileAttribute的輸出緩存僅僅針對HTTP-GET。
二、實例演示:創建采用輸出緩存的服務
接下來我們通過一個簡單的實例來演示如何通過操作行為對某個操作的返回值實施緩存,為此我們創建一個用於返回當前時間的服務。如下所示的是作為服務契約的ITime接口的定義,AspNetCacheProfileAttribute特性被應用到了用於返回當前時間的操作方法GetCurrentTime上。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Web;
4: namespace Artech.WcfServices.Service.Interface
5: {
6: [ServiceContract(Namespace = "http://www.artech.com/")]
7: public interface ITime
8: {
9: [WebGet(UriTemplate = "/current")]
10: [AspNetCacheProfile("default")]
11: DateTime GetCurrentTime();
12: }
13: }
實現了契約接口ITime的服務類型TimeService定義如下。我們將AspNetCompatibilityRequirementsAttribute特性應用在服務類型上並將RequirementsMode屬性設置為Allowed以提供對ASP.NET兼容模式的支持。
1: using System;
2: using System.ServiceModel.Activation;
3: using Artech.WcfServices.Service.Interface;
4: namespace Artech.WcfServices.Service
5: {
6: [AspNetCompatibilityRequirements(
7: RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
8: public class TimeService : ITime
9: {
10: public DateTime GetCurrentTime()
11: {
12: return DateTime.Now;
13: }
14: }
15: }
在一個Web項目中我們為通過IIS寄宿的服務TimeService添加一個對應的.svc文件(TimeService.svc),如下所示的是<%@ServiceHost%>指令的定義。表示ServiceHostFactory類型的指令屬性Factory被設置為System.ServiceModel.Activation.WebServiceHostFactory.
1: <%@ ServiceHost Service="Artech.WcfServices.Service.TimeService" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>
我們在作為服務宿主的Web項目下添加一個配置文件(Web.config)並定義如下的配置。除了服務寄宿的基本配置外,我們將<system.serviceModel>/<serviceHostingEnvironment >配置節的aspNetCompatibilityEnabled屬性設置為True以開啟ASP.NET兼容模式。應用在操作方法GetCurrentTime上的AspNetCacheProfileAttribute特性中指定的名稱為default的CacheProfile定義在該配置中,duration和varyByParam分別被設置為60和none。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Artech.WcfServices.Service.TimeService">
5: <endpoint binding="webHttpBinding"
6: contract="Artech.WcfServices.Service.Interface.ITime"/>
7: </service>
8: </services>
9: <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
10: </system.serviceModel>
11: <system.web>
12: <caching>
13: <outputCacheSettings>
14: <outputCacheProfiles>
15: <add name="default" duration="60" varyByParam="none"/>
16: </outputCacheProfiles>
17: </outputCacheSettings>
18: </caching>
19: </system.web>
20: </configuration>
作為客戶端的控制台程序在進行了相應配置之后通過如下的代碼進行服務的調用。在這段代碼中,我們通過創建的服務代理進行了5次服務調用,並將獲取的時間打印出來。每次服務的時間間隔為1秒。
1: using (ChannelFactory<ITime> channelFactory = new ChannelFactory<ITime>("timeService"))
2: {
3: ITime proxy = channelFactory.CreateChannel();
4: for (int i = 0; i < 5; i++)
5: {
6: Console.WriteLine(proxy.GetCurrentTime().ToLongTimeString());
7: Thread.Sleep(1000);
8: }
9: }
客戶端代碼執行之后會在控制台上輸出如下的結果。由於服務端通過ASP.NET的輸出緩存對第一次執行GetCurrentTime操作的結果進行了緩存,所以客戶端返回的時間都是相同的。
1: 4:48:43 PM
2: 4:48:43 PM
3: 4:48:43 PM
4: 4:48:43 PM
5: 4:48:43 PM
三、 AspNetCacheProfileAttribute是如何實現輸出緩存的?
既然我們采用ASP.NET兼容模式來寄宿服務,意味着我們調用某個服務與訪問某個頁面沒有本質的區別,所以基於Web頁面的輸出緩存能夠應用於基於某個服務操作的調用就不足為奇了。現在有這么一個問題:通過AspNetCacheProfileAttribute特性指定CacheProfile是如何生效的?
如果對ASP.NET具有一定的了解,應該知道可以通過當前HttpResponse(HttpContext.Current.Response)的Cache屬性表示的HttpCachePolicy對象來控制當前輸出緩存的基本策略。實際上AspNetCacheProfileAttribute就是通過這種方式將定義在指定CacheProfile的緩存策略應用到針對當前操作的調用上的。
具體來說,AspNetCacheProfileAttribute針對輸出緩存策略的控制是通過一個實現了接口IParameterInspector的自定義參數檢驗器實現的,這是一個名稱為CachingParameterInspector的內部類型。操作行為AspNetCacheProfileAttribute通過實現的ApplyDispatchBehavior方法將針對某個CacheProfile創建的CachingParameterInspector對象添加到當前分發操作(DispatchOperation)的參數檢驗器列表中。
1: internal class CachingParameterInspector : IParameterInspector
2: {
3: public CachingParameterInspector(string cacheProfileName);
4: public object BeforeCall(string operationName, object[] inputshens);
5: public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
6: {
7: //將指定CacheProfile的輸出緩存策略應用到當前HttpResponse
8: }
9: }
如上面的代碼片斷所示,當AfterCall方法被執行的之后,在構造函數中指定的CacheProfile定義的輸出緩存策略應用到當前HttpResponse。而AfterCall會在操作執行之后,回復消息序列化之前被執行。