From 0e3fe7493e098f2940630fd24adf4157e461efd4 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 16 Dec 2015 20:01:55 -0800 Subject: [PATCH 1/6] Streamlining feature cache and object re-initialiation code paths Shouldn't increase object size, but does improve maintainability --- .../FeatureReferences.cs | 57 ++++ .../DefaultAuthenticationManager.cs | 47 +--- .../DefaultConnectionInfo.cs | 68 ++--- .../DefaultHttpContext.cs | 244 +++++++----------- .../DefaultHttpRequest.cs | 101 ++------ .../DefaultHttpResponse.cs | 67 ++--- .../DefaultWebSocketManager.cs | 54 ++-- .../Features/FeatureHelpers.cs | 116 --------- .../Features/IFeatureCache.cs | 11 - .../Features/QueryFeature.cs | 34 +-- .../Features/RequestCookiesFeature.cs | 35 +-- .../Features/ResponseCookiesFeature.cs | 30 +-- .../DefaultHttpContextTests.cs | 86 +++--- 13 files changed, 299 insertions(+), 651 deletions(-) create mode 100644 src/Microsoft.AspNet.Http.Features/FeatureReferences.cs delete mode 100644 src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs delete mode 100644 src/Microsoft.AspNet.Http/Features/IFeatureCache.cs diff --git a/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs new file mode 100644 index 00000000..75fcacc0 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Http.Features +{ + public struct FeatureReferences + { + public FeatureReferences(IFeatureCollection collection) + { + Collection = collection; + Cache = default(TCache); + Revision = collection.Revision; + } + + public readonly IFeatureCollection Collection; + public int Revision; + public TCache Cache; + + public TFeature Fetch( + ref TFeature cached, + TState state, + Func factory) + { + var cleared = false; + if (Revision != Collection.Revision) + { + cleared = true; + Cache = default(TCache); + Revision = Collection.Revision; + } + + var feature = cached; + if (feature == null) + { + feature = Collection.Get(); + if (feature == null) + { + feature = factory(state); + + Collection.Set(feature); + if (!cleared) + { + Cache = default(TCache); + } + Revision = Collection.Revision; + } + cached = feature; + } + return feature; + } + + public TFeature Fetch(ref TFeature cached, Func factory) => + Fetch(ref cached, Collection, factory); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs index ba239a6c..0c8db874 100644 --- a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs @@ -12,56 +12,27 @@ namespace Microsoft.AspNet.Http.Authentication.Internal { - public class DefaultAuthenticationManager : AuthenticationManager, IFeatureCache + public class DefaultAuthenticationManager : AuthenticationManager { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpAuthenticationFeature _authentication; + private FeatureReferences _features; public DefaultAuthenticationManager(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + Initialize(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(IFeatureCollection features) { - _features = features; - ResetFeatures(); + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _authentication = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _features = default(FeatureReferences); } - private IHttpAuthenticationFeature HttpAuthenticationFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpAuthenticationFeature(), - ref _authentication); - } - } + private IHttpAuthenticationFeature HttpAuthenticationFeature => + _features.Fetch(ref _features.Cache, f => new HttpAuthenticationFeature()); public override IEnumerable GetAuthenticationSchemes() { diff --git a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs index 66fdbbe9..dd19f7d2 100644 --- a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs +++ b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs @@ -10,70 +10,30 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultConnectionInfo : ConnectionInfo, IFeatureCache + public class DefaultConnectionInfo : ConnectionInfo { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpConnectionFeature _connection; - private ITlsConnectionFeature _tlsConnection; + private FeatureReferences _features; public DefaultConnectionInfo(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } + Initialize(features); } - void IFeatureCache.SetFeaturesRevision() + public virtual void Initialize( IFeatureCollection features) { - _cachedFeaturesRevision = _features.Revision; + _features = new FeatureReferences(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Uninitialize() { - _features = features; - ResetFeatures(); + _features = default(FeatureReferences); } - private void ResetFeatures() - { - _connection = null; - _tlsConnection = null; + private IHttpConnectionFeature HttpConnectionFeature => + _features.Fetch(ref _features.Cache.Connection, f => new HttpConnectionFeature()); - ((IFeatureCache)this).SetFeaturesRevision(); - } - - private IHttpConnectionFeature HttpConnectionFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpConnectionFeature(), - ref _connection); - } - } - - private ITlsConnectionFeature TlsConnectionFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new TlsConnectionFeature(), - ref _tlsConnection); - } - } + private ITlsConnectionFeature TlsConnectionFeature=> + _features.Fetch(ref _features.Cache.TlsConnection, f => new TlsConnectionFeature()); public override IPAddress RemoteIpAddress { @@ -115,5 +75,11 @@ public override X509Certificate2 ClientCertificate { return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken); } + + struct FeatureInterfaces + { + public IHttpConnectionFeature Connection; + public ITlsConnectionFeature TlsConnection; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index 9fcfd968..9cc4211c 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -14,173 +14,99 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpContext : HttpContext, IFeatureCache + public class DefaultHttpContext : HttpContext { - private readonly DefaultHttpRequest _request; - private readonly DefaultHttpResponse _response; + private FeatureReferences _features; - private DefaultAuthenticationManager _authenticationManager; - private DefaultConnectionInfo _connection; - private DefaultWebSocketManager _websockets; - - private IItemsFeature _items; - private IServiceProvidersFeature _serviceProviders; - private IHttpAuthenticationFeature _authentication; - private IHttpRequestLifetimeFeature _lifetime; - private ISessionFeature _session; - - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; + private HttpRequest _request; + private HttpResponse _response; + private AuthenticationManager _authenticationManager; + private ConnectionInfo _connection; + private WebSocketManager _websockets; public DefaultHttpContext() : this(new FeatureCollection()) { - _features.Set(new HttpRequestFeature()); - _features.Set(new HttpResponseFeature()); - ((IFeatureCache)this).SetFeaturesRevision(); + Features.Set(new HttpRequestFeature()); + Features.Set(new HttpResponseFeature()); } public DefaultHttpContext(IFeatureCollection features) { - _features = features; - _request = new DefaultHttpRequest(this, features); - _response = new DefaultHttpResponse(this, features); - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision !=_features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + Initialize(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(IFeatureCollection features) { - _features = features; - ResetFeatures(); - - _request.UpdateFeatures(features); - _response.UpdateFeatures(features); - - _authenticationManager?.UpdateFeatures(features); - _connection?.UpdateFeatures(features); - _websockets?.UpdateFeatures(features); - } - - private void ResetFeatures() - { - _items = null; - _serviceProviders = null; - _authentication = null; - _lifetime = null; - _session = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _features = new FeatureReferences(features); + _request = InitializeHttpRequest(); + _response = InitializeHttpResponse(); } - IItemsFeature ItemsFeature + public virtual void Uninitialize() { - get + _features = default(FeatureReferences); + if (_request != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new ItemsFeature(), - ref _items); + UninitializeHttpRequest(_request); + _request = null; } - } - - IServiceProvidersFeature ServiceProvidersFeature - { - get + if (_response != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new ServiceProvidersFeature(), - ref _serviceProviders); + UninitializeHttpResponse(_response); + _response = null; } - } - - private IHttpAuthenticationFeature HttpAuthenticationFeature - { - get + if (_authenticationManager != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpAuthenticationFeature(), - ref _authentication); + UninitializeAuthenticationManager(_authenticationManager); + _authenticationManager = null; } - } - - private IHttpRequestLifetimeFeature LifetimeFeature - { - get + if (_connection != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpRequestLifetimeFeature(), - ref _lifetime); + UninitializeConnectionInfo(_connection); + _connection = null; } - } - - private ISessionFeature SessionFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _session); } - set + if (_websockets != null) { - _features.Set(value); - _session = value; + UninitializeWebSocketManager(_websockets); + _websockets = null; } } + + private IItemsFeature ItemsFeature => + _features.Fetch(ref _features.Cache.Items, f => new ItemsFeature()); - private IHttpRequestIdentifierFeature RequestIdentifierFeature - { - get { - return FeatureHelpers.GetOrCreate( - _features, - () => new HttpRequestIdentifierFeature()); - } - } + private IServiceProvidersFeature ServiceProvidersFeature => + _features.Fetch(ref _features.Cache.ServiceProviders, f => new ServiceProvidersFeature()); - public override IFeatureCollection Features { get { return _features; } } + private IHttpAuthenticationFeature HttpAuthenticationFeature => + _features.Fetch(ref _features.Cache.Authentication, f => new HttpAuthenticationFeature()); - public override HttpRequest Request { get { return _request; } } + private IHttpRequestLifetimeFeature LifetimeFeature => + _features.Fetch(ref _features.Cache.Lifetime, f => new HttpRequestLifetimeFeature()); - public override HttpResponse Response { get { return _response; } } + private ISessionFeature SessionFeature => + _features.Fetch(ref _features.Cache.Session, f => new DefaultSessionFeature()); - public override ConnectionInfo Connection - { - get - { - if (_connection == null) - { - _connection = new DefaultConnectionInfo(_features); - } - return _connection; - } - } + private ISessionFeature SessionFeatureOrNull => + _features.Fetch(ref _features.Cache.Session, f => null); + + + private IHttpRequestIdentifierFeature RequestIdentifierFeature => + _features.Fetch(ref _features.Cache.RequestIdentifier, f => new HttpRequestIdentifierFeature()); + + public override IFeatureCollection Features => _features.Collection; + + public override HttpRequest Request => _request; + + public override HttpResponse Response => _response; + + public override ConnectionInfo Connection => _connection ?? (_connection = InitializeConnectionInfo()); + + public override AuthenticationManager Authentication => _authenticationManager ?? (_authenticationManager = InitializeAuthenticationManager()); + + public override WebSocketManager WebSockets => _websockets ?? (_websockets = InitializeWebSocketManager()); - public override AuthenticationManager Authentication - { - get - { - if (_authenticationManager == null) - { - _authenticationManager = new DefaultAuthenticationManager(_features); - } - return _authenticationManager; - } - } public override ClaimsPrincipal User { @@ -225,7 +151,7 @@ public override ISession Session { get { - var feature = SessionFeature; + var feature = SessionFeatureOrNull; if (feature == null) { throw new InvalidOperationException("Session has not been configured for this application " + @@ -235,31 +161,41 @@ public override ISession Session } set { - var feature = SessionFeature; - if (feature == null) - { - feature = new DefaultSessionFeature(); - SessionFeature = feature; - } - feature.Session = value; + SessionFeature.Session = value; } } - public override WebSocketManager WebSockets - { - get - { - if (_websockets == null) - { - _websockets = new DefaultWebSocketManager(_features); - } - return _websockets; - } - } + public override void Abort() { LifetimeFeature.Abort(); } + + + protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this, Features); + protected virtual void UninitializeHttpRequest(HttpRequest instance) { } + + protected virtual HttpResponse InitializeHttpResponse() => new DefaultHttpResponse(this, Features); + protected virtual void UninitializeHttpResponse(HttpResponse instance) { } + + protected virtual ConnectionInfo InitializeConnectionInfo() => new DefaultConnectionInfo(Features); + protected virtual void UninitializeConnectionInfo(ConnectionInfo instance) { } + + protected virtual AuthenticationManager InitializeAuthenticationManager() => new DefaultAuthenticationManager(Features); + protected virtual void UninitializeAuthenticationManager(AuthenticationManager instance) { } + + protected virtual WebSocketManager InitializeWebSocketManager() => new DefaultWebSocketManager(Features); + protected virtual void UninitializeWebSocketManager(WebSocketManager instance) { } + + struct FeatureInterfaces + { + public IItemsFeature Items; + public IServiceProvidersFeature ServiceProviders; + public IHttpAuthenticationFeature Authentication; + public IHttpRequestLifetimeFeature Lifetime; + public ISessionFeature Session; + public IHttpRequestIdentifierFeature RequestIdentifier; + } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index caa34099..e03f93c8 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -11,96 +11,41 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpRequest : HttpRequest, IFeatureCache + public class DefaultHttpRequest : HttpRequest { - private readonly DefaultHttpContext _context; - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; - private IQueryFeature _query; - private IFormFeature _form; - private IRequestCookiesFeature _cookies; + private HttpContext _context; + private FeatureReferences _features; public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection features) { - _context = context; - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + Initialize(context, features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(HttpContext context, IFeatureCollection features) { - _features = features; - ResetFeatures(); + _context = context; + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _request = null; - _query = null; - _form = null; - _cookies = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _context = null; + _features = default(FeatureReferences); } - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + public override HttpContext HttpContext => _context; - private IQueryFeature QueryFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new QueryFeature(f), - ref _query); - } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache.Request, f => null); - private IFormFeature FormFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - this, - (r) => new FormFeature(r), - ref _form); - } - } + private IQueryFeature QueryFeature => + _features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f)); - private IRequestCookiesFeature RequestCookiesFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new RequestCookiesFeature(f), - ref _cookies); - } - } + private IFormFeature FormFeature => + _features.Fetch(ref _features.Cache.Form, this, self => new FormFeature(self)); - public override HttpContext HttpContext { get { return _context; } } + private IRequestCookiesFeature RequestCookiesFeature => + _features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f)); public override PathString PathBase { @@ -206,5 +151,13 @@ public override Task ReadFormAsync(CancellationToken cancellati { return FormFeature.ReadFormAsync(cancellationToken); } + + struct FeatureInterfaces + { + public IHttpRequestFeature Request; + public IQueryFeature Query; + public IFormFeature Form; + public IRequestCookiesFeature Cookies; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index 99783ce7..901e17cb 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -10,65 +10,34 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpResponse : HttpResponse, IFeatureCache + public class DefaultHttpResponse : HttpResponse { - private readonly DefaultHttpContext _context; - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpResponseFeature _response; - private IResponseCookiesFeature _cookies; + private HttpContext _context; + private FeatureReferences _features; public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection features) { - _context = context; - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); + Initialize(context, features); } - void IFeatureCache.CheckFeaturesRevision() + public virtual void Initialize(HttpContext context, IFeatureCollection features) { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - public void UpdateFeatures(IFeatureCollection features) - { - _features = features; - ResetFeatures(); + _context = context; + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _response = null; - _cookies = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _context = null; + _features = default(FeatureReferences); } - private IHttpResponseFeature HttpResponseFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } - } + private IHttpResponseFeature HttpResponseFeature => + _features.Fetch(ref _features.Cache.Response, f => null); - private IResponseCookiesFeature ResponseCookiesFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new ResponseCookiesFeature(f), - ref _cookies); - } - } + private IResponseCookiesFeature ResponseCookiesFeature => + _features.Fetch(ref _features.Cache.Cookies, f => new ResponseCookiesFeature(f)); + public override HttpContext HttpContext { get { return _context; } } @@ -163,5 +132,11 @@ public override void Redirect(string location, bool permanent) Headers[HeaderNames.Location] = location; } + + struct FeatureInterfaces + { + public IHttpResponseFeature Response; + public IResponseCookiesFeature Cookies; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs index d7b388fb..8beff4db 100644 --- a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs +++ b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs @@ -10,56 +10,30 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultWebSocketManager : WebSocketManager, IFeatureCache + public class DefaultWebSocketManager : WebSocketManager { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; - private IHttpWebSocketFeature _webSockets; + private FeatureReferences _features; public DefaultWebSocketManager(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); + Initialize(features); } - void IFeatureCache.CheckFeaturesRevision() + public virtual void Initialize(IFeatureCollection features) { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() + public virtual void Uninitialize() { - _cachedFeaturesRevision = _features.Revision; + _features = default(FeatureReferences); } - public void UpdateFeatures(IFeatureCollection features) - { - _features = features; - ResetFeatures(); - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache.Request, f => null); - private void ResetFeatures() - { - _request = null; - _webSockets = null; - - ((IFeatureCache)this).SetFeaturesRevision(); - } - - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } - - private IHttpWebSocketFeature WebSocketFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _webSockets); } - } + private IHttpWebSocketFeature WebSocketFeature => + _features.Fetch(ref _features.Cache.WebSockets, f => null); public override bool IsWebSocketRequest { @@ -85,5 +59,11 @@ public override Task AcceptWebSocketAsync(string subProtocol) } return WebSocketFeature.AcceptAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol }); } + + struct FeatureInterfaces + { + public IHttpRequestFeature Request; + public IHttpWebSocketFeature WebSockets; + } } } diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs deleted file mode 100644 index 86b033fd..00000000 --- a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNet.Http.Features -{ - internal static class FeatureHelpers - { - public static T GetAndCache( - IFeatureCache cache, - IFeatureCollection features, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - cachedObject = obj; - } - return obj; - } - - public static T GetOrCreate( - IFeatureCollection features, - Func factory) - where T : class - { - T obj = features.Get(); - if (obj == null) - { - obj = factory(); - features.Set(obj); - } - - return obj; - } - - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(features); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - HttpRequest request, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(request); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - } -} diff --git a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs b/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs deleted file mode 100644 index 14a220d8..00000000 --- a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Http.Features -{ - internal interface IFeatureCache - { - void CheckFeaturesRevision(); - void SetFeaturesRevision(); - } -} diff --git a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs index 6d404a54..73450ad5 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs @@ -7,12 +7,9 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class QueryFeature : IQueryFeature, IFeatureCache + public class QueryFeature : IQueryFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; + private FeatureReferences _features; private string _original; private IQueryCollection _parsedValues; @@ -34,34 +31,17 @@ public QueryFeature(IFeatureCollection features) throw new ArgumentNullException(nameof(features)); } - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _request = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache, f => null); public IQueryCollection Query { get { - if (_features == null) + if (_features.Collection == null) { if (_parsedValues == null) { @@ -91,7 +71,7 @@ public IQueryCollection Query set { _parsedValues = value; - if (_features != null) + if (_features.Collection != null) { if (value == null) { diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index 811b3e83..4fb087e9 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -9,13 +9,9 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class RequestCookiesFeature : IRequestCookiesFeature, IFeatureCache + public class RequestCookiesFeature : IRequestCookiesFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; - + private FeatureReferences _features; private StringValues _original; private IRequestCookieCollection _parsedValues; @@ -36,34 +32,17 @@ public RequestCookiesFeature(IFeatureCollection features) throw new ArgumentNullException(nameof(features)); } - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _request = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + _features = new FeatureReferences(features); } - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache, f => null); public IRequestCookieCollection Cookies { get { - if (_features == null) + if (_features.Collection == null) { if (_parsedValues == null) { @@ -91,7 +70,7 @@ public IRequestCookieCollection Cookies { _parsedValues = value; _original = StringValues.Empty; - if (_features != null) + if (_features.Collection != null) { if (_parsedValues == null || _parsedValues.Count == 0) { diff --git a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs index caea7e4e..0c87dc28 100644 --- a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs @@ -5,38 +5,18 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class ResponseCookiesFeature : IResponseCookiesFeature, IFeatureCache + public class ResponseCookiesFeature : IResponseCookiesFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpResponseFeature _response; + private FeatureReferences _features; private IResponseCookies _cookiesCollection; public ResponseCookiesFeature(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _response = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - private IHttpResponseFeature HttpResponseFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } - } + private IHttpResponseFeature HttpResponseFeature => + _features.Fetch(ref _features.Cache, f => null); public IResponseCookies Cookies { diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index 01fbd73d..a3239619 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -154,68 +154,63 @@ public void SetItems_NewCollectionUsed() public void UpdateFeatures_ClearsCachedFeatures() { var features = new FeatureCollection(); + features.Set(new HttpRequestFeature()); + features.Set(new HttpResponseFeature()); + features.Set(new TestHttpWebSocketFeature()); var context = new DefaultHttpContext(features); - var request = (DefaultHttpRequest)context.Request; - var response = (DefaultHttpResponse)context.Response; - - var authentication = (DefaultAuthenticationManager)context.Authentication; - var connection = (DefaultConnectionInfo)context.Connection; - var websockets = (DefaultWebSocketManager)context.WebSockets; + Assert.Equal(3, features.Count()); - Assert.Equal(0, features.Count()); - - TestCachedFeaturesAreNull(context.GetType(), context, features); - TestCachedFeaturesAreNull(request.GetType(), request, features); - TestCachedFeaturesAreNull(response.GetType(), response, features); - TestCachedFeaturesAreNull(authentication.GetType(), authentication, features); - TestCachedFeaturesAreNull(connection.GetType(), connection, features); - TestCachedFeaturesAreNull(websockets.GetType(), websockets, features); + TestCachedFeaturesAreNull(context, features); + TestCachedFeaturesAreNull(context.Request, features); + TestCachedFeaturesAreNull(context.Response, features); + TestCachedFeaturesAreNull(context.Authentication, features); + TestCachedFeaturesAreNull(context.Connection, features); + TestCachedFeaturesAreNull(context.WebSockets, features); context.Session = new TestSession(); - features.Set(new HttpRequestFeature()); - features.Set(new HttpResponseFeature()); - features.Set(new TestHttpWebSocketFeature()); - TestCachedFeaturesAreSet(context.GetType(), context, features); - TestCachedFeaturesAreSet(request.GetType(), request, features); - TestCachedFeaturesAreSet(response.GetType(), response, features); - TestCachedFeaturesAreSet(authentication.GetType(), authentication, features); - TestCachedFeaturesAreSet(connection.GetType(), connection, features); - TestCachedFeaturesAreSet(websockets.GetType(), websockets, features); + TestCachedFeaturesAreSet(context, features); + TestCachedFeaturesAreSet(context.Request, features); + TestCachedFeaturesAreSet(context.Response, features); + TestCachedFeaturesAreSet(context.Authentication, features); + TestCachedFeaturesAreSet(context.Connection, features); + TestCachedFeaturesAreSet(context.WebSockets, features); - Assert.NotEqual(0, features.Count()); + Assert.NotEqual(3, features.Count()); + context.Uninitialize(); var newFeatures = new FeatureCollection(); - context.UpdateFeatures(newFeatures); + newFeatures.Set(new HttpRequestFeature()); + newFeatures.Set(new HttpResponseFeature()); + newFeatures.Set(new TestHttpWebSocketFeature()); + context.Initialize(newFeatures); - Assert.Equal(0, newFeatures.Count()); + Assert.Equal(3, newFeatures.Count()); - TestCachedFeaturesAreNull(context.GetType(), context, newFeatures); - TestCachedFeaturesAreNull(request.GetType(), request, newFeatures); - TestCachedFeaturesAreNull(response.GetType(), response, newFeatures); - TestCachedFeaturesAreNull(authentication.GetType(), authentication, newFeatures); - TestCachedFeaturesAreNull(connection.GetType(), connection, newFeatures); - TestCachedFeaturesAreNull(websockets.GetType(), websockets, newFeatures); + TestCachedFeaturesAreNull(context, newFeatures); + TestCachedFeaturesAreNull(context.Request, newFeatures); + TestCachedFeaturesAreNull(context.Response, newFeatures); + TestCachedFeaturesAreNull(context.Authentication, newFeatures); + TestCachedFeaturesAreNull(context.Connection, newFeatures); + TestCachedFeaturesAreNull(context.WebSockets, newFeatures); context.Session = new TestSession(); - newFeatures.Set(new HttpRequestFeature()); - newFeatures.Set(new HttpResponseFeature()); - newFeatures.Set(new TestHttpWebSocketFeature()); - TestCachedFeaturesAreSet(context.GetType(), context, newFeatures); - TestCachedFeaturesAreSet(request.GetType(), request, newFeatures); - TestCachedFeaturesAreSet(response.GetType(), response, newFeatures); - TestCachedFeaturesAreSet(authentication.GetType(), authentication, newFeatures); - TestCachedFeaturesAreSet(connection.GetType(), connection, newFeatures); - TestCachedFeaturesAreSet(websockets.GetType(), websockets, newFeatures); + TestCachedFeaturesAreSet(context, newFeatures); + TestCachedFeaturesAreSet(context.Request, newFeatures); + TestCachedFeaturesAreSet(context.Response, newFeatures); + TestCachedFeaturesAreSet(context.Authentication, newFeatures); + TestCachedFeaturesAreSet(context.Connection, newFeatures); + TestCachedFeaturesAreSet(context.WebSockets, newFeatures); - Assert.NotEqual(0, newFeatures.Count()); + Assert.NotEqual(3, newFeatures.Count()); } - void TestCachedFeaturesAreNull(Type type, object value, IFeatureCollection features) + void TestCachedFeaturesAreNull(object value, IFeatureCollection features) { + var type = value.GetType(); var fields = type .GetFields(BindingFlags.NonPublic | BindingFlags.Instance) @@ -234,8 +229,10 @@ void TestCachedFeaturesAreNull(Type type, object value, IFeatureCollection featu } } - void TestCachedFeaturesAreSet(Type type, object value, IFeatureCollection features) + void TestCachedFeaturesAreSet(object value, IFeatureCollection features) { + var type = value.GetType(); + var properties = type .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType.GetTypeInfo().IsInterface); @@ -274,6 +271,7 @@ private static void TestFeatureProperties(object value, IFeatureCollection featu { if (property.Name.Contains("Feature")) { + Console.WriteLine($"{value.GetType().Name} {property.Name}"); var v = property.GetValue(value); Assert.Same(features[property.PropertyType], v); Assert.NotNull(v); From c05c203c289ebc38dfbb7496199b6baad7d1bb75 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 16 Dec 2015 22:02:05 -0800 Subject: [PATCH 2/6] Adding example of what http context pooling might look like --- samples/SampleApp/PooledHttpContext.cs | 54 +++++++++++++++ samples/SampleApp/PooledHttpContextFactory.cs | 65 +++++++++++++++++++ .../DefaultHttpRequest.cs | 2 +- .../DefaultHttpResponse.cs | 2 +- 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 samples/SampleApp/PooledHttpContext.cs create mode 100644 samples/SampleApp/PooledHttpContextFactory.cs diff --git a/samples/SampleApp/PooledHttpContext.cs b/samples/SampleApp/PooledHttpContext.cs new file mode 100644 index 00000000..7736085e --- /dev/null +++ b/samples/SampleApp/PooledHttpContext.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Features; +using Microsoft.AspNet.Http.Internal; + +namespace SampleApp +{ + public class PooledHttpContext : DefaultHttpContext + { + DefaultHttpRequest _pooledHttpRequest; + DefaultHttpResponse _pooledHttpResponse; + + public PooledHttpContext(IFeatureCollection featureCollection) : + base(featureCollection) + { + } + + protected override HttpRequest InitializeHttpRequest() + { + if (_pooledHttpRequest != null) + { + _pooledHttpRequest.Initialize(this, Features); + return _pooledHttpRequest; + } + + return new DefaultHttpRequest(this, Features); + } + + protected override void UninitializeHttpRequest(HttpRequest instance) + { + _pooledHttpRequest = instance as DefaultHttpRequest; + _pooledHttpRequest?.Uninitialize(); + } + + protected override HttpResponse InitializeHttpResponse() + { + if (_pooledHttpResponse != null) + { + _pooledHttpResponse.Initialize(this, Features); + return _pooledHttpResponse; + } + + return new DefaultHttpResponse(this, Features); + } + + protected override void UninitializeHttpResponse(HttpResponse instance) + { + _pooledHttpResponse = instance as DefaultHttpResponse; + _pooledHttpResponse?.Uninitialize(); + } + } +} \ No newline at end of file diff --git a/samples/SampleApp/PooledHttpContextFactory.cs b/samples/SampleApp/PooledHttpContextFactory.cs new file mode 100644 index 00000000..62955d90 --- /dev/null +++ b/samples/SampleApp/PooledHttpContextFactory.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Features; + +namespace SampleApp +{ + public class PooledHttpContextFactory : IHttpContextFactory + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly Stack _pool = new Stack(); + + public PooledHttpContextFactory(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public HttpContext Create(IFeatureCollection featureCollection) + { + PooledHttpContext httpContext = null; + lock (_pool) + { + if (_pool.Count != 0) + { + httpContext = _pool.Pop(); + } + } + + if (httpContext == null) + { + httpContext = new PooledHttpContext(featureCollection); + } + else + { + httpContext.Initialize(featureCollection); + } + + if (_httpContextAccessor != null) + { + _httpContextAccessor.HttpContext = httpContext; + } + return httpContext; + } + + public void Dispose(HttpContext httpContext) + { + if (_httpContextAccessor != null) + { + _httpContextAccessor.HttpContext = null; + } + + var pooled = httpContext as PooledHttpContext; + if (pooled != null) + { + pooled.Uninitialize(); + lock (_pool) + { + _pool.Push(pooled); + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index e03f93c8..f8890e73 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -16,7 +16,7 @@ public class DefaultHttpRequest : HttpRequest private HttpContext _context; private FeatureReferences _features; - public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection features) + public DefaultHttpRequest(HttpContext context, IFeatureCollection features) { Initialize(context, features); } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index 901e17cb..a9cb49cb 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -15,7 +15,7 @@ public class DefaultHttpResponse : HttpResponse private HttpContext _context; private FeatureReferences _features; - public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection features) + public DefaultHttpResponse(HttpContext context, IFeatureCollection features) { Initialize(context, features); } From 4265855652a365f6e350f080a985cf3544441a55 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 16 Dec 2015 22:35:41 -0800 Subject: [PATCH 3/6] PR feedback Adding comment about reason for public field --- .../FeatureReferences.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs index 75fcacc0..30d7c363 100644 --- a/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs +++ b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs @@ -14,9 +14,14 @@ public FeatureReferences(IFeatureCollection collection) Revision = collection.Revision; } - public readonly IFeatureCollection Collection; - public int Revision; - public TCache Cache; + public IFeatureCollection Collection { get; private set; } + public int Revision { get; private set; } + + // cache is a public field because the code calling Fetch must + // be able to pass ref values that "dot through" the TCache struct memory, + // if it was a Property then that getter would return a copy of the memory + // preventing the use of "ref" + public TCache Cache; public TFeature Fetch( ref TFeature cached, From 290ff2a26829bf33750eb111e4558539de9077a8 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 16 Dec 2015 22:37:07 -0800 Subject: [PATCH 4/6] PR feedback --- src/Microsoft.AspNet.Http/DefaultHttpRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index f8890e73..5d3a2875 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -42,7 +42,7 @@ public virtual void Uninitialize() _features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f)); private IFormFeature FormFeature => - _features.Fetch(ref _features.Cache.Form, this, self => new FormFeature(self)); + _features.Fetch(ref _features.Cache.Form, this, f => new FormFeature(f)); private IRequestCookiesFeature RequestCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f)); From 35d71068d4e08edd5b13303f29ba6d98e62a9871 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Wed, 16 Dec 2015 22:46:20 -0800 Subject: [PATCH 5/6] PR feedback --- test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index a3239619..a529c7fb 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -271,7 +271,6 @@ private static void TestFeatureProperties(object value, IFeatureCollection featu { if (property.Name.Contains("Feature")) { - Console.WriteLine($"{value.GetType().Name} {property.Name}"); var v = property.GetValue(value); Assert.Same(features[property.PropertyType], v); Assert.NotNull(v); From c95d816c1dca5ad8e4db8da17fa0ea4ad148822c Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Thu, 17 Dec 2015 11:18:44 -0800 Subject: [PATCH 6/6] Updating unit test to probe _features cache field state --- .../DefaultHttpContextTests.cs | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index a529c7fb..74bda1a6 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -149,7 +149,7 @@ public void SetItems_NewCollectionUsed() items["foo"] = item; Assert.Same(item, context.Items["foo"]); } - + [Fact] public void UpdateFeatures_ClearsCachedFeatures() { @@ -158,75 +158,73 @@ public void UpdateFeatures_ClearsCachedFeatures() features.Set(new HttpResponseFeature()); features.Set(new TestHttpWebSocketFeature()); + // featurecollection is set. all cached interfaces are null. var context = new DefaultHttpContext(features); - + TestAllCachedFeaturesAreNull(context, features); Assert.Equal(3, features.Count()); - TestCachedFeaturesAreNull(context, features); - TestCachedFeaturesAreNull(context.Request, features); - TestCachedFeaturesAreNull(context.Response, features); - TestCachedFeaturesAreNull(context.Authentication, features); - TestCachedFeaturesAreNull(context.Connection, features); - TestCachedFeaturesAreNull(context.WebSockets, features); - - context.Session = new TestSession(); - - TestCachedFeaturesAreSet(context, features); - TestCachedFeaturesAreSet(context.Request, features); - TestCachedFeaturesAreSet(context.Response, features); - TestCachedFeaturesAreSet(context.Authentication, features); - TestCachedFeaturesAreSet(context.Connection, features); - TestCachedFeaturesAreSet(context.WebSockets, features); - + // getting feature properties populates feature collection with defaults + TestAllCachedFeaturesAreSet(context, features); Assert.NotEqual(3, features.Count()); + // featurecollection is null. and all cached interfaces are null. + // only top level is tested because child objects are inaccessible. context.Uninitialize(); + TestCachedFeaturesAreNull(context, null); + + var newFeatures = new FeatureCollection(); newFeatures.Set(new HttpRequestFeature()); newFeatures.Set(new HttpResponseFeature()); newFeatures.Set(new TestHttpWebSocketFeature()); - context.Initialize(newFeatures); + // featurecollection is set to newFeatures. all cached interfaces are null. + context.Initialize(newFeatures); + TestAllCachedFeaturesAreNull(context, newFeatures); Assert.Equal(3, newFeatures.Count()); - TestCachedFeaturesAreNull(context, newFeatures); - TestCachedFeaturesAreNull(context.Request, newFeatures); - TestCachedFeaturesAreNull(context.Response, newFeatures); - TestCachedFeaturesAreNull(context.Authentication, newFeatures); - TestCachedFeaturesAreNull(context.Connection, newFeatures); - TestCachedFeaturesAreNull(context.WebSockets, newFeatures); - - context.Session = new TestSession(); - - TestCachedFeaturesAreSet(context, newFeatures); - TestCachedFeaturesAreSet(context.Request, newFeatures); - TestCachedFeaturesAreSet(context.Response, newFeatures); - TestCachedFeaturesAreSet(context.Authentication, newFeatures); - TestCachedFeaturesAreSet(context.Connection, newFeatures); - TestCachedFeaturesAreSet(context.WebSockets, newFeatures); - + // getting feature properties populates new feature collection with defaults + TestAllCachedFeaturesAreSet(context, newFeatures); Assert.NotEqual(3, newFeatures.Count()); } + void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features) + { + TestCachedFeaturesAreNull(context, features); + TestCachedFeaturesAreNull(context.Request, features); + TestCachedFeaturesAreNull(context.Response, features); + TestCachedFeaturesAreNull(context.Authentication, features); + TestCachedFeaturesAreNull(context.Connection, features); + TestCachedFeaturesAreNull(context.WebSockets, features); + } + void TestCachedFeaturesAreNull(object value, IFeatureCollection features) { var type = value.GetType(); - var fields = type + var field = type .GetFields(BindingFlags.NonPublic | BindingFlags.Instance) - .Where(f => f.FieldType.GetTypeInfo().IsInterface); + .Single(f => + f.FieldType.GetTypeInfo().IsGenericType && + f.FieldType.GetGenericTypeDefinition() == typeof(FeatureReferences<>)); - foreach (var field in fields) - { - if (field.FieldType == typeof(IFeatureCollection)) - { - Assert.Same(features, field.GetValue(value)); - } - else - { - Assert.Null(field.GetValue(value)); - } - } + var boxedExpectedStruct = features == null ? + Activator.CreateInstance(field.FieldType) : + Activator.CreateInstance(field.FieldType, features); + + var boxedActualStruct = field.GetValue(value); + + Assert.Equal(boxedExpectedStruct, boxedActualStruct); + } + + void TestAllCachedFeaturesAreSet(HttpContext context, IFeatureCollection features) + { + TestCachedFeaturesAreSet(context, features); + TestCachedFeaturesAreSet(context.Request, features); + TestCachedFeaturesAreSet(context.Response, features); + TestCachedFeaturesAreSet(context.Authentication, features); + TestCachedFeaturesAreSet(context.Connection, features); + TestCachedFeaturesAreSet(context.WebSockets, features); } void TestCachedFeaturesAreSet(object value, IFeatureCollection features)