diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index 20c5daab7e..bf7c089f95 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -89,13 +89,10 @@ namespace Microsoft.AspNetCore.Hosting.Internal services.TryAddSingleton(listener); services.TryAddSingleton(listener); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddScoped(); services.TryAddSingleton(); - // Conjure up a RequestServices - services.TryAddTransient(); - // Support UseStartup(assemblyName) if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly)) { diff --git a/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs b/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs deleted file mode 100644 index b75958fa52..0000000000 --- a/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs +++ /dev/null @@ -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 Configure(Action next) - { - return builder => - { - builder.UseMiddleware(); - next(builder); - }; - } - } -} diff --git a/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs b/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs deleted file mode 100644 index 9fcb01aa12..0000000000 --- a/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs +++ /dev/null @@ -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(); - - // All done if RequestServices is set - if (servicesFeature?.RequestServices != null) - { - return _next.Invoke(httpContext); - } - - features.Set(new RequestServicesFeature(httpContext, _scopeFactory)); - return _next.Invoke(httpContext); - } - } -} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs index 59c12ace29..9bcb9e3b09 100644 --- a/src/Hosting/Hosting/src/WebHostBuilder.cs +++ b/src/Hosting/Hosting/src/WebHostBuilder.cs @@ -272,13 +272,11 @@ namespace Microsoft.AspNetCore.Hosting services.AddSingleton(listener); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); services.AddScoped(); services.AddOptions(); services.AddLogging(); - // Conjure up a RequestServices - services.AddTransient(); services.AddTransient, DefaultServiceProviderFactory>(); if (!string.IsNullOrEmpty(_options.StartupAssembly)) diff --git a/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs b/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs deleted file mode 100644 index c153af4dc1..0000000000 --- a/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs +++ /dev/null @@ -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(); - - 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(); - - 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() - .BuildServiceProvider(); - - var scopeFactory = serviceProvider.GetRequiredService(); - DisposableThing instance = null; - - var middleware = new RequestServicesContainerMiddleware( - ctx => - { - instance = ctx.RequestServices.GetRequiredService(); - return Task.CompletedTask; - }, - scopeFactory); - - var context = new DefaultHttpContext(); - var responseFeature = new TestHttpResponseFeature(); - context.Features.Set(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 callback, object state)> CompletedCallbacks = new List<(Func 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 callback, object state) - { - CompletedCallbacks.Add((callback, state)); - } - - public void OnStarting(Func callback, object state) - { - } - } - } -} \ No newline at end of file diff --git a/src/Http/Http/src/DefaultHttpContext.cs b/src/Http/Http/src/DefaultHttpContext.cs index 9a7d6b8c3a..0135d2d7ab 100644 --- a/src/Http/Http/src/DefaultHttpContext.cs +++ b/src/Http/Http/src/DefaultHttpContext.cs @@ -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 _newItemsFeature = f => new ItemsFeature(); - private readonly static Func _newServiceProvidersFeature = f => new ServiceProvidersFeature(); + private readonly static Func _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory); private readonly static Func _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature(); private readonly static Func _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature(); private readonly static Func _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); diff --git a/src/Http/Http/src/DefaultHttpContextFactory.cs b/src/Http/Http/src/DefaultHttpContextFactory.cs new file mode 100644 index 0000000000..4c6d862c35 --- /dev/null +++ b/src/Http/Http/src/DefaultHttpContextFactory.cs @@ -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(); + _formOptions = serviceProvider.GetRequiredService>().Value; + _serviceScopeFactory = serviceProvider.GetRequiredService(); + } + + 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; + } + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs b/src/Http/Http/src/Features/RequestServicesFeature.cs similarity index 85% rename from src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs rename to src/Http/Http/src/Features/RequestServicesFeature.cs index a57df9bcbc..ccd77483de 100644 --- a/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs +++ b/src/Http/Http/src/Features/RequestServicesFeature.cs @@ -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; } } -} \ No newline at end of file +} diff --git a/src/Http/Http/src/HttpContextFactory.cs b/src/Http/Http/src/HttpContextFactory.cs index cc2f796d6b..4799e4b908 100644 --- a/src/Http/Http/src/HttpContextFactory.cs +++ b/src/Http/Http/src/HttpContextFactory.cs @@ -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) - : this(formOptions, httpContextAccessor: null) + : this(formOptions, serviceScopeFactory: null) + { + } + + public HttpContextFactory(IOptions formOptions, IServiceScopeFactory serviceScopeFactory) + : this(formOptions, serviceScopeFactory, httpContextAccessor: null) { } public HttpContextFactory(IOptions formOptions, IHttpContextAccessor httpContextAccessor) + : this(formOptions, serviceScopeFactory: null, httpContextAccessor: httpContextAccessor) + { + } + + public HttpContextFactory(IOptions 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; } diff --git a/src/Http/Http/test/DefaultHttpContextFactoryTests.cs b/src/Http/Http/test/DefaultHttpContextFactoryTests.cs new file mode 100644 index 0000000000..8fa2ad68d9 --- /dev/null +++ b/src/Http/Http/test/DefaultHttpContextFactoryTests.cs @@ -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(); + 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(); + 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(), context.ServiceScopeFactory); + } + } +} diff --git a/src/Http/Http/test/DefaultHttpContextTests.cs b/src/Http/Http/test/DefaultHttpContextTests.cs index 56cb63b1a4..5a54bc2726 100644 --- a/src/Http/Http/test/DefaultHttpContextTests.cs +++ b/src/Http/Http/test/DefaultHttpContextTests.cs @@ -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(); + + 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() + .BuildServiceProvider(); + + var scopeFactory = serviceProvider.GetRequiredService(); + DisposableThing instance = null; + + var context = new DefaultHttpContext(); + context.ServiceScopeFactory = scopeFactory; + var responseFeature = new TestHttpResponseFeature(); + context.Features.Set(responseFeature); + + Assert.NotNull(context.RequestServices); + Assert.Single(responseFeature.CompletedCallbacks); + + instance = context.RequestServices.GetRequiredService(); + + 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() == 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 callback, object state)> CompletedCallbacks = new List<(Func 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 callback, object state) + { + CompletedCallbacks.Add((callback, state)); + } + + public void OnStarting(Func callback, object state) + { + } + } + private class TestSession : ISession { private Dictionary _store diff --git a/src/Http/Http/test/HttpContextFactoryTests.cs b/src/Http/Http/test/HttpContextFactoryTests.cs index 56b996f5be..d088d71138 100644 --- a/src/Http/Http/test/HttpContextFactoryTests.cs +++ b/src/Http/Http/test/HttpContextFactoryTests.cs @@ -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(() => new HttpContextFactory(Options.Create(new FormOptions()), accessor)); + var exception2 = Assert.Throws(() => 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; + } } -} \ No newline at end of file +} +#pragma warning restore CS0618 // Type or member is obsolete