Add HttpContextAccessor

This commit is contained in:
Hao Kung 2015-01-12 10:22:15 -08:00
parent d9237b3a08
commit f786fb7bd0
10 changed files with 112 additions and 96 deletions

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Hosting
public HostingManifest(IServiceProvider fallback)
{
var manifest = fallback.GetRequiredService<IServiceManifest>();
Services = new Type[] { typeof(ITypeActivator), typeof(IHostingEnvironment), typeof(ILoggerFactory) }
Services = new Type[] { typeof(ITypeActivator), typeof(IHostingEnvironment), typeof(ILoggerFactory), typeof(IHttpContextAccessor) }
.Concat(manifest.Services).Distinct();
}

View File

@ -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<IHostingEnvironment, HostingEnvironment>());
// TODO: Remove this once we have IHttpContextAccessor
services.AddContextAccessor(configuration);
services.TryAdd(describer.Scoped<IHttpContextAccessor, HttpContextAccessor>());
// REVIEW: don't try add because we pull out IEnumerable<IConfigureHostingEnvironment>?
services.AddInstance<IConfigureHostingEnvironment>(new ConfigureHostingEnvironment(configuration));

View File

@ -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<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();
#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
}
}
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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<ContainerMiddleware>();

View File

@ -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<HttpContext> _rootHttpContextAccessor;
private readonly IServiceScopeFactory _rootServiceScopeFactory;
private readonly IServiceProvider _services;
public ContainerMiddleware(
RequestDelegate next,
IServiceProvider rootServiceProvider,
IContextAccessor<HttpContext> 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<IServiceScopeFactory>();
appHttpContextAccessor = priorApplicationServices.GetRequiredService<IContextAccessor<HttpContext>>();
}
using (var container = new RequestServicesContainer(httpContext, appServiceScopeFactory, appHttpContextAccessor, appServiceProvider))
using (var container = RequestServicesContainer.EnsureRequestServices(httpContext, _services))
{
await _next.Invoke(httpContext);
}

View File

@ -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<HttpContext> 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<IContextAccessor<HttpContext>>();
ScopeContextAccessor = Scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
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<HttpContext> ScopeContextAccessor { get; set; }
private IContextAccessor<HttpContext> 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<IServiceProvider>();
var rootHttpContextAccessor = serviceProvider.GetRequiredService<IContextAccessor<HttpContext>>();
var rootHttpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
var rootServiceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
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<IServiceScopeFactory>();
appHttpContextAccessor = priorApplicationServices.GetRequiredService<IContextAccessor<HttpContext>>();
appHttpContextAccessor = priorApplicationServices.GetRequiredService<IHttpContextAccessor>();
}
// 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
}
}

View File

@ -8,6 +8,10 @@
},
"frameworks": {
"aspnet50": {},
"aspnetcore50": {}
"aspnetcore50": {
"dependencies": {
"System.Threading": "4.0.10-beta-*"
}
},
}
}

View File

@ -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();