Reduce usage of static inprocess application (#962)

This commit is contained in:
Pavel Krymets 2018-06-25 08:46:16 -07:00 committed by GitHub
parent d3d257b90e
commit bc345bc8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 47 deletions

View File

@ -9,8 +9,9 @@
// Initialization export // Initialization export
// //
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
VOID HRESULT
register_callbacks( register_callbacks(
_In_ IN_PROCESS_APPLICATION* pInProcessApplication,
_In_ PFN_REQUEST_HANDLER request_handler, _In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler, _In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
_In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler,
@ -18,13 +19,20 @@ register_callbacks(
_In_ VOID* pvShutdownHandlerContext _In_ VOID* pvShutdownHandlerContext
) )
{ {
IN_PROCESS_APPLICATION::GetInstance()->SetCallbackHandles( if (pInProcessApplication == NULL)
{
return E_INVALIDARG;
}
pInProcessApplication->SetCallbackHandles(
request_handler, request_handler,
shutdown_handler, shutdown_handler,
async_completion_handler, async_completion_handler,
pvRequstHandlerContext, pvRequstHandlerContext,
pvShutdownHandlerContext pvShutdownHandlerContext
); );
return S_OK;
} }
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
@ -150,12 +158,12 @@ http_get_completion_info(
} }
// //
// todo: we should not rely on IN_PROCESS_APPLICATION::GetInstance()
// the signature should be changed. application's based address should be passed in // the signature should be changed. application's based address should be passed in
// //
struct IISConfigurationData struct IISConfigurationData
{ {
IN_PROCESS_APPLICATION* pInProcessApplication;
BSTR pwzFullApplicationPath; BSTR pwzFullApplicationPath;
BSTR pwzVirtualApplicationPath; BSTR pwzVirtualApplicationPath;
BOOL fWindowsAuthEnabled; BOOL fWindowsAuthEnabled;
@ -169,16 +177,15 @@ http_get_application_properties(
_In_ IISConfigurationData* pIISCofigurationData _In_ IISConfigurationData* pIISCofigurationData
) )
{ {
REQUESTHANDLER_CONFIG* pConfiguration = NULL; auto pInProcessApplication = IN_PROCESS_APPLICATION::GetInstance();
IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); if (pInProcessApplication == NULL)
if (pApplication == NULL)
{ {
return E_FAIL; return E_FAIL;
} }
pConfiguration = pApplication->QueryConfig(); auto pConfiguration = pInProcessApplication->QueryConfig();
pIISCofigurationData->pInProcessApplication = pInProcessApplication;
pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr()); pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr());
pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr());
pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled();
@ -433,17 +440,29 @@ http_get_authentication_information(
} }
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
VOID HRESULT
http_stop_calls_into_managed() http_stop_calls_into_managed(_In_ IN_PROCESS_APPLICATION* pInProcessApplication)
{ {
IN_PROCESS_APPLICATION::GetInstance()->StopCallsIntoManaged(); if (pInProcessApplication == NULL)
{
return E_INVALIDARG;
}
pInProcessApplication->StopCallsIntoManaged();
return S_OK;
} }
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
VOID HRESULT
http_stop_incoming_requests() http_stop_incoming_requests(_In_ IN_PROCESS_APPLICATION* pInProcessApplication)
{ {
IN_PROCESS_APPLICATION::GetInstance()->StopIncomingRequests(); if (pInProcessApplication == NULL)
{
return E_INVALIDARG;
}
pInProcessApplication->StopIncomingRequests();
return S_OK;
} }
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT EXTERN_C __MIDL_DECLSPEC_DLLEXPORT

View File

