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<DiagnosticSource>(listener);
|
||||
|
||||
services.TryAddSingleton<IHttpContextFactory, HttpContextFactory>();
|
||||
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
|
||||
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
||||
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
||||
|
||||
// Conjure up a RequestServices
|
||||
services.TryAddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
|
||||
|
||||
// Support UseStartup(assemblyName)
|
||||
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.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
|
||||
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
|
||||
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
|
||||
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
||||
services.AddOptions();
|
||||
services.AddLogging();
|
||||
|
||||
// Conjure up a RequestServices
|
||||
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
|
||||
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
|
||||
|
||||
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.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
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
|
||||
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, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
|
||||
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
|
||||
|
|
@ -64,11 +65,13 @@ namespace Microsoft.AspNetCore.Http
|
|||
|
||||
public FormOptions FormOptions { get; set; }
|
||||
|
||||
public IServiceScopeFactory ServiceScopeFactory { get; set; }
|
||||
|
||||
private IItemsFeature ItemsFeature =>
|
||||
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
|
||||
|
||||
private IServiceProvidersFeature ServiceProvidersFeature =>
|
||||
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
|
||||
_features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature);
|
||||
|
||||
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
|
||||
_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.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting.Internal
|
||||
namespace Microsoft.AspNetCore.Http.Features
|
||||
{
|
||||
public class RequestServicesFeature : IServiceProvidersFeature, IDisposable
|
||||
{
|
||||
|
|
@ -19,7 +16,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
|||
|
||||
public RequestServicesFeature(HttpContext context, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
Debug.Assert(scopeFactory != null);
|
||||
_context = context;
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
|
@ -28,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
|||
{
|
||||
get
|
||||
{
|
||||
if (!_requestServicesSet)
|
||||
if (!_requestServicesSet && _scopeFactory != null)
|
||||
{
|
||||
_context.Response.RegisterForDispose(this);
|
||||
_scope = _scopeFactory.CreateScope();
|
||||
|
|
@ -52,4 +48,4 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
|||
_requestServices = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,29 +3,47 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
[Obsolete("This is obsolete and will be removed in a future version. Use DefaultHttpContextFactory instead.")]
|
||||
public class HttpContextFactory : IHttpContextFactory
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly FormOptions _formOptions;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
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)
|
||||
: this(formOptions, serviceScopeFactory: null, httpContextAccessor: httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpContextFactory(IOptions<FormOptions> formOptions, IServiceScopeFactory serviceScopeFactory, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
if (formOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formOptions));
|
||||
}
|
||||
|
||||
if (serviceScopeFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||
}
|
||||
|
||||
_formOptions = formOptions.Value;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +61,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
}
|
||||
|
||||
httpContext.FormOptions = _formOptions;
|
||||
httpContext.ServiceScopeFactory = _serviceScopeFactory;
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace System.IO.Pipelines.Tests
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
public class DefaultHttpContextTests
|
||||
{
|
||||
|
|
@ -188,6 +190,48 @@ namespace System.IO.Pipelines.Tests
|
|||
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)
|
||||
{
|
||||
TestCachedFeaturesAreNull(context, features);
|
||||
|
|
@ -237,7 +281,7 @@ namespace System.IO.Pipelines.Tests
|
|||
|
||||
var fields = type
|
||||
.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)
|
||||
{
|
||||
|
|
@ -281,6 +325,36 @@ namespace System.IO.Pipelines.Tests
|
|||
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 Dictionary<string, byte[]> _store
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// 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;
|
||||
|
||||
|
|
@ -11,12 +13,24 @@ namespace Microsoft.AspNetCore.Http
|
|||
{
|
||||
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]
|
||||
public void CreateHttpContextSetsHttpContextAccessor()
|
||||
{
|
||||
// Arrange
|
||||
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
|
||||
var context = contextFactory.Create(new FeatureCollection());
|
||||
|
|
@ -30,7 +44,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
{
|
||||
// Arrange
|
||||
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
|
||||
var context = contextFactory.Create(new FeatureCollection());
|
||||
|
|
@ -47,11 +61,17 @@ namespace Microsoft.AspNetCore.Http
|
|||
public void AllowsCreatingContextWithoutSettingAccessor()
|
||||
{
|
||||
// Arrange
|
||||
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()));
|
||||
var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), new MyServiceScopeFactory());
|
||||
|
||||
// Act & Assert
|
||||
var context = contextFactory.Create(new FeatureCollection());
|
||||
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