| File: WebApplication.cs | Web Access |
| Project: src\aspnetcore\src\DefaultBuilder\src\Microsoft.AspNetCore.csproj (Microsoft.AspNetCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Builder; /// <summary> /// The web application used to configure the HTTP pipeline, and routes. /// </summary> [DebuggerDisplay("{DebuggerToString(),nq}")] [DebuggerTypeProxy(typeof(WebApplicationDebugView))] public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable { internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder"; private readonly IHost _host; private readonly List<EndpointDataSource> _dataSources = new(); internal WebApplication(IHost host) { _host = host; ApplicationBuilder = new ApplicationBuilder(host.Services, ServerFeatures); Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName ?? nameof(WebApplication)); Properties[GlobalEndpointRouteBuilderKey] = this; } /// <summary> /// The application's configured services. /// </summary> public IServiceProvider Services => _host.Services; /// <summary> /// The application's configured <see cref="IConfiguration"/>. /// </summary> public IConfiguration Configuration => _host.Services.GetRequiredService<IConfiguration>(); /// <summary> /// The application's configured <see cref="IWebHostEnvironment"/>. /// </summary> public IWebHostEnvironment Environment => _host.Services.GetRequiredService<IWebHostEnvironment>(); /// <summary> /// Allows consumers to be notified of application lifetime events. /// </summary> public IHostApplicationLifetime Lifetime => _host.Services.GetRequiredService<IHostApplicationLifetime>(); /// <summary> /// The default logger for the application. /// </summary> public ILogger Logger { get; } /// <summary> /// The list of URLs that the HTTP server is bound to. /// </summary> public ICollection<string> Urls => ServerFeatures.GetRequiredFeature<IServerAddressesFeature>().Addresses; IServiceProvider IApplicationBuilder.ApplicationServices { get => ApplicationBuilder.ApplicationServices; set => ApplicationBuilder.ApplicationServices = value; } internal IFeatureCollection ServerFeatures => _host.Services.GetRequiredService<IServer>().Features; IFeatureCollection IApplicationBuilder.ServerFeatures => ServerFeatures; internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties; IDictionary<string, object?> IApplicationBuilder.Properties => Properties; internal ICollection<EndpointDataSource> DataSources => _dataSources; ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => DataSources; internal ApplicationBuilder ApplicationBuilder { get; } IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; /// <summary> /// Initializes a new instance of the <see cref="WebApplication"/> class with preconfigured defaults. /// </summary> /// <param name="args">The command line arguments.</param> /// <returns>The <see cref="WebApplication"/>.</returns> public static WebApplication Create(string[]? args = null) => new WebApplicationBuilder(new() { Args = args }).Build(); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. /// </summary> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateBuilder() => new(new()); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults. /// </summary> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateSlimBuilder() => new(new(), slim: true); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. /// </summary> /// <param name="args">The command line arguments.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateBuilder(string[] args) => new(new() { Args = args }); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults. /// </summary> /// <param name="args">The command line arguments.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateSlimBuilder(string[] args) => new(new() { Args = args }, slim: true); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. /// </summary> /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) => new(options); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults. /// </summary> /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) => new(options, slim: true); /// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with no defaults. /// </summary> /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateEmptyBuilder(WebApplicationOptions options) => new(options, slim: false, empty: true); /// <summary> /// Start the application. /// </summary> /// <param name="cancellationToken"></param> /// <returns> /// A <see cref="Task"/> that represents the startup of the <see cref="WebApplication"/>. /// Successful completion indicates the HTTP server is ready to accept new requests. /// </returns> public Task StartAsync(CancellationToken cancellationToken = default) => _host.StartAsync(cancellationToken); /// <summary> /// Shuts down the application. /// </summary> /// <param name="cancellationToken"></param> /// <returns> /// A <see cref="Task"/> that represents the shutdown of the <see cref="WebApplication"/>. /// Successful completion indicates that the HTTP server has stopped. /// </returns> public Task StopAsync(CancellationToken cancellationToken = default) => _host.StopAsync(cancellationToken); /// <summary> /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered. /// </summary> /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param> /// <returns> /// A <see cref="Task"/> that represents the entire runtime of the <see cref="WebApplication"/> from startup to shutdown. /// </returns> public Task RunAsync([StringSyntax(StringSyntaxAttribute.Uri)] string? url = null) { Listen(url); return HostingAbstractionsHostExtensions.RunAsync(this); } /// <summary> /// Runs an application and blocks the calling thread until host shutdown. /// </summary> /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param> public void Run([StringSyntax(StringSyntaxAttribute.Uri)] string? url = null) { Listen(url); HostingAbstractionsHostExtensions.Run(this); } /// <summary> /// Disposes the application. /// </summary> void IDisposable.Dispose() => _host.Dispose(); /// <summary> /// Disposes the application. /// </summary> public ValueTask DisposeAsync() => ((IAsyncDisposable)_host).DisposeAsync(); internal RequestDelegate BuildRequestDelegate() => ApplicationBuilder.Build(); RequestDelegate IApplicationBuilder.Build() => BuildRequestDelegate(); // REVIEW: Should this be wrapping another type? IApplicationBuilder IApplicationBuilder.New() { var newBuilder = ApplicationBuilder.New(); // Remove the route builder so branched pipelines have their own routing world newBuilder.Properties.Remove(GlobalEndpointRouteBuilderKey); return newBuilder; } /// <summary> /// Adds the middleware to the application request pipeline. /// </summary> /// <param name="middleware">The middleware.</param> /// <returns>An instance of <see cref="IApplicationBuilder"/> after the operation has completed.</returns> public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { ApplicationBuilder.Use(middleware); return this; } IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => ((IApplicationBuilder)this).New(); private void Listen(string? url) { if (url is null) { return; } var addresses = ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; if (addresses is null) { throw new InvalidOperationException($"Changing the URL is not supported because no valid {nameof(IServerAddressesFeature)} was found."); } if (addresses.IsReadOnly) { throw new InvalidOperationException($"Changing the URL is not supported because {nameof(IServerAddressesFeature.Addresses)} {nameof(ICollection<string>.IsReadOnly)}."); } addresses.Clear(); addresses.Add(url); } private string DebuggerToString() { return $@"ApplicationName = ""{Environment.ApplicationName}"", IsRunning = {(IsRunning ? "true" : "false")}"; } // Web app is running if the app has been started and hasn't been stopped. private bool IsRunning => Lifetime.ApplicationStarted.IsCancellationRequested && !Lifetime.ApplicationStopped.IsCancellationRequested; internal sealed class WebApplicationDebugView(WebApplication webApplication) { private readonly WebApplication _webApplication = webApplication; public IServiceProvider Services => _webApplication.Services; public IConfiguration Configuration => _webApplication.Configuration; public IWebHostEnvironment Environment => _webApplication.Environment; public IHostApplicationLifetime Lifetime => _webApplication.Lifetime; public ILogger Logger => _webApplication.Logger; public string Urls => string.Join(", ", _webApplication.Urls); public IReadOnlyList<Endpoint> Endpoints { get { var dataSource = _webApplication.Services.GetRequiredService<EndpointDataSource>(); if (dataSource is CompositeEndpointDataSource compositeEndpointDataSource) { // The web app's data sources aren't registered until the routing middleware is. That often happens when the app is run. // We want endpoints to be available in the debug view before the app starts. Test if all the web app's the data sources are registered. if (compositeEndpointDataSource.DataSources.Intersect(_webApplication.DataSources).Count() == _webApplication.DataSources.Count) { // Data sources are centrally registered. return dataSource.Endpoints; } else { // Fallback to just the web app's data sources to support debugging before the web app starts. return new CompositeEndpointDataSource(_webApplication.DataSources).Endpoints; } } return dataSource.Endpoints; } } public bool IsRunning => _webApplication.IsRunning; public IList<string>? Middleware { get { if (_webApplication.Properties.TryGetValue("__MiddlewareDescriptions", out var value) && value is IList<string> descriptions) { return descriptions; } throw new NotSupportedException("Unable to get configured middleware."); } } } }