@ -1,13 +1,15 @@
// 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.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.IIS.Core namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct IISConfigurationData internal struct IISConfigurationData
{ {
public IntPtr pNativeApplication;
[MarshalAs(UnmanagedType.BStr)] [MarshalAs(UnmanagedType.BStr)]
public string pwzFullApplicationPath; public string pwzFullApplicationPath;
[MarshalAs(UnmanagedType.BStr)] [MarshalAs(UnmanagedType.BStr)]

View File

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.IIS.Core namespace Microsoft.AspNetCore.Server.IIS.Core
@ -27,8 +28,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private readonly MemoryPool<byte> _memoryPool = new SlabMemoryPool(); private readonly MemoryPool<byte> _memoryPool = new SlabMemoryPool();
private GCHandle _httpServerHandle; private GCHandle _httpServerHandle;
private readonly IApplicationLifetime _applicationLifetime; private readonly IApplicationLifetime _applicationLifetime;
private readonly IAuthenticationSchemeProvider _authentication; private readonly ILogger<IISHttpServer> _logger;
private readonly IISServerOptions _options; private readonly IISServerOptions _options;
private readonly IISNativeApplication _nativeApplication;
private volatile int _stopping; private volatile int _stopping;
private bool Stopping => _stopping == 1; private bool Stopping => _stopping == 1;
@ -56,10 +58,17 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
return _websocketAvailable.Value; return _websocketAvailable.Value;
} }
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISServerOptions> options) public IISHttpServer(
IISNativeApplication nativeApplication,
IApplicationLifetime applicationLifetime,
IAuthenticationSchemeProvider authentication,
IOptions<IISServerOptions> options,
ILogger<IISHttpServer> logger
)
{ {
_nativeApplication = nativeApplication;
_applicationLifetime = applicationLifetime; _applicationLifetime = applicationLifetime;
_authentication = authentication; _logger = logger;
_options = options.Value; _options = options.Value;
if (_options.ForwardWindowsAuthentication) if (_options.ForwardWindowsAuthentication)
@ -73,10 +82,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_httpServerHandle = GCHandle.Alloc(this); _httpServerHandle = GCHandle.Alloc(this);
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this); _iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this);
_nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
// Start the server by registering the callback
NativeMethods.HttpRegisterCallbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -86,7 +92,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
cancellationToken.Register(() => cancellationToken.Register(() =>
{ {
NativeMethods.HttpStopCallsIntoManaged(); _nativeApplication.StopCallsIntoManaged();
_shutdownSignal.TrySetResult(null); _shutdownSignal.TrySetResult(null);
}); });
} }
@ -98,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
} }
// First call back into native saying "DON'T SEND ME ANY MORE REQUESTS" // First call back into native saying "DON'T SEND ME ANY MORE REQUESTS"
NativeMethods.HttpStopIncomingRequests(); _nativeApplication.StopIncomingRequests();
try try
{ {
@ -110,7 +116,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
else else
{ {
// We have drained all requests. Block any callbacks into managed at this point. // We have drained all requests. Block any callbacks into managed at this point.
NativeMethods.HttpStopCallsIntoManaged(); _nativeApplication.StopCallsIntoManaged();
_shutdownSignal.TrySetResult(null); _shutdownSignal.TrySetResult(null);
} }
} }
@ -127,7 +133,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_stopping = 1; _stopping = 1;
// Block any more calls into managed from native as we are unloading. // Block any more calls into managed from native as we are unloading.
NativeMethods.HttpStopCallsIntoManaged(); _nativeApplication.StopCallsIntoManaged();
_shutdownSignal.TrySetResult(null); _shutdownSignal.TrySetResult(null);
if (_httpServerHandle.IsAllocated) if (_httpServerHandle.IsAllocated)
@ -136,8 +142,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
} }
_memoryPool.Dispose(); _memoryPool.Dispose();
_nativeApplication.Dispose();
GC.SuppressFinalize(this);
} }
private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext) private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext)
@ -155,8 +160,19 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private static async Task HandleRequest(IISHttpContext context) private static async Task HandleRequest(IISHttpContext context)
{ {
var result = await context.ProcessRequestAsync(); bool successfulRequest = false;
CompleteRequest(context, result); try
{
successfulRequest = await context.ProcessRequestAsync();
}
catch (Exception ex)
{
context.Server._logger.LogError("Exception in ProcessRequestAsync", ex);
}
finally
{
CompleteRequest(context, successfulRequest);
}
} }
private static bool HandleShutdown(IntPtr pvRequestContext) private static bool HandleShutdown(IntPtr pvRequestContext)
@ -181,7 +197,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping) if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping)
{ {
// All requests have been drained. // All requests have been drained.
NativeMethods.HttpStopCallsIntoManaged(); context.Server._nativeApplication.StopCallsIntoManaged();
context.Server._shutdownSignal.TrySetResult(null); context.Server._shutdownSignal.TrySetResult(null);
} }
@ -215,12 +231,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server); return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server);
} }
} }
~IISHttpServer()
{
// If this finalize is invoked, try our best to block all calls into managed.
NativeMethods.HttpStopCallsIntoManaged();
}
} }
// Over engineering to avoid allocations... // Over engineering to avoid allocations...

