// 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.Threading; using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Server.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.AspNet.Hosting.Internal { public class WebApplication : IWebApplication { // This is defined by IIS's HttpPlatformHandler. private static readonly string ServerPort = "HTTP_PLATFORM_PORT"; private readonly IServiceCollection _applicationServiceCollection; private readonly IStartupLoader _startupLoader; private readonly ApplicationLifetime _applicationLifetime; private readonly WebApplicationOptions _options; private readonly IConfiguration _config; private IServiceProvider _applicationServices; private RequestDelegate _application; private ILogger _logger; // Only one of these should be set internal string StartupAssemblyName { get; set; } internal StartupMethods Startup { get; set; } internal Type StartupType { get; set; } // Only one of these should be set internal IServerFactory ServerFactory { get; set; } internal string ServerFactoryLocation { get; set; } private IServer Server { get; set; } public WebApplication( IServiceCollection appServices, IStartupLoader startupLoader, WebApplicationOptions options, IConfiguration config) { if (appServices == null) { throw new ArgumentNullException(nameof(appServices)); } if (startupLoader == null) { throw new ArgumentNullException(nameof(startupLoader)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } _config = config; _options = options; _applicationServiceCollection = appServices; _startupLoader = startupLoader; _applicationLifetime = new ApplicationLifetime(); _applicationServiceCollection.AddSingleton(_applicationLifetime); } public IServiceProvider Services { get { EnsureApplicationServices(); return _applicationServices; } } public IFeatureCollection ServerFeatures { get { return Server?.Features; } } public void Initialize() { if (_application == null) { _application = BuildApplication(); } } public virtual void Start() { Initialize(); _logger = _applicationServices.GetRequiredService>(); var diagnosticSource = _applicationServices.GetRequiredService(); var httpContextFactory = _applicationServices.GetRequiredService(); _logger.Starting(); Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory)); _applicationLifetime.NotifyStarted(); _logger.Started(); } private void EnsureApplicationServices() { if (_applicationServices == null) { EnsureStartup(); _applicationServices = Startup.ConfigureServicesDelegate(_applicationServiceCollection); } } private void EnsureStartup() { if (Startup != null) { return; } if (StartupType == null) { var diagnosticTypeMessages = new List(); StartupType = _startupLoader.FindStartupType(StartupAssemblyName, diagnosticTypeMessages); if (StartupType == null) { throw new ArgumentException( diagnosticTypeMessages.Aggregate("Failed to find a startup type for the web application.", (a, b) => a + "\r\n" + b), StartupAssemblyName); } } var diagnosticMessages = new List(); Startup = _startupLoader.LoadMethods(StartupType, diagnosticMessages); if (Startup == null) { throw new ArgumentException( diagnosticMessages.Aggregate("Failed to find a startup entry point for the web application.", (a, b) => a + "\r\n" + b), StartupAssemblyName); } } private RequestDelegate BuildApplication() { try { EnsureApplicationServices(); EnsureServer(); var builderFactory = _applicationServices.GetRequiredService(); var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService>(); var configure = Startup.ConfigureDelegate; foreach (var filter in startupFilters) { configure = filter.Configure(configure); } configure(builder); return builder.Build(); } catch (Exception ex) when (_options.CaptureStartupErrors) { // EnsureApplicationServices may have failed due to a missing or throwing Startup class. if (_applicationServices == null) { _applicationServices = _applicationServiceCollection.BuildServiceProvider(); } EnsureServer(); // Write errors to standard out so they can be retrieved when not in development mode. Console.Out.WriteLine("Application startup exception: " + ex.ToString()); var logger = _applicationServices.GetRequiredService>(); logger.ApplicationError(ex); // Generate an HTML error page. var runtimeEnv = _applicationServices.GetRequiredService(); var hostingEnv = _applicationServices.GetRequiredService(); var showDetailedErrors = hostingEnv.IsDevelopment() || _options.DetailedErrors; var errorBytes = StartupExceptionPage.GenerateErrorHtml(showDetailedErrors, runtimeEnv, ex); return context => { context.Response.StatusCode = 500; context.Response.Headers["Cache-Control"] = "private, max-age=0"; context.Response.ContentType = "text/html; charset=utf-8"; context.Response.ContentLength = errorBytes.Length; return context.Response.Body.WriteAsync(errorBytes, 0, errorBytes.Length); }; } } private void EnsureServer() { if (Server == null) { if (ServerFactory == null) { // Blow up if we don't have a server set at this point if (ServerFactoryLocation == null) { throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()"); } ServerFactory = _applicationServices.GetRequiredService().LoadServerFactory(ServerFactoryLocation); } Server = ServerFactory.CreateServer(_config); var addresses = Server.Features?.Get()?.Addresses; if (addresses != null && !addresses.IsReadOnly) { var port = _config[ServerPort]; if (!string.IsNullOrEmpty(port)) { addresses.Add("http://localhost:" + port); } // Provide a default address if there aren't any configured. if (addresses.Count == 0) { addresses.Add("http://localhost:5000"); } } } } public void Dispose() { _logger?.Shutdown(); _applicationLifetime.StopApplication(); Server?.Dispose(); _applicationLifetime.NotifyStopped(); (_applicationServices as IDisposable)?.Dispose(); } private class Disposable : IDisposable { private Action _dispose; public Disposable(Action dispose) { _dispose = dispose; } public void Dispose() { Interlocked.Exchange(ref _dispose, () => { }).Invoke(); } } } }