diff --git a/.gitmodules b/.gitmodules index cf504a5e0b..26d30c99b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,14 +26,6 @@ path = modules/EntityFrameworkCore url = https://github.com/aspnet/EntityFrameworkCore.git branch = release/2.2 -[submodule "modules/Hosting"] - path = modules/Hosting - url = https://github.com/aspnet/Hosting.git - branch = release/2.2 -[submodule "modules/HttpAbstractions"] - path = modules/HttpAbstractions - url = https://github.com/aspnet/HttpAbstractions.git - branch = release/2.2 [submodule "modules/HttpSysServer"] path = modules/HttpSysServer url = https://github.com/aspnet/HttpSysServer.git diff --git a/Directory.Build.props b/Directory.Build.props index b121675096..cf5ad72327 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -84,6 +84,8 @@ false true + $(MSBuildThisFileDirectory)src\Shared\ + true diff --git a/build/artifacts.props b/build/artifacts.props index 1364e33315..4203c4cd99 100644 --- a/build/artifacts.props +++ b/build/artifacts.props @@ -189,8 +189,6 @@ - - diff --git a/build/buildorder.props b/build/buildorder.props index a5e43379ce..8efbbd42ba 100644 --- a/build/buildorder.props +++ b/build/buildorder.props @@ -8,8 +8,6 @@ - - @@ -36,6 +34,6 @@ - + diff --git a/build/dependencies.props b/build/dependencies.props index ed701e7dfe..48d2a749f4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -63,6 +63,8 @@ 2.2.0 2.2.0 2.2.0 + 2.2.0 + 2.2.0 2.2.0 2.2.0 2.2.0 @@ -91,8 +93,11 @@ 2.2.0 2.2.0 2.2.0 + + 2.2.0 2.2.0 + 0.6.0-rtm-final 0.9.9 diff --git a/build/external-dependencies.props b/build/external-dependencies.props index 4ed8ca7d83..e99d58281f 100644 --- a/build/external-dependencies.props +++ b/build/external-dependencies.props @@ -46,6 +46,8 @@ + + @@ -74,8 +76,11 @@ + + + diff --git a/build/repo.props b/build/repo.props index d5d5cee4c3..0c06c29e6c 100644 --- a/build/repo.props +++ b/build/repo.props @@ -49,7 +49,7 @@ - + @@ -62,6 +62,8 @@ - + @@ -55,8 +55,6 @@ - - diff --git a/eng/Baseline.props b/eng/Baseline.props index 4cf3227b79..f052db5eb0 100644 --- a/eng/Baseline.props +++ b/eng/Baseline.props @@ -96,6 +96,53 @@ + + + 2.1.1 + + + + + + + + + 2.1.1 + + + + + + + + 2.1.1 + + + + + + + + + + + 2.1.1 + + + + + + + + + + + + + + + + 2.2.0 @@ -103,6 +150,42 @@ + + + 2.1.1 + + + + + + + + 2.1.1 + + + + + + + + + + 2.1.1 + + + + + + + 2.1.1 + + + + + + + + 2.2.0 @@ -111,6 +194,13 @@ + + + 2.1.1 + + + + 2.2.0 @@ -199,6 +289,14 @@ + + + 2.1.1 + + + + + 2.2.0 @@ -209,4 +307,20 @@ - \ No newline at end of file + + + 2.1.1 + + + + + + + + 2.1.1 + + + + + + diff --git a/eng/Dependencies.props b/eng/Dependencies.props index bf70c875ec..d32aec7bd4 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -17,6 +17,7 @@ + @@ -24,14 +25,21 @@ + + + + + + + @@ -51,6 +59,8 @@ + + diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 7d59a10167..164c258a97 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -12,6 +12,21 @@ + + + + + + + + + + + + + + + diff --git a/eng/dependencies.temp.props b/eng/dependencies.temp.props index b89d33a877..c7e0c9bc50 100644 --- a/eng/dependencies.temp.props +++ b/eng/dependencies.temp.props @@ -1,5 +1,5 @@ @@ -7,9 +7,5 @@ This is required to provide dependencies for samples and tests. - - - - diff --git a/eng/tools/BaselineGenerator/baseline.xml b/eng/tools/BaselineGenerator/baseline.xml index e5b96b4371..c37bf56d4f 100644 --- a/eng/tools/BaselineGenerator/baseline.xml +++ b/eng/tools/BaselineGenerator/baseline.xml @@ -3,6 +3,8 @@ + + @@ -14,13 +16,25 @@ + + + + + + + + + + + + diff --git a/modules/Hosting b/modules/Hosting deleted file mode 160000 index 0724e6cde1..0000000000 --- a/modules/Hosting +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0724e6cde1149ee1a19bfec9c13a2c9327b71213 diff --git a/modules/HttpAbstractions b/modules/HttpAbstractions deleted file mode 160000 index 91db78cf92..0000000000 --- a/modules/HttpAbstractions +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 91db78cf926939821bc96e8e60616cf5dde0b489 diff --git a/src/Hosting/Abstractions/src/EnvironmentName.cs b/src/Hosting/Abstractions/src/EnvironmentName.cs new file mode 100644 index 0000000000..d5522d1124 --- /dev/null +++ b/src/Hosting/Abstractions/src/EnvironmentName.cs @@ -0,0 +1,15 @@ +// 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. + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Commonly used environment names. + /// + public static class EnvironmentName + { + public static readonly string Development = "Development"; + public static readonly string Staging = "Staging"; + public static readonly string Production = "Production"; + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs new file mode 100644 index 0000000000..f61b86eb86 --- /dev/null +++ b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs @@ -0,0 +1,197 @@ +// 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.Globalization; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class HostingAbstractionsWebHostBuilderExtensions + { + private static readonly string ServerUrlsSeparator = ";"; + + /// + /// Use the given configuration settings on the web host. + /// + /// The to configure. + /// The containing settings to be used. + /// The . + public static IWebHostBuilder UseConfiguration(this IWebHostBuilder hostBuilder, IConfiguration configuration) + { + foreach (var setting in configuration.AsEnumerable()) + { + hostBuilder.UseSetting(setting.Key, setting.Value); + } + + return hostBuilder; + } + + /// + /// Set whether startup errors should be captured in the configuration settings of the web host. + /// When enabled, startup exceptions will be caught and an error page will be returned. If disabled, startup exceptions will be propagated. + /// + /// The to configure. + /// true to use startup error page; otherwise false. + /// The . + public static IWebHostBuilder CaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupErrors) + { + return hostBuilder.UseSetting(WebHostDefaults.CaptureStartupErrorsKey, captureStartupErrors ? "true" : "false"); + } + + /// + /// Specify the assembly containing the startup type to be used by the web host. + /// + /// The to configure. + /// The name of the assembly containing the startup type. + /// The . + public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName) + { + if (startupAssemblyName == null) + { + throw new ArgumentNullException(nameof(startupAssemblyName)); + } + + + return hostBuilder + .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) + .UseSetting(WebHostDefaults.StartupAssemblyKey, startupAssemblyName); + } + + /// + /// Specify the server to be used by the web host. + /// + /// The to configure. + /// The to be used. + /// The . + public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServer server) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + + return hostBuilder.ConfigureServices(services => + { + // It would be nicer if this was transient but we need to pass in the + // factory instance directly + services.AddSingleton(server); + }); + } + + /// + /// Specify the environment to be used by the web host. + /// + /// The to configure. + /// The environment to host the application in. + /// The . + public static IWebHostBuilder UseEnvironment(this IWebHostBuilder hostBuilder, string environment) + { + if (environment == null) + { + throw new ArgumentNullException(nameof(environment)); + } + + return hostBuilder.UseSetting(WebHostDefaults.EnvironmentKey, environment); + } + + /// + /// Specify the content root directory to be used by the web host. + /// + /// The to configure. + /// Path to root directory of the application. + /// The . + public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot) + { + if (contentRoot == null) + { + throw new ArgumentNullException(nameof(contentRoot)); + } + + return hostBuilder.UseSetting(WebHostDefaults.ContentRootKey, contentRoot); + } + + /// + /// Specify the webroot directory to be used by the web host. + /// + /// The to configure. + /// Path to the root directory used by the web server. + /// The . + public static IWebHostBuilder UseWebRoot(this IWebHostBuilder hostBuilder, string webRoot) + { + if (webRoot == null) + { + throw new ArgumentNullException(nameof(webRoot)); + } + + return hostBuilder.UseSetting(WebHostDefaults.WebRootKey, webRoot); + } + + /// + /// Specify the urls the web host will listen on. + /// + /// The to configure. + /// The urls the hosted application will listen on. + /// The . + public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls) + { + if (urls == null) + { + throw new ArgumentNullException(nameof(urls)); + } + + return hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls)); + } + + /// + /// Indicate whether the host should listen on the URLs configured on the + /// instead of those configured on the . + /// + /// The to configure. + /// true to prefer URLs configured on the ; otherwise false. + /// The . + public static IWebHostBuilder PreferHostingUrls(this IWebHostBuilder hostBuilder, bool preferHostingUrls) + { + return hostBuilder.UseSetting(WebHostDefaults.PreferHostingUrlsKey, preferHostingUrls ? "true" : "false"); + } + + /// + /// Specify if startup status messages should be suppressed. + /// + /// The to configure. + /// true to suppress writing of hosting startup status messages; otherwise false. + /// The . + public static IWebHostBuilder SuppressStatusMessages(this IWebHostBuilder hostBuilder, bool suppressStatusMessages) + { + return hostBuilder.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, suppressStatusMessages ? "true" : "false"); + } + + /// + /// Specify the amount of time to wait for the web host to shutdown. + /// + /// The to configure. + /// The amount of time to wait for server shutdown. + /// The . + public static IWebHostBuilder UseShutdownTimeout(this IWebHostBuilder hostBuilder, TimeSpan timeout) + { + return hostBuilder.UseSetting(WebHostDefaults.ShutdownTimeoutKey, ((int)timeout.TotalSeconds).ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Start the web host and listen on the specified urls. + /// + /// The to start. + /// The urls the hosted application will listen on. + /// The . + public static IWebHost Start(this IWebHostBuilder hostBuilder, params string[] urls) + { + var host = hostBuilder.UseUrls(urls).Build(); + host.StartAsync(CancellationToken.None).GetAwaiter().GetResult(); + return host; + } + } +} diff --git a/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs b/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs new file mode 100644 index 0000000000..ad3269859d --- /dev/null +++ b/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs @@ -0,0 +1,80 @@ +// 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; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Extension methods for . + /// + public static class HostingEnvironmentExtensions + { + /// + /// Checks if the current hosting environment name is . + /// + /// An instance of . + /// True if the environment name is , otherwise false. + public static bool IsDevelopment(this IHostingEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment(EnvironmentName.Development); + } + + /// + /// Checks if the current hosting environment name is . + /// + /// An instance of . + /// True if the environment name is , otherwise false. + public static bool IsStaging(this IHostingEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment(EnvironmentName.Staging); + } + + /// + /// Checks if the current hosting environment name is . + /// + /// An instance of . + /// True if the environment name is , otherwise false. + public static bool IsProduction(this IHostingEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment(EnvironmentName.Production); + } + + /// + /// Compares the current hosting environment name against the specified value. + /// + /// An instance of . + /// Environment name to validate against. + /// True if the specified name is the same as the current environment, otherwise false. + public static bool IsEnvironment( + this IHostingEnvironment hostingEnvironment, + string environmentName) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return string.Equals( + hostingEnvironment.EnvironmentName, + environmentName, + StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/HostingStartupAttribute.cs b/src/Hosting/Abstractions/src/HostingStartupAttribute.cs new file mode 100644 index 0000000000..cb028c327b --- /dev/null +++ b/src/Hosting/Abstractions/src/HostingStartupAttribute.cs @@ -0,0 +1,40 @@ +// 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.Reflection; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Marker attribute indicating an implementation of that will be loaded and executed when building an . + /// + [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] + public sealed class HostingStartupAttribute : Attribute + { + /// + /// Constructs the with the specified type. + /// + /// A type that implements . + public HostingStartupAttribute(Type hostingStartupType) + { + if (hostingStartupType == null) + { + throw new ArgumentNullException(nameof(hostingStartupType)); + } + + if (!typeof(IHostingStartup).GetTypeInfo().IsAssignableFrom(hostingStartupType.GetTypeInfo())) + { + throw new ArgumentException($@"""{hostingStartupType}"" does not implement {typeof(IHostingStartup)}.", nameof(hostingStartupType)); + } + + HostingStartupType = hostingStartupType; + } + + /// + /// The implementation of that should be loaded when + /// starting an application. + /// + public Type HostingStartupType { get; } + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/IApplicationLifetime.cs b/src/Hosting/Abstractions/src/IApplicationLifetime.cs new file mode 100644 index 0000000000..f4613dd7d9 --- /dev/null +++ b/src/Hosting/Abstractions/src/IApplicationLifetime.cs @@ -0,0 +1,37 @@ +// 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; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Allows consumers to perform cleanup during a graceful shutdown. + /// + public interface IApplicationLifetime + { + /// + /// Triggered when the application host has fully started and is about to wait + /// for a graceful shutdown. + /// + CancellationToken ApplicationStarted { get; } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// Requests may still be in flight. Shutdown will block until this event completes. + /// + CancellationToken ApplicationStopping { get; } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// All requests should be complete at this point. Shutdown will block + /// until this event completes. + /// + CancellationToken ApplicationStopped { get; } + + /// + /// Requests termination of the current application. + /// + void StopApplication(); + } +} diff --git a/src/Hosting/Abstractions/src/IHostingEnvironment.cs b/src/Hosting/Abstractions/src/IHostingEnvironment.cs new file mode 100644 index 0000000000..5feeb38eb7 --- /dev/null +++ b/src/Hosting/Abstractions/src/IHostingEnvironment.cs @@ -0,0 +1,45 @@ +// 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 Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Provides information about the web hosting environment an application is running in. + /// + public interface IHostingEnvironment + { + /// + /// Gets or sets the name of the environment. The host automatically sets this property to the value + /// of the "ASPNETCORE_ENVIRONMENT" environment variable, or "environment" as specified in any other configuration source. + /// + string EnvironmentName { get; set; } + + /// + /// Gets or sets the name of the application. This property is automatically set by the host to the assembly containing + /// the application entry point. + /// + string ApplicationName { get; set; } + + /// + /// Gets or sets the absolute path to the directory that contains the web-servable application content files. + /// + string WebRootPath { get; set; } + + /// + /// Gets or sets an pointing at . + /// + IFileProvider WebRootFileProvider { get; set; } + + /// + /// Gets or sets the absolute path to the directory that contains the application content files. + /// + string ContentRootPath { get; set; } + + /// + /// Gets or sets an pointing at . + /// + IFileProvider ContentRootFileProvider { get; set; } + } +} diff --git a/src/Hosting/Abstractions/src/IHostingStartup.cs b/src/Hosting/Abstractions/src/IHostingStartup.cs new file mode 100644 index 0000000000..e65ed18fb6 --- /dev/null +++ b/src/Hosting/Abstractions/src/IHostingStartup.cs @@ -0,0 +1,22 @@ +// 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.Hosting +{ + /// + /// Represents platform specific configuration that will be applied to a when building an . + /// + public interface IHostingStartup + { + /// + /// Configure the . + /// + /// + /// Configure is intended to be called before user code, allowing a user to overwrite any changes made. + /// + /// + void Configure(IWebHostBuilder builder); + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/IStartup.cs b/src/Hosting/Abstractions/src/IStartup.cs new file mode 100644 index 0000000000..3a533c8df2 --- /dev/null +++ b/src/Hosting/Abstractions/src/IStartup.cs @@ -0,0 +1,16 @@ +// 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; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public interface IStartup + { + IServiceProvider ConfigureServices(IServiceCollection services); + + void Configure(IApplicationBuilder app); + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/IStartupFilter.cs b/src/Hosting/Abstractions/src/IStartupFilter.cs new file mode 100644 index 0000000000..2f0a3cf39d --- /dev/null +++ b/src/Hosting/Abstractions/src/IStartupFilter.cs @@ -0,0 +1,13 @@ +// 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 +{ + public interface IStartupFilter + { + Action Configure(Action next); + } +} diff --git a/src/Hosting/Abstractions/src/IWebHost.cs b/src/Hosting/Abstractions/src/IWebHost.cs new file mode 100644 index 0000000000..97331e4768 --- /dev/null +++ b/src/Hosting/Abstractions/src/IWebHost.cs @@ -0,0 +1,43 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Represents a configured web host. + /// + public interface IWebHost : IDisposable + { + /// + /// The exposed by the configured server. + /// + IFeatureCollection ServerFeatures { get; } + + /// + /// The for the host. + /// + IServiceProvider Services { get; } + + /// + /// Starts listening on the configured addresses. + /// + void Start(); + + /// + /// Starts listening on the configured addresses. + /// + Task StartAsync(CancellationToken cancellationToken = default); + + /// + /// Attempt to gracefully stop the host. + /// + /// + /// + Task StopAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Hosting/Abstractions/src/IWebHostBuilder.cs b/src/Hosting/Abstractions/src/IWebHostBuilder.cs new file mode 100644 index 0000000000..2cf3bc1163 --- /dev/null +++ b/src/Hosting/Abstractions/src/IWebHostBuilder.cs @@ -0,0 +1,63 @@ +// 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// A builder for . + /// + public interface IWebHostBuilder + { + /// + /// Builds an which hosts a web application. + /// + IWebHost Build(); + + /// + /// Adds a delegate for configuring the that will construct an . + /// + /// The delegate for configuring the that will be used to construct an . + /// The . + /// + /// The and on the are uninitialized at this stage. + /// The is pre-populated with the settings of the . + /// + IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate); + + /// + /// Adds a delegate for configuring additional services for the host or web application. This may be called + /// multiple times. + /// + /// A delegate for configuring the . + /// The . + IWebHostBuilder ConfigureServices(Action configureServices); + + /// + /// Adds a delegate for configuring additional services for the host or web application. This may be called + /// multiple times. + /// + /// A delegate for configuring the . + /// The . + IWebHostBuilder ConfigureServices(Action configureServices); + + /// + /// Get the setting value from the configuration. + /// + /// The key of the setting to look up. + /// The value the setting currently contains. + string GetSetting(string key); + + /// + /// Add or replace a setting in the configuration. + /// + /// The key of the setting to add or replace. + /// The value of the setting to add or replace. + /// The . + IWebHostBuilder UseSetting(string key, string value); + } +} \ No newline at end of file diff --git a/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs b/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs new file mode 100644 index 0000000000..e58ac19774 --- /dev/null +++ b/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs @@ -0,0 +1,16 @@ +// 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.Hosting.Internal +{ + /// + /// This API supports the ASP.NET Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public interface IStartupConfigureContainerFilter + { + Action ConfigureContainer(Action container); + } +} diff --git a/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs b/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs new file mode 100644 index 0000000000..ad203bcedb --- /dev/null +++ b/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs @@ -0,0 +1,17 @@ +// 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.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + /// + /// This API supports the ASP.NET Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public interface IStartupConfigureServicesFilter + { + Action ConfigureServices(Action next); + } +} diff --git a/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj b/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj new file mode 100644 index 0000000000..a01be8ea3f --- /dev/null +++ b/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj @@ -0,0 +1,17 @@ + + + + ASP.NET Core hosting and startup abstractions for web applications. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;hosting + + + + + + + + + diff --git a/src/Hosting/Abstractions/src/WebHostBuilderContext.cs b/src/Hosting/Abstractions/src/WebHostBuilderContext.cs new file mode 100644 index 0000000000..58e8d0798b --- /dev/null +++ b/src/Hosting/Abstractions/src/WebHostBuilderContext.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.Extensions.Configuration; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Context containing the common services on the . Some properties may be null until set by the . + /// + public class WebHostBuilderContext + { + /// + /// The initialized by the . + /// + public IHostingEnvironment HostingEnvironment { get; set; } + + /// + /// The containing the merged configuration of the application and the . + /// + public IConfiguration Configuration { get; set; } + } +} diff --git a/src/Hosting/Abstractions/src/WebHostDefaults.cs b/src/Hosting/Abstractions/src/WebHostDefaults.cs new file mode 100644 index 0000000000..4de391d0a2 --- /dev/null +++ b/src/Hosting/Abstractions/src/WebHostDefaults.cs @@ -0,0 +1,25 @@ +// 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. + +namespace Microsoft.AspNetCore.Hosting +{ + public static class WebHostDefaults + { + public static readonly string ApplicationKey = "applicationName"; + public static readonly string StartupAssemblyKey = "startupAssembly"; + public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies"; + public static readonly string HostingStartupExcludeAssembliesKey = "hostingStartupExcludeAssemblies"; + + public static readonly string DetailedErrorsKey = "detailedErrors"; + public static readonly string EnvironmentKey = "environment"; + public static readonly string WebRootKey = "webroot"; + public static readonly string CaptureStartupErrorsKey = "captureStartupErrors"; + public static readonly string ServerUrlsKey = "urls"; + public static readonly string ContentRootKey = "contentRoot"; + public static readonly string PreferHostingUrlsKey = "preferHostingUrls"; + public static readonly string PreventHostingStartupKey = "preventHostingStartup"; + public static readonly string SuppressStatusMessagesKey = "suppressStatusMessages"; + + public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds"; + } +} diff --git a/src/Hosting/Abstractions/src/baseline.netcore.json b/src/Hosting/Abstractions/src/baseline.netcore.json new file mode 100644 index 0000000000..7536bf1233 --- /dev/null +++ b/src/Hosting/Abstractions/src/baseline.netcore.json @@ -0,0 +1,947 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Hosting.Abstractions, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Hosting.EnvironmentName", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Development", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Staging", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Production", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.HostingAbstractionsWebHostBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "UseConfiguration", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "configuration", + "Type": "Microsoft.Extensions.Configuration.IConfiguration" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CaptureStartupErrors", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "captureStartupErrors", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseStartup", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "startupAssemblyName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseServer", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "server", + "Type": "Microsoft.AspNetCore.Hosting.Server.IServer" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseEnvironment", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "environment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseContentRoot", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "contentRoot", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseWebRoot", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "webRoot", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseUrls", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "urls", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PreferHostingUrls", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "preferHostingUrls", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseShutdownTimeout", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "timeout", + "Type": "System.TimeSpan" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Start", + "Parameters": [ + { + "Name": "hostBuilder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + }, + { + "Name": "urls", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.HostingEnvironmentExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "IsDevelopment", + "Parameters": [ + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsStaging", + "Parameters": [ + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsProduction", + "Parameters": [ + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsEnvironment", + "Parameters": [ + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + }, + { + "Name": "environmentName", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.HostingStartupAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HostingStartupType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "hostingStartupType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IApplicationLifetime", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApplicationStarted", + "Parameters": [], + "ReturnType": "System.Threading.CancellationToken", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApplicationStopping", + "Parameters": [], + "ReturnType": "System.Threading.CancellationToken", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApplicationStopped", + "Parameters": [], + "ReturnType": "System.Threading.CancellationToken", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StopApplication", + "Parameters": [], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IHostingEnvironment", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_EnvironmentName", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EnvironmentName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApplicationName", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApplicationName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_WebRootPath", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_WebRootPath", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_WebRootFileProvider", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_WebRootFileProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Extensions.FileProviders.IFileProvider" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentRootPath", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentRootPath", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentRootFileProvider", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentRootFileProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Extensions.FileProviders.IFileProvider" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IHostingStartup", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Configure", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IStartup", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ConfigureServices", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + } + ], + "ReturnType": "System.IServiceProvider", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Configure", + "Parameters": [ + { + "Name": "app", + "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IStartupFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Configure", + "Parameters": [ + { + "Name": "next", + "Type": "System.Action" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IWebHost", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "System.IDisposable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ServerFeatures", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Services", + "Parameters": [], + "ReturnType": "System.IServiceProvider", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Start", + "Parameters": [], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StartAsync", + "Parameters": [ + { + "Name": "cancellationToken", + "Type": "System.Threading.CancellationToken", + "DefaultValue": "default(System.Threading.CancellationToken)" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StopAsync", + "Parameters": [ + { + "Name": "cancellationToken", + "Type": "System.Threading.CancellationToken", + "DefaultValue": "default(System.Threading.CancellationToken)" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Build", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureAppConfiguration", + "Parameters": [ + { + "Name": "configureDelegate", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureServices", + "Parameters": [ + { + "Name": "configureServices", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureServices", + "Parameters": [ + { + "Name": "configureServices", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSetting", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseSetting", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HostingEnvironment", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Hosting.IHostingEnvironment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HostingEnvironment", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Configuration", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Configuration.IConfiguration", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Configuration", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Extensions.Configuration.IConfiguration" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Hosting.WebHostDefaults", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "ApplicationKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "StartupAssemblyKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "HostingStartupAssembliesKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "DetailedErrorsKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "EnvironmentKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "WebRootKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "CaptureStartupErrorsKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "ServerUrlsKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "ContentRootKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "PreferHostingUrlsKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "PreventHostingStartupKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "ShutdownTimeoutKey", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs b/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs new file mode 100644 index 0000000000..e188c0b7fd --- /dev/null +++ b/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs @@ -0,0 +1,25 @@ +// 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; +using Microsoft.AspNetCore.Builder.Internal; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Hosting.Builder +{ + public class ApplicationBuilderFactory : IApplicationBuilderFactory + { + private readonly IServiceProvider _serviceProvider; + + public ApplicationBuilderFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures) + { + return new ApplicationBuilder(_serviceProvider, serverFeatures); + } + } +} diff --git a/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs b/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs new file mode 100644 index 0000000000..d44398fb69 --- /dev/null +++ b/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Hosting.Builder +{ + public interface IApplicationBuilderFactory + { + IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures); + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs b/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs new file mode 100644 index 0000000000..958f8b5dcc --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs @@ -0,0 +1,114 @@ +// 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.Threading; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + /// + /// Allows consumers to perform cleanup during a graceful shutdown. + /// + public class ApplicationLifetime : IApplicationLifetime, Extensions.Hosting.IApplicationLifetime + { + private readonly CancellationTokenSource _startedSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource(); + private readonly ILogger _logger; + + public ApplicationLifetime(ILogger logger) + { + _logger = logger; + } + + /// + /// Triggered when the application host has fully started and is about to wait + /// for a graceful shutdown. + /// + public CancellationToken ApplicationStarted => _startedSource.Token; + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// Request may still be in flight. Shutdown will block until this event completes. + /// + public CancellationToken ApplicationStopping => _stoppingSource.Token; + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// All requests should be complete at this point. Shutdown will block + /// until this event completes. + /// + public CancellationToken ApplicationStopped => _stoppedSource.Token; + + /// + /// Signals the ApplicationStopping event and blocks until it completes. + /// + public void StopApplication() + { + // Lock on CTS to synchronize multiple calls to StopApplication. This guarantees that the first call + // to StopApplication and its callbacks run to completion before subsequent calls to StopApplication, + // which will no-op since the first call already requested cancellation, get a chance to execute. + lock (_stoppingSource) + { + try + { + ExecuteHandlers(_stoppingSource); + } + catch (Exception ex) + { + _logger.ApplicationError(LoggerEventIds.ApplicationStoppingException, + "An error occurred stopping the application", + ex); + } + } + } + + /// + /// Signals the ApplicationStarted event and blocks until it completes. + /// + public void NotifyStarted() + { + try + { + ExecuteHandlers(_startedSource); + } + catch (Exception ex) + { + _logger.ApplicationError(LoggerEventIds.ApplicationStartupException, + "An error occurred starting the application", + ex); + } + } + + /// + /// Signals the ApplicationStopped event and blocks until it completes. + /// + public void NotifyStopped() + { + try + { + ExecuteHandlers(_stoppedSource); + } + catch (Exception ex) + { + _logger.ApplicationError(LoggerEventIds.ApplicationStoppedException, + "An error occurred stopping the application", + ex); + } + } + + private void ExecuteHandlers(CancellationTokenSource cancel) + { + // Noop if this is already cancelled + if (cancel.IsCancellationRequested) + { + return; + } + + // Run the cancellation token callbacks + cancel.Cancel(throwOnFirstException: false); + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs b/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs new file mode 100644 index 0000000000..b75958fa52 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs @@ -0,0 +1,20 @@ +// 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/ConfigureBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs new file mode 100644 index 0000000000..37b715c5b0 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs @@ -0,0 +1,59 @@ +// 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.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class ConfigureBuilder + { + public ConfigureBuilder(MethodInfo configure) + { + MethodInfo = configure; + } + + public MethodInfo MethodInfo { get; } + + public Action Build(object instance) => builder => Invoke(instance, builder); + + private void Invoke(object instance, IApplicationBuilder builder) + { + // Create a scope for Configure, this allows creating scoped dependencies + // without the hassle of manually creating a scope. + using (var scope = builder.ApplicationServices.CreateScope()) + { + var serviceProvider = scope.ServiceProvider; + var parameterInfos = MethodInfo.GetParameters(); + var parameters = new object[parameterInfos.Length]; + for (var index = 0; index < parameterInfos.Length; index++) + { + var parameterInfo = parameterInfos[index]; + if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) + { + parameters[index] = builder; + } + else + { + try + { + parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); + } + catch (Exception ex) + { + throw new Exception(string.Format( + "Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.", + parameterInfo.ParameterType.FullName, + parameterInfo.Name, + MethodInfo.Name, + MethodInfo.DeclaringType.FullName), ex); + } + } + } + MethodInfo.Invoke(instance, parameters); + } + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs new file mode 100644 index 0000000000..ed8d0fd06e --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs @@ -0,0 +1,52 @@ +// 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.Reflection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class ConfigureContainerBuilder + { + public ConfigureContainerBuilder(MethodInfo configureContainerMethod) + { + MethodInfo = configureContainerMethod; + } + + public MethodInfo MethodInfo { get; } + + public Func, Action> ConfigureContainerFilters { get; set; } + + public Action Build(object instance) => container => Invoke(instance, container); + + public Type GetContainerType() + { + var parameters = MethodInfo.GetParameters(); + if (parameters.Length != 1) + { + // REVIEW: This might be a breaking change + throw new InvalidOperationException($"The {MethodInfo.Name} method must take only one parameter."); + } + return parameters[0].ParameterType; + } + + private void Invoke(object instance, object container) + { + ConfigureContainerFilters(StartupConfigureContainer)(container); + + void StartupConfigureContainer(object containerBuilder) => InvokeCore(instance, containerBuilder); + } + + private void InvokeCore(object instance, object container) + { + if (MethodInfo == null) + { + return; + } + + var arguments = new object[1] { container }; + + MethodInfo.Invoke(instance, arguments); + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs new file mode 100644 index 0000000000..4206d0d62a --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs @@ -0,0 +1,56 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class ConfigureServicesBuilder + { + public ConfigureServicesBuilder(MethodInfo configureServices) + { + MethodInfo = configureServices; + } + + public MethodInfo MethodInfo { get; } + + public Func, Func> StartupServiceFilters { get; set; } + + public Func Build(object instance) => services => Invoke(instance, services); + + private IServiceProvider Invoke(object instance, IServiceCollection services) + { + return StartupServiceFilters(Startup)(services); + + IServiceProvider Startup(IServiceCollection serviceCollection) => InvokeCore(instance, serviceCollection); + } + + private IServiceProvider InvokeCore(object instance, IServiceCollection services) + { + if (MethodInfo == null) + { + return null; + } + + // Only support IServiceCollection parameters + var parameters = MethodInfo.GetParameters(); + if (parameters.Length > 1 || + parameters.Any(p => p.ParameterType != typeof(IServiceCollection))) + { + throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection."); + } + + var arguments = new object[MethodInfo.GetParameters().Length]; + + if (parameters.Length > 0) + { + arguments[0] = services; + } + + return MethodInfo.Invoke(instance, arguments) as IServiceProvider; + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs b/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs new file mode 100644 index 0000000000..ee6fbcfad8 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs @@ -0,0 +1,76 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class HostedServiceExecutor + { + private readonly IEnumerable _services; + private readonly ILogger _logger; + + public HostedServiceExecutor(ILogger logger, IEnumerable services) + { + _logger = logger; + _services = services; + } + + public async Task StartAsync(CancellationToken token) + { + try + { + await ExecuteAsync(service => service.StartAsync(token)); + } + catch (Exception ex) + { + _logger.ApplicationError(LoggerEventIds.HostedServiceStartException, "An error occurred starting the application", ex); + } + } + + public async Task StopAsync(CancellationToken token) + { + try + { + await ExecuteAsync(service => service.StopAsync(token)); + } + catch (Exception ex) + { + _logger.ApplicationError(LoggerEventIds.HostedServiceStopException, "An error occurred stopping the application", ex); + } + } + + private async Task ExecuteAsync(Func callback) + { + List exceptions = null; + + foreach (var service in _services) + { + try + { + await callback(service); + } + catch (Exception ex) + { + if (exceptions == null) + { + exceptions = new List(); + } + + exceptions.Add(ex); + } + } + + // Throw an aggregate exception if there were any exceptions + if (exceptions != null) + { + throw new AggregateException(exceptions); + } + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs new file mode 100644 index 0000000000..cd3e2d9fb2 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingApplication.cs @@ -0,0 +1,67 @@ +// 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.Hosting.Server; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class HostingApplication : IHttpApplication + { + private readonly RequestDelegate _application; + private readonly IHttpContextFactory _httpContextFactory; + private HostingApplicationDiagnostics _diagnostics; + + public HostingApplication( + RequestDelegate application, + ILogger logger, + DiagnosticListener diagnosticSource, + IHttpContextFactory httpContextFactory) + { + _application = application; + _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource); + _httpContextFactory = httpContextFactory; + } + + // Set up the request + public Context CreateContext(IFeatureCollection contextFeatures) + { + var context = new Context(); + var httpContext = _httpContextFactory.Create(contextFeatures); + + _diagnostics.BeginRequest(httpContext, ref context); + + context.HttpContext = httpContext; + return context; + } + + // Execute the request + public Task ProcessRequestAsync(Context context) + { + return _application(context.HttpContext); + } + + // Clean up the request + public void DisposeContext(Context context, Exception exception) + { + var httpContext = context.HttpContext; + _diagnostics.RequestEnd(httpContext, exception, context); + _httpContextFactory.Dispose(httpContext); + _diagnostics.ContextDisposed(context); + } + + public struct Context + { + public HttpContext HttpContext { get; set; } + public IDisposable Scope { get; set; } + public long StartTimestamp { get; set; } + public bool EventLogEnabled { get; set; } + public Activity Activity { get; set; } + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs new file mode 100644 index 0000000000..d485b1a060 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -0,0 +1,283 @@ +// 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.Runtime.CompilerServices; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class HostingApplicationDiagnostics + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private const string ActivityName = "Microsoft.AspNetCore.Hosting.HttpRequestIn"; + private const string ActivityStartKey = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start"; + + private const string DeprecatedDiagnosticsBeginRequestKey = "Microsoft.AspNetCore.Hosting.BeginRequest"; + private const string DeprecatedDiagnosticsEndRequestKey = "Microsoft.AspNetCore.Hosting.EndRequest"; + private const string DiagnosticsUnhandledExceptionKey = "Microsoft.AspNetCore.Hosting.UnhandledException"; + + private const string RequestIdHeaderName = "Request-Id"; + private const string CorrelationContextHeaderName = "Correlation-Context"; + + private readonly DiagnosticListener _diagnosticListener; + private readonly ILogger _logger; + + public HostingApplicationDiagnostics(ILogger logger, DiagnosticListener diagnosticListener) + { + _logger = logger; + _diagnosticListener = diagnosticListener; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BeginRequest(HttpContext httpContext, ref HostingApplication.Context context) + { + long startTimestamp = 0; + + if (HostingEventSource.Log.IsEnabled()) + { + context.EventLogEnabled = true; + // To keep the hot path short we defer logging in this function to non-inlines + RecordRequestStartEventLog(httpContext); + } + + var diagnosticListenerEnabled = _diagnosticListener.IsEnabled(); + var loggingEnabled = _logger.IsEnabled(LogLevel.Critical); + + // If logging is enabled or the diagnostic listener is enabled, try to get the correlation + // id from the header + StringValues correlationId; + if (diagnosticListenerEnabled || loggingEnabled) + { + httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out correlationId); + } + + if (diagnosticListenerEnabled) + { + if (_diagnosticListener.IsEnabled(ActivityName, httpContext)) + { + context.Activity = StartActivity(httpContext, correlationId); + } + if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey)) + { + startTimestamp = Stopwatch.GetTimestamp(); + RecordBeginRequestDiagnostics(httpContext, startTimestamp); + } + } + + // To avoid allocation, return a null scope if the logger is not on at least to some degree. + if (loggingEnabled) + { + // Scope may be relevant for a different level of logging, so we always create it + // see: https://github.com/aspnet/Hosting/pull/944 + // Scope can be null if logging is not on. + context.Scope = _logger.RequestScope(httpContext, correlationId); + + if (_logger.IsEnabled(LogLevel.Information)) + { + if (startTimestamp == 0) + { + startTimestamp = Stopwatch.GetTimestamp(); + } + + // Non-inline + LogRequestStarting(httpContext); + } + } + + context.StartTimestamp = startTimestamp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RequestEnd(HttpContext httpContext, Exception exception, HostingApplication.Context context) + { + // Local cache items resolved multiple items, in order of use so they are primed in cpu pipeline when used + var startTimestamp = context.StartTimestamp; + long currentTimestamp = 0; + + // If startTimestamp was 0, then Information logging wasn't enabled at for this request (and calcuated time will be wildly wrong) + // Is used as proxy to reduce calls to virtual: _logger.IsEnabled(LogLevel.Information) + if (startTimestamp != 0) + { + currentTimestamp = Stopwatch.GetTimestamp(); + // Non-inline + LogRequestFinished(httpContext, startTimestamp, currentTimestamp); + } + + if (_diagnosticListener.IsEnabled()) + { + if (currentTimestamp == 0) + { + currentTimestamp = Stopwatch.GetTimestamp(); + } + + if (exception == null) + { + // No exception was thrown, request was sucessful + if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsEndRequestKey)) + { + // Diagnostics is enabled for EndRequest, but it may not be for BeginRequest + // so call GetTimestamp if currentTimestamp is zero (from above) + RecordEndRequestDiagnostics(httpContext, currentTimestamp); + } + } + else + { + // Exception was thrown from request + if (_diagnosticListener.IsEnabled(DiagnosticsUnhandledExceptionKey)) + { + // Diagnostics is enabled for UnhandledException, but it may not be for BeginRequest + // so call GetTimestamp if currentTimestamp is zero (from above) + RecordUnhandledExceptionDiagnostics(httpContext, currentTimestamp, exception); + } + + } + + var activity = context.Activity; + // Always stop activity if it was started + if (activity != null) + { + StopActivity(httpContext, activity); + } + } + + if (context.EventLogEnabled && exception != null) + { + // Non-inline + HostingEventSource.Log.UnhandledException(); + } + + // Logging Scope is finshed with + context.Scope?.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ContextDisposed(HostingApplication.Context context) + { + if (context.EventLogEnabled) + { + // Non-inline + HostingEventSource.Log.RequestStop(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void LogRequestStarting(HttpContext httpContext) + { + // IsEnabled is checked in the caller, so if we are here just log + _logger.Log( + logLevel: LogLevel.Information, + eventId: LoggerEventIds.RequestStarting, + state: new HostingRequestStartingLog(httpContext), + exception: null, + formatter: HostingRequestStartingLog.Callback); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void LogRequestFinished(HttpContext httpContext, long startTimestamp, long currentTimestamp) + { + // IsEnabled isn't checked in the caller, startTimestamp > 0 is used as a fast proxy check + // but that may be because diagnostics are enabled, which also uses startTimestamp, so check here + if (_logger.IsEnabled(LogLevel.Information)) + { + var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); + + _logger.Log( + logLevel: LogLevel.Information, + eventId: LoggerEventIds.RequestFinished, + state: new HostingRequestFinishedLog(httpContext, elapsed), + exception: null, + formatter: HostingRequestFinishedLog.Callback); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RecordBeginRequestDiagnostics(HttpContext httpContext, long startTimestamp) + { + _diagnosticListener.Write( + DeprecatedDiagnosticsBeginRequestKey, + new + { + httpContext = httpContext, + timestamp = startTimestamp + }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RecordEndRequestDiagnostics(HttpContext httpContext, long currentTimestamp) + { + _diagnosticListener.Write( + DeprecatedDiagnosticsEndRequestKey, + new + { + httpContext = httpContext, + timestamp = currentTimestamp + }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RecordUnhandledExceptionDiagnostics(HttpContext httpContext, long currentTimestamp, Exception exception) + { + _diagnosticListener.Write( + DiagnosticsUnhandledExceptionKey, + new + { + httpContext = httpContext, + timestamp = currentTimestamp, + exception = exception + }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void RecordRequestStartEventLog(HttpContext httpContext) + { + HostingEventSource.Log.RequestStart(httpContext.Request.Method, httpContext.Request.Path); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private Activity StartActivity(HttpContext httpContext, StringValues requestId) + { + var activity = new Activity(ActivityName); + if (!StringValues.IsNullOrEmpty(requestId)) + { + activity.SetParentId(requestId); + + // We expect baggage to be empty by default + // Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items) + string[] baggage = httpContext.Request.Headers.GetCommaSeparatedValues(CorrelationContextHeaderName); + if (baggage != StringValues.Empty) + { + foreach (var item in baggage) + { + if (NameValueHeaderValue.TryParse(item, out var baggageItem)) + { + activity.AddBaggage(baggageItem.Name.ToString(), baggageItem.Value.ToString()); + } + } + } + } + + if (_diagnosticListener.IsEnabled(ActivityStartKey)) + { + _diagnosticListener.StartActivity(activity, new { HttpContext = httpContext }); + } + else + { + activity.Start(); + } + + return activity; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void StopActivity(HttpContext httpContext, Activity activity) + { + _diagnosticListener.StopActivity(activity, new { HttpContext = httpContext }); + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs b/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs new file mode 100644 index 0000000000..1f8d1887d7 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class HostingEnvironment : IHostingEnvironment, Extensions.Hosting.IHostingEnvironment + { + public string EnvironmentName { get; set; } = Hosting.EnvironmentName.Production; + + public string ApplicationName { get; set; } + + public string WebRootPath { get; set; } + + public IFileProvider WebRootFileProvider { get; set; } + + public string ContentRootPath { get; set; } + + public IFileProvider ContentRootFileProvider { get; set; } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs new file mode 100644 index 0000000000..c12d0bcdd6 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs @@ -0,0 +1,65 @@ +// 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.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public static class HostingEnvironmentExtensions + { + public static void Initialize(this IHostingEnvironment hostingEnvironment, string contentRootPath, WebHostOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + if (string.IsNullOrEmpty(contentRootPath)) + { + throw new ArgumentException("A valid non-empty content root must be provided.", nameof(contentRootPath)); + } + if (!Directory.Exists(contentRootPath)) + { + throw new ArgumentException($"The content root '{contentRootPath}' does not exist.", nameof(contentRootPath)); + } + + hostingEnvironment.ApplicationName = options.ApplicationName; + hostingEnvironment.ContentRootPath = contentRootPath; + hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath); + + var webRoot = options.WebRoot; + if (webRoot == null) + { + // Default to /wwwroot if it exists. + var wwwroot = Path.Combine(hostingEnvironment.ContentRootPath, "wwwroot"); + if (Directory.Exists(wwwroot)) + { + hostingEnvironment.WebRootPath = wwwroot; + } + } + else + { + hostingEnvironment.WebRootPath = Path.Combine(hostingEnvironment.ContentRootPath, webRoot); + } + + if (!string.IsNullOrEmpty(hostingEnvironment.WebRootPath)) + { + hostingEnvironment.WebRootPath = Path.GetFullPath(hostingEnvironment.WebRootPath); + if (!Directory.Exists(hostingEnvironment.WebRootPath)) + { + Directory.CreateDirectory(hostingEnvironment.WebRootPath); + } + hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(hostingEnvironment.WebRootPath); + } + else + { + hostingEnvironment.WebRootFileProvider = new NullFileProvider(); + } + + hostingEnvironment.EnvironmentName = + options.Environment ?? + hostingEnvironment.EnvironmentName; + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/HostingEventSource.cs b/src/Hosting/Hosting/src/Internal/HostingEventSource.cs new file mode 100644 index 0000000000..199f8aae85 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingEventSource.cs @@ -0,0 +1,55 @@ +// 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.Diagnostics.Tracing; +using System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + [EventSource(Name = "Microsoft-AspNetCore-Hosting")] + public sealed class HostingEventSource : EventSource + { + public static readonly HostingEventSource Log = new HostingEventSource(); + + private HostingEventSource() { } + + // NOTE + // - The 'Start' and 'Stop' suffixes on the following event names have special meaning in EventSource. They + // enable creating 'activities'. + // For more information, take a look at the following blog post: + // https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/ + // - A stop event's event id must be next one after its start event. + + [Event(1, Level = EventLevel.Informational)] + public void HostStart() + { + WriteEvent(1); + } + + [Event(2, Level = EventLevel.Informational)] + public void HostStop() + { + WriteEvent(2); + } + + [Event(3, Level = EventLevel.Informational)] + public void RequestStart(string method, string path) + { + WriteEvent(3, method, path); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [Event(4, Level = EventLevel.Informational)] + public void RequestStop() + { + WriteEvent(4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [Event(5, Level = EventLevel.Error)] + public void UnhandledException() + { + WriteEvent(5); + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs new file mode 100644 index 0000000000..a0579880a0 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs @@ -0,0 +1,166 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal static class HostingLoggerExtensions + { + public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext, string correlationId) + { + return logger.BeginScope(new HostingLogScope(httpContext, correlationId)); + } + + public static void ApplicationError(this ILogger logger, Exception exception) + { + logger.ApplicationError( + eventId: LoggerEventIds.ApplicationStartupException, + message: "Application startup exception", + exception: exception); + } + + public static void HostingStartupAssemblyError(this ILogger logger, Exception exception) + { + logger.ApplicationError( + eventId: LoggerEventIds.HostingStartupAssemblyException, + message: "Hosting startup assembly exception", + exception: exception); + } + + public static void ApplicationError(this ILogger logger, EventId eventId, string message, Exception exception) + { + var reflectionTypeLoadException = exception as ReflectionTypeLoadException; + if (reflectionTypeLoadException != null) + { + foreach (var ex in reflectionTypeLoadException.LoaderExceptions) + { + message = message + Environment.NewLine + ex.Message; + } + } + + logger.LogCritical( + eventId: eventId, + message: message, + exception: exception); + } + + public static void Starting(this ILogger logger) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug( + eventId: LoggerEventIds.Starting, + message: "Hosting starting"); + } + } + + public static void Started(this ILogger logger) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug( + eventId: LoggerEventIds.Started, + message: "Hosting started"); + } + } + + public static void Shutdown(this ILogger logger) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug( + eventId: LoggerEventIds.Shutdown, + message: "Hosting shutdown"); + } + } + + public static void ServerShutdownException(this ILogger logger, Exception ex) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug( + eventId: LoggerEventIds.ServerShutdownException, + exception: ex, + message: "Server shutdown exception"); + } + } + + private class HostingLogScope : IReadOnlyList> + { + private readonly HttpContext _httpContext; + private readonly string _correlationId; + + private string _cachedToString; + + public int Count + { + get + { + return 3; + } + } + + public KeyValuePair this[int index] + { + get + { + if (index == 0) + { + return new KeyValuePair("RequestId", _httpContext.TraceIdentifier); + } + else if (index == 1) + { + return new KeyValuePair("RequestPath", _httpContext.Request.Path.ToString()); + } + else if (index == 2) + { + return new KeyValuePair("CorrelationId", _correlationId); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public HostingLogScope(HttpContext httpContext, string correlationId) + { + _httpContext = httpContext; + _correlationId = correlationId; + } + + public override string ToString() + { + if (_cachedToString == null) + { + _cachedToString = string.Format( + CultureInfo.InvariantCulture, + "RequestId:{0} RequestPath:{1}", + _httpContext.TraceIdentifier, + _httpContext.Request.Path); + } + + return _cachedToString; + } + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} + diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs new file mode 100644 index 0000000000..ab440481ba --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs @@ -0,0 +1,75 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class HostingRequestFinishedLog : IReadOnlyList> + { + internal static readonly Func Callback = (state, exception) => ((HostingRequestFinishedLog)state).ToString(); + + private readonly HttpContext _httpContext; + private readonly TimeSpan _elapsed; + + private string _cachedToString; + + public int Count => 3; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair("ElapsedMilliseconds", _elapsed.TotalMilliseconds); + case 1: + return new KeyValuePair("StatusCode", _httpContext.Response.StatusCode); + case 2: + return new KeyValuePair("ContentType", _httpContext.Response.ContentType); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public HostingRequestFinishedLog(HttpContext httpContext, TimeSpan elapsed) + { + _httpContext = httpContext; + _elapsed = elapsed; + } + + public override string ToString() + { + if (_cachedToString == null) + { + _cachedToString = string.Format( + CultureInfo.InvariantCulture, + "Request finished in {0}ms {1} {2}", + _elapsed.TotalMilliseconds, + _httpContext.Response.StatusCode, + _httpContext.Response.ContentType); + } + + return _cachedToString; + } + + public IEnumerator> GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs new file mode 100644 index 0000000000..7506028a3c --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs @@ -0,0 +1,91 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class HostingRequestStartingLog : IReadOnlyList> + { + internal static readonly Func Callback = (state, exception) => ((HostingRequestStartingLog)state).ToString(); + + private readonly HttpRequest _request; + + private string _cachedToString; + + public int Count => 9; + + public KeyValuePair this[int index] + { + get + { + switch (index) + { + case 0: + return new KeyValuePair("Protocol", _request.Protocol); + case 1: + return new KeyValuePair("Method", _request.Method); + case 2: + return new KeyValuePair("ContentType", _request.ContentType); + case 3: + return new KeyValuePair("ContentLength", _request.ContentLength); + case 4: + return new KeyValuePair("Scheme", _request.Scheme.ToString()); + case 5: + return new KeyValuePair("Host", _request.Host.ToString()); + case 6: + return new KeyValuePair("PathBase", _request.PathBase.ToString()); + case 7: + return new KeyValuePair("Path", _request.Path.ToString()); + case 8: + return new KeyValuePair("QueryString", _request.QueryString.ToString()); + default: + throw new IndexOutOfRangeException(nameof(index)); + } + } + } + + public HostingRequestStartingLog(HttpContext httpContext) + { + _request = httpContext.Request; + } + + public override string ToString() + { + if (_cachedToString == null) + { + _cachedToString = string.Format( + CultureInfo.InvariantCulture, + "Request starting {0} {1} {2}://{3}{4}{5}{6} {7} {8}", + _request.Protocol, + _request.Method, + _request.Scheme, + _request.Host.Value, + _request.PathBase.Value, + _request.Path.Value, + _request.QueryString.Value, + _request.ContentType, + _request.ContentLength); + } + + return _cachedToString; + } + + public IEnumerator> GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs b/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs new file mode 100644 index 0000000000..f7d8f61933 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs @@ -0,0 +1,21 @@ +// 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. + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal static class LoggerEventIds + { + public const int RequestStarting = 1; + public const int RequestFinished = 2; + public const int Starting = 3; + public const int Started = 4; + public const int Shutdown = 5; + public const int ApplicationStartupException = 6; + public const int ApplicationStoppingException = 7; + public const int ApplicationStoppedException = 8; + public const int HostedServiceStartException = 9; + public const int HostedServiceStopException = 10; + public const int HostingStartupAssemblyException = 11; + public const int ServerShutdownException = 12; + } +} diff --git a/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs b/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs new file mode 100644 index 0000000000..9fcb01aa12 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs @@ -0,0 +1,50 @@ +// 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/Internal/RequestServicesFeature.cs b/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs new file mode 100644 index 0000000000..a57df9bcbc --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class RequestServicesFeature : IServiceProvidersFeature, IDisposable + { + private readonly IServiceScopeFactory _scopeFactory; + private IServiceProvider _requestServices; + private IServiceScope _scope; + private bool _requestServicesSet; + private HttpContext _context; + + public RequestServicesFeature(HttpContext context, IServiceScopeFactory scopeFactory) + { + Debug.Assert(scopeFactory != null); + _context = context; + _scopeFactory = scopeFactory; + } + + public IServiceProvider RequestServices + { + get + { + if (!_requestServicesSet) + { + _context.Response.RegisterForDispose(this); + _scope = _scopeFactory.CreateScope(); + _requestServices = _scope.ServiceProvider; + _requestServicesSet = true; + } + return _requestServices; + } + + set + { + _requestServices = value; + _requestServicesSet = true; + } + } + + public void Dispose() + { + _scope?.Dispose(); + _scope = null; + _requestServices = null; + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs b/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..48b8758181 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +// 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 Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal static class ServiceCollectionExtensions + { + public static IServiceCollection Clone(this IServiceCollection serviceCollection) + { + IServiceCollection clone = new ServiceCollection(); + foreach (var service in serviceCollection) + { + clone.Add(service); + } + return clone; + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/StartupLoader.cs b/src/Hosting/Hosting/src/Internal/StartupLoader.cs new file mode 100644 index 0000000000..d7211d39d9 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/StartupLoader.cs @@ -0,0 +1,336 @@ +// 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.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class StartupLoader + { + // Creates an instance with the actions to run for configuring the application services and the + // request pipeline of the application. + // When using convention based startup, the process for initializing the services is as follows: + // The host looks for a method with the signature ConfigureServices( services). + // If it can't find one, it looks for a method with the signature ConfigureServices( services). + // When the configure services method is void returning, the host builds a services configuration function that runs all the + // instances registered on the host, along with the ConfigureServices method following a decorator pattern. + // Additionally to the ConfigureServices method, the Startup class can define a ConfigureContainer<TContainerBuilder>(TContainerBuilder builder) + // method that further configures services into the container. If the ConfigureContainer method is defined, the services configuration function + // creates a TContainerBuilder and runs all the + // instances registered on the host, along with the ConfigureContainer method following a decorator pattern. + // For example: + // StartupFilter1 + // StartupFilter2 + // ConfigureServices + // StartupFilter2 + // StartupFilter1 + // ConfigureContainerFilter1 + // ConfigureContainerFilter2 + // ConfigureContainer + // ConfigureContainerFilter2 + // ConfigureContainerFilter1 + // + // If the Startup class ConfigureServices returns an and there is at least an registered we + // throw as the filters can't be applied. + public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) + { + var configureMethod = FindConfigureDelegate(startupType, environmentName); + + var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); + var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); + + object instance = null; + if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) + { + instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); + } + + // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not + // going to be used for anything. + var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); + + var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance( + typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type), + hostingServiceProvider, + servicesMethod, + configureContainerMethod, + instance); + + return new StartupMethods(instance, configureMethod.Build(instance), builder.Build()); + } + + private abstract class ConfigureServicesDelegateBuilder + { + public abstract Func Build(); + } + + private class ConfigureServicesDelegateBuilder : ConfigureServicesDelegateBuilder + { + public ConfigureServicesDelegateBuilder( + IServiceProvider hostingServiceProvider, + ConfigureServicesBuilder configureServicesBuilder, + ConfigureContainerBuilder configureContainerBuilder, + object instance) + { + HostingServiceProvider = hostingServiceProvider; + ConfigureServicesBuilder = configureServicesBuilder; + ConfigureContainerBuilder = configureContainerBuilder; + Instance = instance; + } + + public IServiceProvider HostingServiceProvider { get; } + public ConfigureServicesBuilder ConfigureServicesBuilder { get; } + public ConfigureContainerBuilder ConfigureContainerBuilder { get; } + public object Instance { get; } + + public override Func Build() + { + ConfigureServicesBuilder.StartupServiceFilters = BuildStartupServicesFilterPipeline; + var configureServicesCallback = ConfigureServicesBuilder.Build(Instance); + + ConfigureContainerBuilder.ConfigureContainerFilters = ConfigureContainerPipeline; + var configureContainerCallback = ConfigureContainerBuilder.Build(Instance); + + return ConfigureServices(configureServicesCallback, configureContainerCallback); + + Action ConfigureContainerPipeline(Action action) + { + return Target; + + // The ConfigureContainer pipeline needs an Action as source, so we just adapt the + // signature with this function. + void Source(TContainerBuilder containerBuilder) => + action(containerBuilder); + + // The ConfigureContainerBuilder.ConfigureContainerFilters expects an Action as value, but our pipeline + // produces an Action given a source, so we wrap it on an Action that internally casts + // the object containerBuilder to TContainerBuilder to match the expected signature of our ConfigureContainer pipeline. + void Target(object containerBuilder) => + BuildStartupConfigureContainerFiltersPipeline(Source)((TContainerBuilder)containerBuilder); + } + } + + Func ConfigureServices( + Func configureServicesCallback, + Action configureContainerCallback) + { + return ConfigureServicesWithContainerConfiguration; + + IServiceProvider ConfigureServicesWithContainerConfiguration(IServiceCollection services) + { + // Call ConfigureServices, if that returned an IServiceProvider, we're done + IServiceProvider applicationServiceProvider = configureServicesCallback.Invoke(services); + + if (applicationServiceProvider != null) + { + return applicationServiceProvider; + } + + // If there's a ConfigureContainer method + if (ConfigureContainerBuilder.MethodInfo != null) + { + var serviceProviderFactory = HostingServiceProvider.GetRequiredService>(); + var builder = serviceProviderFactory.CreateBuilder(services); + configureContainerCallback(builder); + applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder); + } + else + { + // Get the default factory + var serviceProviderFactory = HostingServiceProvider.GetRequiredService>(); + var builder = serviceProviderFactory.CreateBuilder(services); + applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder); + } + + return applicationServiceProvider ?? services.BuildServiceProvider(); + } + } + + private Func BuildStartupServicesFilterPipeline(Func startup) + { + return RunPipeline; + + IServiceProvider RunPipeline(IServiceCollection services) + { + var filters = HostingServiceProvider.GetRequiredService>().Reverse().ToArray(); + + // If there are no filters just run startup (makes IServiceProvider ConfigureServices(IServiceCollection services) work. + if (filters.Length == 0) + { + return startup(services); + } + + Action pipeline = InvokeStartup; + for (int i = 0; i < filters.Length; i++) + { + pipeline = filters[i].ConfigureServices(pipeline); + } + + pipeline(services); + + // We return null so that the host here builds the container (same result as void ConfigureServices(IServiceCollection services); + return null; + + void InvokeStartup(IServiceCollection serviceCollection) + { + var result = startup(serviceCollection); + if (filters.Length > 0 && result != null) + { + // public IServiceProvider ConfigureServices(IServiceCollection serviceCollection) is not compatible with IStartupServicesFilter; + var message = $"A ConfigureServices method that returns an {nameof(IServiceProvider)} is " + + $"not compatible with the use of one or more {nameof(IStartupConfigureServicesFilter)}. " + + $"Use a void returning ConfigureServices method instead or a ConfigureContainer method."; + throw new InvalidOperationException(message); + }; + } + } + } + + private Action BuildStartupConfigureContainerFiltersPipeline(Action configureContainer) + { + return RunPipeline; + + void RunPipeline(TContainerBuilder containerBuilder) + { + var filters = HostingServiceProvider + .GetRequiredService>>() + .Reverse() + .ToArray(); + + Action pipeline = InvokeConfigureContainer; + for (int i = 0; i < filters.Length; i++) + { + pipeline = filters[i].ConfigureContainer(pipeline); + } + + pipeline(containerBuilder); + + void InvokeConfigureContainer(TContainerBuilder builder) => configureContainer(builder); + } + } + } + + public static Type FindStartupType(string startupAssemblyName, string environmentName) + { + if (string.IsNullOrEmpty(startupAssemblyName)) + { + throw new ArgumentException( + string.Format("A startup method, startup type or startup assembly is required. If specifying an assembly, '{0}' cannot be null or empty.", + nameof(startupAssemblyName)), + nameof(startupAssemblyName)); + } + + var assembly = Assembly.Load(new AssemblyName(startupAssemblyName)); + if (assembly == null) + { + throw new InvalidOperationException(String.Format("The assembly '{0}' failed to load.", startupAssemblyName)); + } + + var startupNameWithEnv = "Startup" + environmentName; + var startupNameWithoutEnv = "Startup"; + + // Check the most likely places first + var type = + assembly.GetType(startupNameWithEnv) ?? + assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ?? + assembly.GetType(startupNameWithoutEnv) ?? + assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv); + + if (type == null) + { + // Full scan + var definedTypes = assembly.DefinedTypes.ToList(); + + var startupType1 = definedTypes.Where(info => info.Name.Equals(startupNameWithEnv, StringComparison.OrdinalIgnoreCase)); + var startupType2 = definedTypes.Where(info => info.Name.Equals(startupNameWithoutEnv, StringComparison.OrdinalIgnoreCase)); + + var typeInfo = startupType1.Concat(startupType2).FirstOrDefault(); + if (typeInfo != null) + { + type = typeInfo.AsType(); + } + } + + if (type == null) + { + throw new InvalidOperationException(String.Format("A type named '{0}' or '{1}' could not be found in assembly '{2}'.", + startupNameWithEnv, + startupNameWithoutEnv, + startupAssemblyName)); + } + + return type; + } + + private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) + { + var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); + return new ConfigureBuilder(configureMethod); + } + + private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) + { + var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); + return new ConfigureContainerBuilder(configureMethod); + } + + private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) + { + var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) + ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); + return new ConfigureServicesBuilder(servicesMethod); + } + + private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true) + { + var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName); + var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, ""); + + var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase)).ToList(); + if (selectedMethods.Count > 1) + { + throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithEnv)); + } + if (selectedMethods.Count == 0) + { + selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase)).ToList(); + if (selectedMethods.Count > 1) + { + throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithNoEnv)); + } + } + + var methodInfo = selectedMethods.FirstOrDefault(); + if (methodInfo == null) + { + if (required) + { + throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.", + methodNameWithEnv, + methodNameWithNoEnv, + startupType.FullName)); + + } + return null; + } + if (returnType != null && methodInfo.ReturnType != returnType) + { + if (required) + { + throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.", + methodInfo.Name, + startupType.FullName, + returnType.Name)); + } + return null; + } + return methodInfo; + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/StartupMethods.cs b/src/Hosting/Hosting/src/Internal/StartupMethods.cs new file mode 100644 index 0000000000..f854c85946 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/StartupMethods.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class StartupMethods + { + public StartupMethods(object instance, Action configure, Func configureServices) + { + Debug.Assert(configure != null); + Debug.Assert(configureServices != null); + + StartupInstance = instance; + ConfigureDelegate = configure; + ConfigureServicesDelegate = configureServices; + } + + public object StartupInstance { get; } + public Func ConfigureServicesDelegate { get; } + public Action ConfigureDelegate { get; } + + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs new file mode 100644 index 0000000000..3764427f63 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -0,0 +1,362 @@ +// 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.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Builder; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Hosting.Views; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.StackTrace.Sources; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class WebHost : IWebHost + { + private static readonly string DeprecatedServerUrlsKey = "server.urls"; + + private readonly IServiceCollection _applicationServiceCollection; + private IStartup _startup; + private ApplicationLifetime _applicationLifetime; + private HostedServiceExecutor _hostedServiceExecutor; + + private readonly IServiceProvider _hostingServiceProvider; + private readonly WebHostOptions _options; + private readonly IConfiguration _config; + private readonly AggregateException _hostingStartupErrors; + + private IServiceProvider _applicationServices; + private ExceptionDispatchInfo _applicationServicesException; + private ILogger _logger; + + private bool _stopped; + + // Used for testing only + internal WebHostOptions Options => _options; + + private IServer Server { get; set; } + + public WebHost( + IServiceCollection appServices, + IServiceProvider hostingServiceProvider, + WebHostOptions options, + IConfiguration config, + AggregateException hostingStartupErrors) + { + if (appServices == null) + { + throw new ArgumentNullException(nameof(appServices)); + } + + if (hostingServiceProvider == null) + { + throw new ArgumentNullException(nameof(hostingServiceProvider)); + } + + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + _config = config; + _hostingStartupErrors = hostingStartupErrors; + _options = options; + _applicationServiceCollection = appServices; + _hostingServiceProvider = hostingServiceProvider; + _applicationServiceCollection.AddSingleton(); + // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360 + _applicationServiceCollection.AddSingleton(sp => + { + return sp.GetRequiredService() as Extensions.Hosting.IApplicationLifetime; + }); + _applicationServiceCollection.AddSingleton(); + } + + public IServiceProvider Services + { + get + { + return _applicationServices; + } + } + + public IFeatureCollection ServerFeatures + { + get + { + EnsureServer(); + return Server?.Features; + } + } + + // Called immediately after the constructor so the properties can rely on it. + public void Initialize() + { + try + { + EnsureApplicationServices(); + } + catch (Exception ex) + { + // EnsureApplicationServices may have failed due to a missing or throwing Startup class. + if (_applicationServices == null) + { + _applicationServices = _applicationServiceCollection.BuildServiceProvider(); + } + + if (!_options.CaptureStartupErrors) + { + throw; + } + + _applicationServicesException = ExceptionDispatchInfo.Capture(ex); + } + } + + public void Start() + { + StartAsync().GetAwaiter().GetResult(); + } + + public virtual async Task StartAsync(CancellationToken cancellationToken = default) + { + HostingEventSource.Log.HostStart(); + _logger = _applicationServices.GetRequiredService>(); + _logger.Starting(); + + var application = BuildApplication(); + + _applicationLifetime = _applicationServices.GetRequiredService() as ApplicationLifetime; + _hostedServiceExecutor = _applicationServices.GetRequiredService(); + var diagnosticSource = _applicationServices.GetRequiredService(); + var httpContextFactory = _applicationServices.GetRequiredService(); + var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory); + await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); + + // Fire IApplicationLifetime.Started + _applicationLifetime?.NotifyStarted(); + + // Fire IHostedService.Start + await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); + + _logger.Started(); + + // Log the fact that we did load hosting startup assemblies. + if (_logger.IsEnabled(LogLevel.Debug)) + { + foreach (var assembly in _options.GetFinalHostingStartupAssemblies()) + { + _logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly); + } + } + + if (_hostingStartupErrors != null) + { + foreach (var exception in _hostingStartupErrors.InnerExceptions) + { + _logger.HostingStartupAssemblyError(exception); + } + } + } + + private void EnsureApplicationServices() + { + if (_applicationServices == null) + { + EnsureStartup(); + _applicationServices = _startup.ConfigureServices(_applicationServiceCollection); + } + } + + private void EnsureStartup() + { + if (_startup != null) + { + return; + } + + _startup = _hostingServiceProvider.GetService(); + + if (_startup == null) + { + throw new InvalidOperationException($"No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); + } + } + + private RequestDelegate BuildApplication() + { + try + { + _applicationServicesException?.Throw(); + EnsureServer(); + + var builderFactory = _applicationServices.GetRequiredService(); + var builder = builderFactory.CreateBuilder(Server.Features); + builder.ApplicationServices = _applicationServices; + + var startupFilters = _applicationServices.GetService>(); + Action configure = _startup.Configure; + foreach (var filter in startupFilters.Reverse()) + { + configure = filter.Configure(configure); + } + + configure(builder); + + return builder.Build(); + } + catch (Exception ex) + { + if (!_options.SuppressStatusMessages) + { + // Write errors to standard out so they can be retrieved when not in development mode. + Console.WriteLine("Application startup exception: " + ex.ToString()); + } + var logger = _applicationServices.GetRequiredService>(); + logger.ApplicationError(ex); + + if (!_options.CaptureStartupErrors) + { + throw; + } + + EnsureServer(); + + // Generate an HTML error page. + var hostingEnv = _applicationServices.GetRequiredService(); + var showDetailedErrors = hostingEnv.IsDevelopment() || _options.DetailedErrors; + + var model = new ErrorPageModel + { + RuntimeDisplayName = RuntimeInformation.FrameworkDescription + }; + var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly; + var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString(); + var clrVersion = assemblyVersion; + model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString(); + var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly; + model.CurrentAssemblyVesion = currentAssembly + .GetCustomAttribute() + .InformationalVersion; + model.ClrVersion = clrVersion; + model.OperatingSystemDescription = RuntimeInformation.OSDescription; + + if (showDetailedErrors) + { + var exceptionDetailProvider = new ExceptionDetailsProvider( + hostingEnv.ContentRootFileProvider, + sourceCodeLineCount: 6); + + model.ErrorDetails = exceptionDetailProvider.GetDetails(ex); + } + else + { + model.ErrorDetails = new ExceptionDetails[0]; + } + + var errorPage = new ErrorPage(model); + return context => + { + context.Response.StatusCode = 500; + context.Response.Headers["Cache-Control"] = "no-cache"; + return errorPage.ExecuteAsync(context); + }; + } + } + + private void EnsureServer() + { + if (Server == null) + { + Server = _applicationServices.GetRequiredService(); + + var serverAddressesFeature = Server.Features?.Get(); + var addresses = serverAddressesFeature?.Addresses; + if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0) + { + var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey]; + if (!string.IsNullOrEmpty(urls)) + { + serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey); + + foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + addresses.Add(value); + } + } + } + } + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + if (_stopped) + { + return; + } + _stopped = true; + + _logger?.Shutdown(); + + var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token; + if (!cancellationToken.CanBeCanceled) + { + cancellationToken = timeoutToken; + } + else + { + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token; + } + + // Fire IApplicationLifetime.Stopping + _applicationLifetime?.StopApplication(); + + if (Server != null) + { + await Server.StopAsync(cancellationToken).ConfigureAwait(false); + } + + // Fire the IHostedService.Stop + if (_hostedServiceExecutor != null) + { + await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false); + } + + // Fire IApplicationLifetime.Stopped + _applicationLifetime?.NotifyStopped(); + + HostingEventSource.Log.HostStop(); + } + + public void Dispose() + { + if (!_stopped) + { + try + { + StopAsync().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + _logger?.ServerShutdownException(ex); + } + } + + (_applicationServices as IDisposable)?.Dispose(); + (_hostingServiceProvider as IDisposable)?.Dispose(); + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/WebHostOptions.cs b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs new file mode 100644 index 0000000000..e9e611bc69 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs @@ -0,0 +1,96 @@ +// 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.Globalization; +using System.Linq; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class WebHostOptions + { + public WebHostOptions() { } + + public WebHostOptions(IConfiguration configuration) + : this(configuration, string.Empty) { } + + public WebHostOptions(IConfiguration configuration, string applicationNameFallback) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + ApplicationName = configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback; + StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey]; + DetailedErrors = WebHostUtilities.ParseBool(configuration, WebHostDefaults.DetailedErrorsKey); + CaptureStartupErrors = WebHostUtilities.ParseBool(configuration, WebHostDefaults.CaptureStartupErrorsKey); + Environment = configuration[WebHostDefaults.EnvironmentKey]; + WebRoot = configuration[WebHostDefaults.WebRootKey]; + ContentRootPath = configuration[WebHostDefaults.ContentRootKey]; + PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey); + SuppressStatusMessages = WebHostUtilities.ParseBool(configuration, WebHostDefaults.SuppressStatusMessagesKey); + + // Search the primary assembly and configured assemblies. + HostingStartupAssemblies = Split($"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}"); + HostingStartupExcludeAssemblies = Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]); + + var timeout = configuration[WebHostDefaults.ShutdownTimeoutKey]; + if (!string.IsNullOrEmpty(timeout) + && int.TryParse(timeout, NumberStyles.None, CultureInfo.InvariantCulture, out var seconds)) + { + ShutdownTimeout = TimeSpan.FromSeconds(seconds); + } + } + + public string ApplicationName { get; set; } + + public bool PreventHostingStartup { get; set; } + + public bool SuppressStatusMessages { get; set; } + + public IReadOnlyList HostingStartupAssemblies { get; set; } + + public IReadOnlyList HostingStartupExcludeAssemblies { get; set; } + + public bool DetailedErrors { get; set; } + + public bool CaptureStartupErrors { get; set; } + + public string Environment { get; set; } + + public string StartupAssembly { get; set; } + + public string WebRoot { get; set; } + + public string ContentRootPath { get; set; } + + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); + + public IEnumerable GetFinalHostingStartupAssemblies() + { + return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase); + } + + private IReadOnlyList Split(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return Array.Empty(); + } + + var list = new List(); + foreach (var part in value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + var trimmedPart = part; + if (!string.IsNullOrEmpty(trimmedPart)) + { + list.Add(trimmedPart); + } + } + return list; + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs b/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs new file mode 100644 index 0000000000..49635699d1 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs @@ -0,0 +1,17 @@ +// 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.Extensions.Configuration; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class WebHostUtilities + { + public static bool ParseBool(IConfiguration configuration, string key) + { + return string.Equals("true", configuration[key], StringComparison.OrdinalIgnoreCase) + || string.Equals("1", configuration[key], StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj new file mode 100644 index 0000000000..627b36bbc2 --- /dev/null +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -0,0 +1,30 @@ + + + + ASP.NET Core hosting infrastructure and startup logic for web applications. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;hosting + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs b/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..39703dc79d --- /dev/null +++ b/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Hosting.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Hosting/Hosting/src/Properties/Resources.Designer.cs b/src/Hosting/Hosting/src/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..088072729c --- /dev/null +++ b/src/Hosting/Hosting/src/Properties/Resources.Designer.cs @@ -0,0 +1,94 @@ +// +namespace Microsoft.AspNetCore.Hosting +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Hosting.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Internal Server Error + /// + internal static string ErrorPageHtml_Title + { + get { return GetString("ErrorPageHtml_Title"); } + } + + /// + /// Internal Server Error + /// + internal static string FormatErrorPageHtml_Title() + { + return GetString("ErrorPageHtml_Title"); + } + + /// + /// An error occurred while starting the application. + /// + internal static string ErrorPageHtml_UnhandledException + { + get { return GetString("ErrorPageHtml_UnhandledException"); } + } + + /// + /// An error occurred while starting the application. + /// + internal static string FormatErrorPageHtml_UnhandledException() + { + return GetString("ErrorPageHtml_UnhandledException"); + } + + /// + /// Unknown location + /// + internal static string ErrorPageHtml_UnknownLocation + { + get { return GetString("ErrorPageHtml_UnknownLocation"); } + } + + /// + /// Unknown location + /// + internal static string FormatErrorPageHtml_UnknownLocation() + { + return GetString("ErrorPageHtml_UnknownLocation"); + } + + /// + /// WebHostBuilder allows creation only of a single instance of WebHost + /// + internal static string WebHostBuilder_SingleInstance + { + get { return GetString("WebHostBuilder_SingleInstance"); } + } + + /// + /// WebHostBuilder allows creation only of a single instance of WebHost + /// + internal static string FormatWebHostBuilder_SingleInstance() + { + return GetString("WebHostBuilder_SingleInstance"); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Hosting/Hosting/src/Resources.resx b/src/Hosting/Hosting/src/Resources.resx new file mode 100644 index 0000000000..64d6fe523d --- /dev/null +++ b/src/Hosting/Hosting/src/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Internal Server Error + + + An error occurred while starting the application. + + + Unknown location + + + WebHostBuilder allows creation only of a single instance of WebHost + + \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs b/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs new file mode 100644 index 0000000000..098ec8cdb0 --- /dev/null +++ b/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs @@ -0,0 +1,14 @@ +// 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.Collections.Generic; + +namespace Microsoft.AspNetCore.Hosting.Server.Features +{ + public class ServerAddressesFeature : IServerAddressesFeature + { + public ICollection Addresses { get; } = new List(); + + public bool PreferHostingUrls { get; set; } + } +} diff --git a/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs b/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs new file mode 100644 index 0000000000..b31f9478d1 --- /dev/null +++ b/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs @@ -0,0 +1,56 @@ +// 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.Reflection; +using System.Runtime.ExceptionServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public class ConventionBasedStartup : IStartup + { + private readonly StartupMethods _methods; + + public ConventionBasedStartup(StartupMethods methods) + { + _methods = methods; + } + + public void Configure(IApplicationBuilder app) + { + try + { + _methods.ConfigureDelegate(app); + } + catch (Exception ex) + { + if (ex is TargetInvocationException) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + } + + throw; + } + } + + public IServiceProvider ConfigureServices(IServiceCollection services) + { + try + { + return _methods.ConfigureServicesDelegate(services); + } + catch (Exception ex) + { + if (ex is TargetInvocationException) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + } + + throw; + } + } + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Startup/DelegateStartup.cs b/src/Hosting/Hosting/src/Startup/DelegateStartup.cs new file mode 100644 index 0000000000..d354ad946e --- /dev/null +++ b/src/Hosting/Hosting/src/Startup/DelegateStartup.cs @@ -0,0 +1,21 @@ +// 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; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public class DelegateStartup : StartupBase + { + private Action _configureApp; + + public DelegateStartup(IServiceProviderFactory factory, Action configureApp) : base(factory) + { + _configureApp = configureApp; + } + + public override void Configure(IApplicationBuilder app) => _configureApp(app); + } +} \ No newline at end of file diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs new file mode 100644 index 0000000000..52a6db45d3 --- /dev/null +++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs @@ -0,0 +1,1055 @@ +namespace Microsoft.AspNetCore.Hosting.Views +{ +#line 1 "ErrorPage.cshtml" +using System + +#line default +#line hidden + ; +#line 2 "ErrorPage.cshtml" +using System.Globalization + +#line default +#line hidden + ; +#line 3 "ErrorPage.cshtml" +using System.Linq + +#line default +#line hidden + ; +#line 4 "ErrorPage.cshtml" +using System.Net + +#line default +#line hidden + ; +#line 5 "ErrorPage.cshtml" +using System.Reflection + +#line default +#line hidden + ; +#line 6 "ErrorPage.cshtml" +using Microsoft.AspNetCore.Hosting.Views + +#line default +#line hidden + ; + using System.Threading.Tasks; + + internal class ErrorPage : Microsoft.Extensions.RazorViews.BaseView + { +#line 9 "ErrorPage.cshtml" + + public ErrorPage(ErrorPageModel model) + { + Model = model; + } + + public ErrorPageModel Model { get; set; } + +#line default +#line hidden + #line hidden + public ErrorPage() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("\r\n"); +#line 17 "ErrorPage.cshtml" + + Response.ContentType = "text/html; charset=utf-8"; + var location = string.Empty; + +#line default +#line hidden + + WriteLiteral("\r\n