View File

@ -0,0 +1,48 @@
// 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;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class IISNativeApplication
{
private readonly IntPtr _nativeApplication;
public IISNativeApplication(IntPtr nativeApplication)
{
_nativeApplication = nativeApplication;
}
public void StopIncomingRequests()
{
NativeMethods.HttpStopIncomingRequests(_nativeApplication);
}
public void StopCallsIntoManaged()
{
NativeMethods.HttpStopCallsIntoManaged(_nativeApplication);
}
public void RegisterCallbacks(
NativeMethods.PFN_REQUEST_HANDLER requestHandler,
NativeMethods.PFN_SHUTDOWN_HANDLER shutdownHandler,
NativeMethods.PFN_ASYNC_COMPLETION onAsyncCompletion,
IntPtr requestContext,
IntPtr shutdownContext)
{
NativeMethods.HttpRegisterCallbacks(_nativeApplication, requestHandler, shutdownHandler, onAsyncCompletion, requestContext, shutdownContext);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
~IISNativeApplication()
{
// If this finalize is invoked, try our best to block all calls into managed.
StopCallsIntoManaged();
}
}
}

View File

@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.IIS
private static extern void http_indicate_completion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus); private static extern void http_indicate_completion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus);
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
private static extern void register_callbacks(PFN_REQUEST_HANDLER request_callback, PFN_SHUTDOWN_HANDLER shutdown_callback, PFN_ASYNC_COMPLETION managed_context_handler, IntPtr pvRequestContext, IntPtr pvShutdownContext); private static extern int register_callbacks(IntPtr pInProcessApplication, PFN_REQUEST_HANDLER requestCallback, PFN_SHUTDOWN_HANDLER shutdownCallback, PFN_ASYNC_COMPLETION asyncCallback, IntPtr pvRequestContext, IntPtr pvShutdownContext);
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
private static extern unsafe int http_write_response_bytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected); private static extern unsafe int http_write_response_bytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected);
@ -65,10 +65,10 @@ namespace Microsoft.AspNetCore.Server.IIS
private static extern unsafe HttpApiTypes.HTTP_REQUEST_V2* http_get_raw_request(IntPtr pInProcessHandler); private static extern unsafe HttpApiTypes.HTTP_REQUEST_V2* http_get_raw_request(IntPtr pInProcessHandler);
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
private static extern void http_stop_calls_into_managed(); private static extern int http_stop_calls_into_managed(IntPtr pInProcessApplication);
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
private static extern void http_stop_incoming_requests(); private static extern int http_stop_incoming_requests(IntPtr pInProcessApplication);
[DllImport(AspNetCoreModuleDll)] [DllImport(AspNetCoreModuleDll)]
private static extern unsafe HttpApiTypes.HTTP_RESPONSE_V2* http_get_raw_response(IntPtr pInProcessHandler); private static extern unsafe HttpApiTypes.HTTP_RESPONSE_V2* http_get_raw_response(IntPtr pInProcessHandler);
@ -142,9 +142,9 @@ namespace Microsoft.AspNetCore.Server.IIS
{ {
http_indicate_completion(pInProcessHandler, notificationStatus); http_indicate_completion(pInProcessHandler, notificationStatus);
} }
public static void HttpRegisterCallbacks(PFN_REQUEST_HANDLER request_callback, PFN_SHUTDOWN_HANDLER shutdown_callback, PFN_ASYNC_COMPLETION managed_context_handler, IntPtr pvRequestContext, IntPtr pvShutdownContext) public static void HttpRegisterCallbacks(IntPtr pInProcessApplication, PFN_REQUEST_HANDLER requestCallback, PFN_SHUTDOWN_HANDLER shutdownCallback, PFN_ASYNC_COMPLETION asyncCallback, IntPtr pvRequestContext, IntPtr pvShutdownContext)
{ {
register_callbacks(request_callback, shutdown_callback, managed_context_handler, pvRequestContext, pvShutdownContext); Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, asyncCallback, pvRequestContext, pvShutdownContext));
} }
public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected) public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected)
@ -161,14 +161,14 @@ namespace Microsoft.AspNetCore.Server.IIS
return http_get_raw_request(pInProcessHandler); return http_get_raw_request(pInProcessHandler);
} }
public static void HttpStopCallsIntoManaged() public static void HttpStopCallsIntoManaged(IntPtr pInProcessApplication)
{ {
http_stop_calls_into_managed(); Validate(http_stop_calls_into_managed(pInProcessApplication));
} }
public static void HttpStopIncomingRequests() public static void HttpStopIncomingRequests(IntPtr pInProcessApplication)
{ {
http_stop_incoming_requests(); Validate(http_stop_incoming_requests(pInProcessApplication));
} }
public static unsafe HttpApiTypes.HTTP_RESPONSE_V2* HttpGetRawResponse(IntPtr pInProcessHandler) public static unsafe HttpApiTypes.HTTP_RESPONSE_V2* HttpGetRawResponse(IntPtr pInProcessHandler)

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Hosting
hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath); hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
return hostBuilder.ConfigureServices( return hostBuilder.ConfigureServices(
services => { services => {
services.AddSingleton(new IISNativeApplication(iisConfigData.pNativeApplication));
services.AddSingleton<IServer, IISHttpServer>(); services.AddSingleton<IServer, IISHttpServer>();
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath)); services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
services.AddAuthenticationCore(); services.AddAuthenticationCore();

View File

@ -114,7 +114,7 @@ namespace IISIntegration.FunctionalTests.Inprocess
var hostShutdownToken = deploymentResult.DeploymentResult.HostShutdownToken; var hostShutdownToken = deploymentResult.DeploymentResult.HostShutdownToken;
Assert.True(hostShutdownToken.WaitHandle.WaitOne(millisecondsTimeout: 1000)); Assert.True(hostShutdownToken.WaitHandle.WaitOne(Helpers.DefaultTimeout));
Assert.True(hostShutdownToken.IsCancellationRequested); Assert.True(hostShutdownToken.IsCancellationRequested);
} }

