最近用ActionFilter給REST Api加入本地緩存功能,在OnActionExecutedAsync重寫中,須要將緩存對象的內容以byte[]的形式存入緩存,並緩存Etag、ContentType信息。web
而在該方法中以api
var content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false);
獲取byte[]形式的響應內容時,提示Cannot access a closed Stream.即認爲響應流已關閉,但其實上下文中並未顯示關閉Stream對象或以using操做流對象。緩存
爲了驗證,一樣的代碼在一個新建的web api2項目中使用,則可以正常獲取響應內容,查了好久未果。。。。async
如下是上下文代碼:ide
1 public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) 2 { 3 if (actionExecutedContext.ActionContext.Response == null 4 || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) 5 return; 6 if (!IsCachingAllowed(actionExecutedContext.ActionContext)) 7 return; 8 var cacheTime = CacheTimeQuery.Execute(DateTime.Now); 9 if (cacheTime.AbsoluteExpiration > DateTime.Now) 10 { 11 var httpConfig = actionExecutedContext.Request.GetConfiguration(); 12 var config = httpConfig.RestCacheConfiguration(); 13 var cacheKeyGenerator = config.GetCacheKeyGenerator(CacheKeyGenerator); 14 var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue 15 ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext); 16 var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType); 17 if (!string.IsNullOrWhiteSpace(cachekey) && !(_restCache.Contains(cachekey))) 18 { 19 SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime)); 20 var responseContent = actionExecutedContext.Response.Content; 21 if (responseContent != null) 22 { 23 var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName); 24 var contentType = responseContent.Headers.ContentType; 25 string etag = actionExecutedContext.Response.Headers.ETag.Tag; 26 try 27 { 28 var content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false); 29 responseContent.Headers.Remove("Content-Length"); 30 _restCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration); 31 _restCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey); 32 _restCache.Add(cachekey + Constants.ContentTypeKey, 33 contentType, 34 cacheTime.AbsoluteExpiration, baseKey); 35 _restCache.Add(cachekey + Constants.EtagKey, 36 etag, 37 cacheTime.AbsoluteExpiration, baseKey); 38 } 39 catch (Exception exp) 40 { 41 //捕捉到Cannot access a closed Stream. 42 throw; 43 } 44 } 45 } 46 } 47 ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime); 48 }
既然想以byte[]的形式緩存響應內容失效,只能另想辦法,所以,直接緩存response content對象,在OnActionExecuting方法中取出該ObjectContent對象,再構建一個相同對象做爲response content便可,如下爲代碼:ui
OnActionExecuting:spa
public override void OnActionExecuting(HttpActionContext actionContext) { if (actionContext == null) throw new ArgumentNullException("actionContext"); if (!IsCachingAllowed(actionContext)) return; var config = actionContext.Request.GetConfiguration(); EnsureCacheTimeQuery(); EnsureCache(config); var cacheKeyGenerator = config.RestCacheConfiguration().GetCacheKeyGenerator(CacheKeyGenerator); var responseMediaType = GetExpectedMediaType(config, actionContext); actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType; var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType); if (!_restCache.Contains(cachekey)) return; if (actionContext.Request.Headers.IfNoneMatch != null) { var etag = _restCache.Get<string>(cachekey + Constants.EtagKey); if (etag != null) { if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag)) { var time = CacheTimeQuery.Execute(DateTime.Now); var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified); ApplyCacheHeaders(quickResponse, time); actionContext.Response = quickResponse; return; } } } var val = _restCache.Get<ObjectContent>(cachekey); if (val == null) return; var contenttype = _restCache.Get<MediaTypeHeaderValue>(cachekey + Constants.ContentTypeKey) ?? responseMediaType; actionContext.Response = actionContext.Request.CreateResponse(); var newContent = new ObjectContent<object>(val.Value, val.Formatter); actionContext.Response.Content = newContent; actionContext.Response.Content.Headers.ContentType = contenttype; var responseEtag = _restCache.Get<string>(cachekey + Constants.EtagKey); if (responseEtag != null) SetEtag(actionContext.Response, responseEtag); var cacheTime = CacheTimeQuery.Execute(DateTime.Now); ApplyCacheHeaders(actionContext.Response, cacheTime); }
OnActionExecutedAsync:3d
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return; if (!IsCachingAllowed(actionExecutedContext.ActionContext)) return; var cacheTime = CacheTimeQuery.Execute(DateTime.Now); if (cacheTime.AbsoluteExpiration > DateTime.Now) { var httpConfig = actionExecutedContext.Request.GetConfiguration(); var config = httpConfig.RestCacheConfiguration(); var cacheKeyGenerator = config.GetCacheKeyGenerator(CacheKeyGenerator); var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext); var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType); if (!string.IsNullOrWhiteSpace(cachekey) && !(_restCache.Contains(cachekey))) { SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime)); var responseContent = actionExecutedContext.Response.Content; if (responseContent != null) { var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName); var contentType = responseContent.Headers.ContentType; string etag = actionExecutedContext.Response.Headers.ETag.Tag; try { responseContent.Headers.Remove("Content-Length"); _restCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration); _restCache.Add(cachekey, responseContent, cacheTime.AbsoluteExpiration, baseKey); _restCache.Add(cachekey + Constants.ContentTypeKey, contentType, cacheTime.AbsoluteExpiration, baseKey); _restCache.Add(cachekey + Constants.EtagKey, etag, cacheTime.AbsoluteExpiration, baseKey); } catch (Exception exp) { //捕捉到Cannot access a closed Stream. throw; } } } } ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime); }
相同的代碼在不一樣上下文中對流操做表現不一樣,該問題還未找到緣由,有待排查。rest