From f786fb7bd0b60c1551774235a4954830760e6c2e Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 12 Jan 2015 10:22:15 -0800 Subject: [PATCH] Add HttpContextAccessor --- .../HostingServices.cs | 2 +- .../HostingServicesCollectionExtensions.cs | 5 +- .../HttpContextAccessor.cs | 73 ++++++++++++++++++ .../IHttpContextAccessor.cs | 15 ++++ .../Startup/StartupLoader.cs | 3 - .../ContainerExtensions.cs | 4 - .../ContainerMiddleware.cs | 76 ++----------------- .../RequestServicesContainer.cs | 23 +++--- .../project.json | 6 +- .../UseRequestServicesFacts.cs | 1 + 10 files changed, 112 insertions(+), 96 deletions(-) create mode 100644 src/Microsoft.AspNet.Hosting/HttpContextAccessor.cs create mode 100644 src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs diff --git a/src/Microsoft.AspNet.Hosting/HostingServices.cs b/src/Microsoft.AspNet.Hosting/HostingServices.cs index 3ff28e403f..b717bb7050 100644 --- a/src/Microsoft.AspNet.Hosting/HostingServices.cs +++ b/src/Microsoft.AspNet.Hosting/HostingServices.cs @@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Hosting public HostingManifest(IServiceProvider fallback) { var manifest = fallback.GetRequiredService(); - Services = new Type[] { typeof(ITypeActivator), typeof(IHostingEnvironment), typeof(ILoggerFactory) } + Services = new Type[] { typeof(ITypeActivator), typeof(IHostingEnvironment), typeof(ILoggerFactory), typeof(IHttpContextAccessor) } .Concat(manifest.Services).Distinct(); } diff --git a/src/Microsoft.AspNet.Hosting/HostingServicesCollectionExtensions.cs b/src/Microsoft.AspNet.Hosting/HostingServicesCollectionExtensions.cs index ac661999b2..5122ca73da 100644 --- a/src/Microsoft.AspNet.Hosting/HostingServicesCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Hosting/HostingServicesCollectionExtensions.cs @@ -38,11 +38,8 @@ namespace Microsoft.Framework.DependencyInjection services.AddTypeActivator(configuration); // TODO: Do we expect this to be provide by the runtime eventually? services.AddLogging(configuration); - // REVIEW: okay to use existing hosting environment/httpcontext if specified? services.TryAdd(describer.Singleton()); - - // TODO: Remove this once we have IHttpContextAccessor - services.AddContextAccessor(configuration); + services.TryAdd(describer.Scoped()); // REVIEW: don't try add because we pull out IEnumerable? services.AddInstance(new ConfigureHostingEnvironment(configuration)); diff --git a/src/Microsoft.AspNet.Hosting/HttpContextAccessor.cs b/src/Microsoft.AspNet.Hosting/HttpContextAccessor.cs new file mode 100644 index 0000000000..c811d4bfef --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/HttpContextAccessor.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +#if ASPNET50 +using System.Runtime.Remoting.Messaging; +using System.Runtime.Remoting; +#elif ASPNETCORE50 +using System.Threading; +#endif +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Hosting +{ + public class HttpContextAccessor : IHttpContextAccessor + { + private HttpContext _value; + + public bool IsRootContext { get; set; } + + public HttpContext Value + { + get + { + return IsRootContext ? AccessRootHttpContext() : _value; + } + } + + public HttpContext SetValue(HttpContext value) + { + if (IsRootContext) + { + return ExchangeRootHttpContext(value); + } + var prior = _value; + _value = value; + return prior; + } + +#if ASPNET50 + private const string LogicalDataKey = "__HttpContext_Current__"; +#elif ASPNETCORE50 + private static AsyncLocal _httpContextCurrent = new AsyncLocal(); +#endif + + private static HttpContext AccessRootHttpContext() + { +#if ASPNET50 + var handle = CallContext.LogicalGetData(LogicalDataKey) as ObjectHandle; + return handle != null ? handle.Unwrap() as HttpContext : null; +#elif ASPNETCORE50 + return _httpContextCurrent.Value; +#else + throw new Exception("TODO: CallContext not available"); +#endif + } + + private static HttpContext ExchangeRootHttpContext(HttpContext httpContext) + { +#if ASPNET50 + var prior = CallContext.LogicalGetData(LogicalDataKey) as ObjectHandle; + CallContext.LogicalSetData(LogicalDataKey, new ObjectHandle(httpContext)); + return prior != null ? prior.Unwrap() as HttpContext : null; +#elif ASPNETCORE50 + var prior = _httpContextCurrent.Value; + _httpContextCurrent.Value = httpContext; + return prior; +#else + return null; +#endif + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs b/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs new file mode 100644 index 0000000000..01656f4cd7 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; + +namespace Microsoft.AspNet.Hosting +{ + public interface IHttpContextAccessor + { + bool IsRootContext { get; set; } + HttpContext Value { get; } + + HttpContext SetValue(HttpContext value); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs b/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs index 3b300a1eca..a7e85283bf 100644 --- a/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs +++ b/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs @@ -9,7 +9,6 @@ using System.Reflection; using Microsoft.AspNet.Builder; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Hosting.Startup { @@ -153,8 +152,6 @@ namespace Microsoft.AspNet.Hosting.Startup if (servicesMethod != null) { var services = HostingServices.Create(builder.ApplicationServices); - // TODO: remove this once IHttpContextAccessor service is added - services.AddContextAccessor(); if (servicesMethod.ReturnType == typeof(IServiceProvider)) { // IServiceProvider ConfigureServices(IServiceCollection) diff --git a/src/Microsoft.AspNet.RequestContainer/ContainerExtensions.cs b/src/Microsoft.AspNet.RequestContainer/ContainerExtensions.cs index 9813e9164f..06d89995f2 100644 --- a/src/Microsoft.AspNet.RequestContainer/ContainerExtensions.cs +++ b/src/Microsoft.AspNet.RequestContainer/ContainerExtensions.cs @@ -48,10 +48,6 @@ namespace Microsoft.AspNet.Builder // Import services from hosting/KRE as fallback var serviceCollection = HostingServices.Create(builder.ApplicationServices); - // TODO: remove this once IHttpContextAccessor service is added - serviceCollection.AddContextAccessor(); - - // REVIEW: serviceCollection has the merged services, manifests are lost after this builder.ApplicationServices = configureServices(serviceCollection); return builder.UseMiddleware(); diff --git a/src/Microsoft.AspNet.RequestContainer/ContainerMiddleware.cs b/src/Microsoft.AspNet.RequestContainer/ContainerMiddleware.cs index 4145525ab8..097acf6d1b 100644 --- a/src/Microsoft.AspNet.RequestContainer/ContainerMiddleware.cs +++ b/src/Microsoft.AspNet.RequestContainer/ContainerMiddleware.cs @@ -2,72 +2,21 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -#if ASPNET50 -using System.Runtime.Remoting.Messaging; -#endif using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; -using Microsoft.Framework.DependencyInjection; -#if ASPNET50 -using System.Runtime.Remoting; -#endif + namespace Microsoft.AspNet.RequestContainer { public class ContainerMiddleware { - private const string LogicalDataKey = "__HttpContext_Current__"; private readonly RequestDelegate _next; - private readonly IServiceProvider _rootServiceProvider; - private readonly IContextAccessor _rootHttpContextAccessor; - private readonly IServiceScopeFactory _rootServiceScopeFactory; + private readonly IServiceProvider _services; - public ContainerMiddleware( - RequestDelegate next, - IServiceProvider rootServiceProvider, - IContextAccessor rootHttpContextAccessor, - IServiceScopeFactory rootServiceScopeFactory) + public ContainerMiddleware(RequestDelegate next, IServiceProvider services) { - if (rootServiceProvider == null) - { - throw new ArgumentNullException("rootServiceProvider"); - } - if (rootHttpContextAccessor == null) - { - throw new ArgumentNullException("rootHttpContextAccessor"); - } - if (rootServiceScopeFactory == null) - { - throw new ArgumentNullException("rootServiceScopeFactory"); - } - + _services = services; _next = next; - _rootServiceProvider = rootServiceProvider; - _rootServiceScopeFactory = rootServiceScopeFactory; - _rootHttpContextAccessor = rootHttpContextAccessor; - - _rootHttpContextAccessor.SetContextSource(AccessRootHttpContext, ExchangeRootHttpContext); - } - - internal static HttpContext AccessRootHttpContext() - { -#if ASPNET50 - var handle = CallContext.LogicalGetData(LogicalDataKey) as ObjectHandle; - return handle != null ? handle.Unwrap() as HttpContext : null; -#else - throw new Exception("TODO: CallContext not available"); -#endif - } - - internal static HttpContext ExchangeRootHttpContext(HttpContext httpContext) - { -#if ASPNET50 - var prior = CallContext.LogicalGetData(LogicalDataKey) as ObjectHandle; - CallContext.LogicalSetData(LogicalDataKey, new ObjectHandle(httpContext)); - return prior != null ? prior.Unwrap() as HttpContext : null; -#else - return null; -#endif } public async Task Invoke(HttpContext httpContext) @@ -77,22 +26,7 @@ namespace Microsoft.AspNet.RequestContainer throw new Exception("TODO: nested request container scope? this is probably a mistake on your part?"); } - var priorApplicationServices = httpContext.ApplicationServices; - var priorRequestServices = httpContext.RequestServices; - - var appServiceProvider = _rootServiceProvider; - var appServiceScopeFactory = _rootServiceScopeFactory; - var appHttpContextAccessor = _rootHttpContextAccessor; - - if (priorApplicationServices != null && - priorApplicationServices != appServiceProvider) - { - appServiceProvider = priorApplicationServices; - appServiceScopeFactory = priorApplicationServices.GetRequiredService(); - appHttpContextAccessor = priorApplicationServices.GetRequiredService>(); - } - - using (var container = new RequestServicesContainer(httpContext, appServiceScopeFactory, appHttpContextAccessor, appServiceProvider)) + using (var container = RequestServicesContainer.EnsureRequestServices(httpContext, _services)) { await _next.Invoke(httpContext); } diff --git a/src/Microsoft.AspNet.RequestContainer/RequestServicesContainer.cs b/src/Microsoft.AspNet.RequestContainer/RequestServicesContainer.cs index a7c646321e..8b3d32316d 100644 --- a/src/Microsoft.AspNet.RequestContainer/RequestServicesContainer.cs +++ b/src/Microsoft.AspNet.RequestContainer/RequestServicesContainer.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Framework.DependencyInjection; @@ -12,7 +13,7 @@ namespace Microsoft.AspNet.RequestContainer public RequestServicesContainer( HttpContext context, IServiceScopeFactory scopeFactory, - IContextAccessor appContextAccessor, + IHttpContextAccessor appContextAccessor, IServiceProvider appServiceProvider) { if (scopeFactory == null) @@ -36,7 +37,7 @@ namespace Microsoft.AspNet.RequestContainer // Begin the scope Scope = scopeFactory.CreateScope(); - ScopeContextAccessor = Scope.ServiceProvider.GetRequiredService>(); + ScopeContextAccessor = Scope.ServiceProvider.GetRequiredService(); Context.ApplicationServices = appServiceProvider; Context.RequestServices = Scope.ServiceProvider; @@ -51,8 +52,9 @@ namespace Microsoft.AspNet.RequestContainer private HttpContext PriorAppHttpContext { get; set; } private HttpContext PriorScopeHttpContext { get; set; } private IServiceScope Scope { get; set; } - private IContextAccessor ScopeContextAccessor { get; set; } - private IContextAccessor AppContextAccessor { get; set; } + private IHttpContextAccessor ScopeContextAccessor { get; set; } + private IHttpContextAccessor AppContextAccessor { get; set; } + // CONSIDER: this could be an extension method on HttpContext instead public static RequestServicesContainer EnsureRequestServices(HttpContext httpContext, IServiceProvider services) @@ -64,7 +66,6 @@ namespace Microsoft.AspNet.RequestContainer } var serviceProvider = httpContext.ApplicationServices ?? services; - if (serviceProvider == null) { throw new InvalidOperationException("TODO: services and httpContext.ApplicationServices are both null!"); @@ -72,10 +73,10 @@ namespace Microsoft.AspNet.RequestContainer // Matches constructor of RequestContainer var rootServiceProvider = serviceProvider.GetRequiredService(); - var rootHttpContextAccessor = serviceProvider.GetRequiredService>(); + var rootHttpContextAccessor = serviceProvider.GetRequiredService(); var rootServiceScopeFactory = serviceProvider.GetRequiredService(); - rootHttpContextAccessor.SetContextSource(ContainerMiddleware.AccessRootHttpContext, ContainerMiddleware.ExchangeRootHttpContext); + rootHttpContextAccessor.IsRootContext = true; // Pre Scope setup var priorApplicationServices = serviceProvider; @@ -90,14 +91,14 @@ namespace Microsoft.AspNet.RequestContainer { appServiceProvider = priorApplicationServices; appServiceScopeFactory = priorApplicationServices.GetRequiredService(); - appHttpContextAccessor = priorApplicationServices.GetRequiredService>(); + appHttpContextAccessor = priorApplicationServices.GetRequiredService(); } // Creates the scope and does the service swaps return new RequestServicesContainer(httpContext, appServiceScopeFactory, appHttpContextAccessor, appServiceProvider); } - #region IDisposable Support +#region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) @@ -137,8 +138,6 @@ namespace Microsoft.AspNet.RequestContainer // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); } - #endregion - +#endregion } - } \ No newline at end of file diff --git a/src/Microsoft.AspNet.RequestContainer/project.json b/src/Microsoft.AspNet.RequestContainer/project.json index d8f3cef5a9..076064ad98 100644 --- a/src/Microsoft.AspNet.RequestContainer/project.json +++ b/src/Microsoft.AspNet.RequestContainer/project.json @@ -8,6 +8,10 @@ }, "frameworks": { "aspnet50": {}, - "aspnetcore50": {} + "aspnetcore50": { + "dependencies": { + "System.Threading": "4.0.10-beta-*" + } + }, } } diff --git a/test/Microsoft.AspNet.Hosting.Tests/UseRequestServicesFacts.cs b/test/Microsoft.AspNet.Hosting.Tests/UseRequestServicesFacts.cs index 7b01e54c6f..0e00d492ef 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/UseRequestServicesFacts.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/UseRequestServicesFacts.cs @@ -92,6 +92,7 @@ namespace Microsoft.AspNet.Hosting.Tests [InlineData(typeof(ITypeActivator))] [InlineData(typeof(IApplicationLifetime))] [InlineData(typeof(ILoggerFactory))] + [InlineData(typeof(IHttpContextAccessor))] public void UseRequestServicesHostingImportedServicesAreDefined(Type service) { var baseServiceProvider = HostingServices.Create().BuildServiceProvider();