View File

@ -0,0 +1,31 @@
// 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.Threading.Tasks;
using IISIntegration.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfIISExpressSchemaMissingInProcess]
public class ShutdownTests : IISFunctionalTestBase
{
public ShutdownTests(ITestOutputHelper output) : base(output)
{
}
[ConditionalFact]
public async Task ServerShutsDownWhenMainExits()
{
var parameters = Helpers.GetBaseDeploymentParameters();
var result = await DeployAsync(parameters);
var response = await result.RetryingHttpClient.GetAsync("/Shutdown");
Assert.True(result.DeploymentResult.HostShutdownToken.WaitHandle.WaitOne(Helpers.DefaultTimeout));
}
}
}

View File

@ -1,6 +1,7 @@
// 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.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
public class Helpers public class Helpers
{ {
public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3);
public static string GetTestWebSitePath(string name) public static string GetTestWebSitePath(string name)
{ {
return Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"),"test", "WebSites", name); return Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"),"test", "WebSites", name);

View File

@ -8,10 +8,12 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.IISIntegration.FunctionalTests;
using Microsoft.AspNetCore.Server.IIS; using Microsoft.AspNetCore.Server.IIS;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Xunit; using Xunit;
@ -725,5 +727,14 @@ namespace IISTestSite
}); });
} }
private void Shutdown(IApplicationBuilder app)
{
app.Run(async ctx =>
{
await ctx.Response.WriteAsync("Shutting down");
ctx.RequestServices.GetService<IApplicationLifetime>().StopApplication();
});
}
} }
} }