Move request servies feature into DefaultHttpContext (#6541)
- This completely removes the per request allocation until the feature is used. - In order to make this change viable, we need to introduce a new HttpContextFactory that can accept new services without adding 2^n constructors. As a result, this change introduces a DefaultHttpContextFactory that takes an IServiceProvider and resolves dependencies based on the needs of the DefaultHttpContext and features. - Throw in the older HttpContextFactory constructor when the IServiceScopeFactory is null - It also saves us from revving the feature collection version unnecessarily.
This commit is contained in:
parent
13841abd78
commit
c458fe6ebe
|
|
@ -89,13 +89,10 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
services.TryAddSingleton<DiagnosticListener>(listener);
|
services.TryAddSingleton<DiagnosticListener>(listener);
|
||||||
services.TryAddSingleton<DiagnosticSource>(listener);
|
services.TryAddSingleton<DiagnosticSource>(listener);
|
||||||
|
|
||||||
services.TryAddSingleton<IHttpContextFactory, HttpContextFactory>();
|
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
|
||||||
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
||||||
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
||||||
|
|
||||||
// Conjure up a RequestServices
|
|
||||||
services.TryAddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
|
|
||||||
|
|
||||||
// Support UseStartup(assemblyName)
|
// Support UseStartup(assemblyName)
|
||||||
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
|
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,20 +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;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting.Internal
|
|
||||||
{
|
|
||||||
public class AutoRequestServicesStartupFilter : IStartupFilter
|
|
||||||
{
|
|
||||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
|
||||||
{
|
|
||||||
return builder =>
|
|
||||||
{
|
|
||||||
builder.UseMiddleware<RequestServicesContainerMiddleware>();
|
|
||||||
next(builder);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +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;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting.Internal
|
|
||||||
{
|
|
||||||
public class RequestServicesContainerMiddleware
|
|
||||||
{
|
|
||||||
private readonly RequestDelegate _next;
|
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
|
||||||
|
|
||||||
public RequestServicesContainerMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory)
|
|
||||||
{
|
|
||||||
if (next == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(next));
|
|
||||||
}
|
|
||||||
if (scopeFactory == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(scopeFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
_next = next;
|
|
||||||
_scopeFactory = scopeFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Invoke(HttpContext httpContext)
|
|
||||||
{
|
|
||||||
Debug.Assert(httpContext != null);
|
|
||||||
|
|
||||||
var features = httpContext.Features;
|
|
||||||
var servicesFeature = features.Get<IServiceProvidersFeature>();
|
|
||||||
|
|
||||||
// All done if RequestServices is set
|
|
||||||
if (servicesFeature?.RequestServices != null)
|
|
||||||
{
|
|
||||||
return _next.Invoke(httpContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
features.Set<IServiceProvidersFeature>(new RequestServicesFeature(httpContext, _scopeFactory));
|
|
||||||
return _next.Invoke(httpContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -272,13 +272,11 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
services.AddSingleton<DiagnosticSource>(listener);
|
services.AddSingleton<DiagnosticSource>(listener);
|
||||||
|
|
||||||
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
||||||
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
|
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
|
||||||
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
|
||||||
// Conjure up a RequestServices
|
|
||||||
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
|
|
||||||
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
|
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_options.StartupAssembly))
|
if (!string.IsNullOrEmpty(_options.StartupAssembly))
|
||||||
|
|
|
||||||
|
|
@ -1,122 +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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Builder.Internal;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Internal;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting.Tests
|
|
||||||
{
|
|
||||||
public class RequestServicesContainerMiddlewareTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestServicesAreSet()
|
|
||||||
{
|
|
||||||
var serviceProvider = new ServiceCollection()
|
|
||||||
.BuildServiceProvider();
|
|
||||||
|
|
||||||
var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
|
|
||||||
var middleware = new RequestServicesContainerMiddleware(
|
|
||||||
ctx => Task.CompletedTask,
|
|
||||||
scopeFactory);
|
|
||||||
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
await middleware.Invoke(context);
|
|
||||||
|
|
||||||
Assert.NotNull(context.RequestServices);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestServicesAreNotOverwrittenIfAlreadySet()
|
|
||||||
{
|
|
||||||
var serviceProvider = new ServiceCollection()
|
|
||||||
.BuildServiceProvider();
|
|
||||||
|
|
||||||
var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
|
|
||||||
var middleware = new RequestServicesContainerMiddleware(
|
|
||||||
ctx => Task.CompletedTask,
|
|
||||||
scopeFactory);
|
|
||||||
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
context.RequestServices = serviceProvider;
|
|
||||||
await middleware.Invoke(context);
|
|
||||||
|
|
||||||
Assert.Same(serviceProvider, context.RequestServices);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestServicesAreDisposedOnCompleted()
|
|
||||||
{
|
|
||||||
var serviceProvider = new ServiceCollection()
|
|
||||||
.AddTransient<DisposableThing>()
|
|
||||||
.BuildServiceProvider();
|
|
||||||
|
|
||||||
var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
|
||||||
DisposableThing instance = null;
|
|
||||||
|
|
||||||
var middleware = new RequestServicesContainerMiddleware(
|
|
||||||
ctx =>
|
|
||||||
{
|
|
||||||
instance = ctx.RequestServices.GetRequiredService<DisposableThing>();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
},
|
|
||||||
scopeFactory);
|
|
||||||
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
var responseFeature = new TestHttpResponseFeature();
|
|
||||||
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
|
||||||
|
|
||||||
await middleware.Invoke(context);
|
|
||||||
|
|
||||||
Assert.NotNull(context.RequestServices);
|
|
||||||
Assert.Single(responseFeature.CompletedCallbacks);
|
|
||||||
|
|
||||||
var callback = responseFeature.CompletedCallbacks[0];
|
|
||||||
await callback.callback(callback.state);
|
|
||||||
|
|
||||||
Assert.Null(context.RequestServices);
|
|
||||||
Assert.True(instance.Disposed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DisposableThing : IDisposable
|
|
||||||
{
|
|
||||||
public bool Disposed { get; set; }
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestHttpResponseFeature : IHttpResponseFeature
|
|
||||||
{
|
|
||||||
public List<(Func<object, Task> callback, object state)> CompletedCallbacks = new List<(Func<object, Task> callback, object state)>();
|
|
||||||
|
|
||||||
public int StatusCode { get; set; }
|
|
||||||
public string ReasonPhrase { get; set; }
|
|
||||||
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
|
|
||||||
public Stream Body { get; set; }
|
|
||||||
|
|
||||||
public bool HasStarted => false;
|
|
||||||
|
|
||||||
public void OnCompleted(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
CompletedCallbacks.Add((callback, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnStarting(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +16,7 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||||
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
|
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
|
||||||
private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
|
private readonly static Func<DefaultHttpContext, IServiceProvidersFeature> _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory);
|
||||||
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
|
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
|
||||||
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
|
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
|
||||||
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
|
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
|
||||||
|
|
@ -64,11 +65,13 @@ namespace Microsoft.AspNetCore.Http
|
||||||
|
|
||||||
public FormOptions FormOptions { get; set; }
|
public FormOptions FormOptions { get; set; }
|
||||||
|
|
||||||
|
public IServiceScopeFactory ServiceScopeFactory { get; set; }
|
||||||
|
|
||||||
private IItemsFeature ItemsFeature =>
|
private IItemsFeature ItemsFeature =>
|
||||||
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
|
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
|
||||||
|
|
||||||
private IServiceProvidersFeature ServiceProvidersFeature =>
|
private IServiceProvidersFeature ServiceProvidersFeature =>
|
||||||
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
|
_features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature);
|
||||||
|
|
||||||
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
|
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
|
||||||
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
|
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http
|
||||||
|
{
|
||||||
|
public class DefaultHttpContextFactory : IHttpContextFactory
|
||||||
|
{
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly FormOptions _formOptions;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
|
||||||
|
// This takes the IServiceProvider because it needs to support an ever expanding
|
||||||
|
// set of services that flow down into HttpContext features
|
||||||
|
public DefaultHttpContextFactory(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
// May be null
|
||||||
|
_httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
|
||||||
|
_formOptions = serviceProvider.GetRequiredService<IOptions<FormOptions>>().Value;
|
||||||
|
_serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpContext Create(IFeatureCollection featureCollection)
|
||||||
|
{
|
||||||
|
if (featureCollection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(featureCollection));
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(featureCollection);
|
||||||
|
if (_httpContextAccessor != null)
|
||||||
|
{
|
||||||
|
_httpContextAccessor.HttpContext = httpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpContext.FormOptions = _formOptions;
|
||||||
|
httpContext.ServiceScopeFactory = _serviceScopeFactory;
|
||||||
|
|
||||||
|
return httpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultHttpContext CreateHttpContext(IFeatureCollection featureCollection)
|
||||||
|
{
|
||||||
|
if (featureCollection is IHttpContextContainer container)
|
||||||
|
{
|
||||||
|
return container.HttpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultHttpContext(featureCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (_httpContextAccessor != null)
|
||||||
|
{
|
||||||
|
_httpContextAccessor.HttpContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting.Internal
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
public class RequestServicesFeature : IServiceProvidersFeature, IDisposable
|
public class RequestServicesFeature : IServiceProvidersFeature, IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +16,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
|
|
||||||
public RequestServicesFeature(HttpContext context, IServiceScopeFactory scopeFactory)
|
public RequestServicesFeature(HttpContext context, IServiceScopeFactory scopeFactory)
|
||||||
{
|
{
|
||||||
Debug.Assert(scopeFactory != null);
|
|
||||||
_context = context;
|
_context = context;
|
||||||
_scopeFactory = scopeFactory;
|
_scopeFactory = scopeFactory;
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!_requestServicesSet)
|
if (!_requestServicesSet && _scopeFactory != null)
|
||||||
{
|
{
|
||||||
_context.Response.RegisterForDispose(this);
|
_context.Response.RegisterForDispose(this);
|
||||||
_scope = _scopeFactory.CreateScope();
|
_scope = _scopeFactory.CreateScope();
|
||||||
|
|
@ -52,4 +48,4 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
_requestServices = null;
|
_requestServices = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,29 +3,47 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
|
[Obsolete("This is obsolete and will be removed in a future version. Use DefaultHttpContextFactory instead.")]
|
||||||
public class HttpContextFactory : IHttpContextFactory
|
public class HttpContextFactory : IHttpContextFactory
|
||||||
{
|
{
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly FormOptions _formOptions;
|
private readonly FormOptions _formOptions;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
|
||||||
public HttpContextFactory(IOptions<FormOptions> formOptions)
|
public HttpContextFactory(IOptions<FormOptions> formOptions)
|
||||||
: this(formOptions, httpContextAccessor: null)
|
: this(formOptions, serviceScopeFactory: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpContextFactory(IOptions<FormOptions> formOptions, IServiceScopeFactory serviceScopeFactory)
|
||||||
|
: this(formOptions, serviceScopeFactory, httpContextAccessor: null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpContextFactory(IOptions<FormOptions> formOptions, IHttpContextAccessor httpContextAccessor)
|
public HttpContextFactory(IOptions<FormOptions> formOptions, IHttpContextAccessor httpContextAccessor)
|
||||||
|
: this(formOptions, serviceScopeFactory: null, httpContextAccessor: httpContextAccessor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpContextFactory(IOptions<FormOptions> formOptions, IServiceScopeFactory serviceScopeFactory, IHttpContextAccessor httpContextAccessor)
|
||||||
{
|
{
|
||||||
if (formOptions == null)
|
if (formOptions == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(formOptions));
|
throw new ArgumentNullException(nameof(formOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serviceScopeFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||||
|
}
|
||||||
|
|
||||||
_formOptions = formOptions.Value;
|
_formOptions = formOptions.Value;
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +61,7 @@ namespace Microsoft.AspNetCore.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
httpContext.FormOptions = _formOptions;
|
httpContext.FormOptions = _formOptions;
|
||||||
|
httpContext.ServiceScopeFactory = _serviceScopeFactory;
|
||||||
|
|
||||||
return httpContext;
|
return httpContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
// 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;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http
|
||||||
|
{
|
||||||
|
public class DefaultHttpContextFactoryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateHttpContextSetsHttpContextAccessor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.AddHttpContextAccessor()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var accessor = services.GetRequiredService<IHttpContextAccessor>();
|
||||||
|
var contextFactory = new DefaultHttpContextFactory(services);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(context, accessor.HttpContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposeHttpContextSetsHttpContextAccessorToNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.AddHttpContextAccessor()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var accessor = services.GetRequiredService<IHttpContextAccessor>();
|
||||||
|
var contextFactory = new DefaultHttpContextFactory(services);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(context, accessor.HttpContext);
|
||||||
|
|
||||||
|
contextFactory.Dispose(context);
|
||||||
|
|
||||||
|
Assert.Null(accessor.HttpContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllowsCreatingContextWithoutSettingAccessor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var contextFactory = new DefaultHttpContextFactory(services);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
|
contextFactory.Dispose(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetsDefaultPropertiesOnHttpContext()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
var contextFactory = new DefaultHttpContextFactory(services);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var context = contextFactory.Create(new FeatureCollection()) as DefaultHttpContext;
|
||||||
|
Assert.NotNull(context);
|
||||||
|
Assert.NotNull(context.FormOptions);
|
||||||
|
Assert.NotNull(context.ServiceScopeFactory);
|
||||||
|
|
||||||
|
Assert.Same(services.GetRequiredService<IServiceScopeFactory>(), context.ServiceScopeFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,19 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
public class DefaultHttpContextTests
|
public class DefaultHttpContextTests
|
||||||
{
|
{
|
||||||
|
|
@ -188,6 +190,48 @@ namespace System.IO.Pipelines.Tests
|
||||||
Assert.NotEqual(3, newFeatures.Count());
|
Assert.NotEqual(3, newFeatures.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RequestServicesAreNotOverwrittenIfAlreadySet()
|
||||||
|
{
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
context.ServiceScopeFactory = scopeFactory;
|
||||||
|
context.RequestServices = serviceProvider;
|
||||||
|
|
||||||
|
Assert.Same(serviceProvider, context.RequestServices);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequestServicesAreDisposedOnCompleted()
|
||||||
|
{
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.AddTransient<DisposableThing>()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
DisposableThing instance = null;
|
||||||
|
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
context.ServiceScopeFactory = scopeFactory;
|
||||||
|
var responseFeature = new TestHttpResponseFeature();
|
||||||
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
||||||
|
|
||||||
|
Assert.NotNull(context.RequestServices);
|
||||||
|
Assert.Single(responseFeature.CompletedCallbacks);
|
||||||
|
|
||||||
|
instance = context.RequestServices.GetRequiredService<DisposableThing>();
|
||||||
|
|
||||||
|
var callback = responseFeature.CompletedCallbacks[0];
|
||||||
|
await callback.callback(callback.state);
|
||||||
|
|
||||||
|
Assert.Null(context.RequestServices);
|
||||||
|
Assert.True(instance.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features)
|
void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features)
|
||||||
{
|
{
|
||||||
TestCachedFeaturesAreNull(context, features);
|
TestCachedFeaturesAreNull(context, features);
|
||||||
|
|
@ -237,7 +281,7 @@ namespace System.IO.Pipelines.Tests
|
||||||
|
|
||||||
var fields = type
|
var fields = type
|
||||||
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
|
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
.Where(f => f.FieldType.GetTypeInfo().IsInterface);
|
.Where(f => f.FieldType.GetTypeInfo().IsInterface && f.GetCustomAttribute<CompilerGeneratedAttribute>() == null);
|
||||||
|
|
||||||
foreach (var field in fields)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
|
|
@ -281,6 +325,36 @@ namespace System.IO.Pipelines.Tests
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DisposableThing : IDisposable
|
||||||
|
{
|
||||||
|
public bool Disposed { get; set; }
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHttpResponseFeature : IHttpResponseFeature
|
||||||
|
{
|
||||||
|
public List<(Func<object, Task> callback, object state)> CompletedCallbacks = new List<(Func<object, Task> callback, object state)>();
|
||||||
|
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
public string ReasonPhrase { get; set; }
|
||||||
|
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
|
||||||
|
public Stream Body { get; set; }
|
||||||
|
|
||||||
|
public bool HasStarted => false;
|
||||||
|
|
||||||
|
public void OnCompleted(Func<object, Task> callback, object state)
|
||||||
|
{
|
||||||
|
CompletedCallbacks.Add((callback, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStarting(Func<object, Task> callback, object state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TestSession : ISession
|
private class TestSession : ISession
|
||||||
{
|
{
|
||||||
private Dictionary<string, byte[]> _store
|
private Dictionary<string, byte[]> _store
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -11,12 +13,24 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
public class HttpContextFactoryTests
|
public class HttpContextFactoryTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ConstructorWithoutServiceScopeFactoryThrows()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var accessor = new HttpContextAccessor();
|
||||||
|
var exception1 = Assert.Throws<ArgumentNullException>(() => new HttpContextFactory(Options.Create(new FormOptions()), accessor));
|
||||||
|
var exception2 = Assert.Throws<ArgumentNullException>(() => new HttpContextFactory(Options.Create(new FormOptions())));
|
||||||
|
|
||||||
|
Assert.Equal("serviceScopeFactory", exception1.ParamName);
|
||||||
|
Assert.Equal("serviceScopeFactory", exception2.ParamName);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateHttpContextSetsHttpContextAccessor()
|
public void CreateHttpContextSetsHttpContextAccessor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var accessor = new HttpContextAccessor();
|
var accessor = new HttpContextAccessor();
|
||||||
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), accessor);
|
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), new MyServiceScopeFactory(), accessor);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var context = contextFactory.Create(new FeatureCollection());
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
|
|
@ -30,7 +44,7 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var accessor = new HttpContextAccessor();
|
var accessor = new HttpContextAccessor();
|
||||||
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), accessor);
|
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), new MyServiceScopeFactory(), accessor);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var context = contextFactory.Create(new FeatureCollection());
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
|
|
@ -47,11 +61,17 @@ namespace Microsoft.AspNetCore.Http
|
||||||
public void AllowsCreatingContextWithoutSettingAccessor()
|
public void AllowsCreatingContextWithoutSettingAccessor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()));
|
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), new MyServiceScopeFactory());
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var context = contextFactory.Create(new FeatureCollection());
|
var context = contextFactory.Create(new FeatureCollection());
|
||||||
contextFactory.Dispose(context);
|
contextFactory.Dispose(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MyServiceScopeFactory : IServiceScopeFactory
|
||||||
|
{
|
||||||
|
public IServiceScope CreateScope() => null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue