| File: Builder\ApplicationBuilder.cs | Web Access |
| Project: src\aspnetcore\src\Http\Http\src\Microsoft.AspNetCore.Http.csproj (Microsoft.AspNetCore.Http) |
// 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 Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Builder; /// <summary> /// Default implementation for <see cref="IApplicationBuilder"/>. /// </summary> [DebuggerDisplay("Middleware = {MiddlewareCount}")] [DebuggerTypeProxy(typeof(ApplicationBuilderDebugView))] public partial class ApplicationBuilder : IApplicationBuilder { private const string ServerFeaturesKey = "server.Features"; private const string ApplicationServicesKey = "application.Services"; private const string MiddlewareDescriptionsKey = "__MiddlewareDescriptions"; private const string RequestUnhandledKey = "__RequestUnhandled"; private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new(); private readonly List<string>? _descriptions; private readonly IDebugger _debugger; /// <summary> /// Initializes a new instance of <see cref="ApplicationBuilder"/>. /// </summary> /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param> public ApplicationBuilder(IServiceProvider serviceProvider) : this(serviceProvider, new FeatureCollection()) { } private int MiddlewareCount => _components.Count; /// <summary> /// Initializes a new instance of <see cref="ApplicationBuilder"/>. /// </summary> /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param> /// <param name="server">The server instance that hosts the application.</param> public ApplicationBuilder(IServiceProvider serviceProvider, object server) { Properties = new Dictionary<string, object?>(StringComparer.Ordinal); ApplicationServices = serviceProvider; SetProperty(ServerFeaturesKey, server); // IDebugger service can optionally be added by tests to simulate the debugger being attached. _debugger = (IDebugger?)serviceProvider?.GetService(typeof(IDebugger)) ?? DebuggerWrapper.Instance; if (_debugger.IsAttached) { _descriptions = new(); // Add component descriptions collection to properties so debugging tools can display // a list of configured middleware for an application. SetProperty(MiddlewareDescriptionsKey, _descriptions); } } private ApplicationBuilder(ApplicationBuilder builder) { Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal); _debugger = builder._debugger; if (_debugger.IsAttached) { _descriptions = new(); } } /// <summary> /// Gets the <see cref="IServiceProvider"/> for application services. /// </summary> public IServiceProvider ApplicationServices { get { return GetProperty<IServiceProvider>(ApplicationServicesKey)!; } set { SetProperty<IServiceProvider>(ApplicationServicesKey, value); } } /// <summary> /// Gets the <see cref="IFeatureCollection"/> for server features. /// </summary> /// <remarks> /// An empty collection is returned if a server wasn't specified for the application builder. /// </remarks> public IFeatureCollection ServerFeatures { get { return GetProperty<IFeatureCollection>(ServerFeaturesKey)!; } } /// <summary> /// Gets a set of properties for <see cref="ApplicationBuilder"/>. /// </summary> public IDictionary<string, object?> Properties { get; } private T? GetProperty<T>(string key) { return Properties.TryGetValue(key, out var value) ? (T?)value : default(T); } private void SetProperty<T>(string key, T value) { Properties[key] = value; } /// <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) { _components.Add(middleware); _descriptions?.Add(CreateMiddlewareDescription(middleware)); return this; } private static string CreateMiddlewareDescription(Func<RequestDelegate, RequestDelegate> middleware) { if (middleware.Target != null) { // To IApplicationBuilder, middleware is just a func. Getting a good description is hard. // Inspect the incoming func and attempt to resolve it back to a middleware type if possible. // UseMiddlewareExtensions adds middleware via a method with the name CreateMiddleware. // If this pattern is matched, then ToString on the target returns the middleware type name. if (middleware.Method.Name == "CreateMiddleware") { return middleware.Target.ToString()!; } return middleware.Target.GetType().FullName + "." + middleware.Method.Name; } return middleware.Method.Name.ToString(); } /// <summary> /// Creates a copy of this application builder. /// <para> /// The created clone has the same properties as the current instance, but does not copy /// the request pipeline. /// </para> /// </summary> /// <returns>The cloned instance.</returns> public IApplicationBuilder New() { return new ApplicationBuilder(this); } /// <summary> /// Produces a <see cref="RequestDelegate"/> that executes added middlewares. /// </summary> /// <returns>The <see cref="RequestDelegate"/>.</returns> public RequestDelegate Build() { RequestDelegate app = context => { // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened. // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware. var endpoint = context.GetEndpoint(); var endpointRequestDelegate = endpoint?.RequestDelegate; if (endpointRequestDelegate != null) { var message = $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " + $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " + $"routing."; throw new InvalidOperationException(message); } // Flushing the response and calling through to the next middleware in the pipeline is // a user error, but don't attempt to set the status code if this happens. It leads to a confusing // behavior where the client response looks fine, but the server side logic results in an exception. if (!context.Response.HasStarted) { context.Response.StatusCode = StatusCodes.Status404NotFound; } // Communicates to higher layers that the request wasn't handled by the app pipeline. context.Items[RequestUnhandledKey] = true; return Task.CompletedTask; }; for (var c = _components.Count - 1; c >= 0; c--) { app = _components[c](app); } return app; } private sealed class ApplicationBuilderDebugView(ApplicationBuilder applicationBuilder) { private readonly ApplicationBuilder _applicationBuilder = applicationBuilder; public IServiceProvider ApplicationServices => _applicationBuilder.ApplicationServices; public IDictionary<string, object?> Properties => _applicationBuilder.Properties; public IFeatureCollection ServerFeatures => _applicationBuilder.ServerFeatures; public IList<string>? Middleware { get { if (_applicationBuilder.Properties.TryGetValue("__MiddlewareDescriptions", out var value) && value is IList<string> descriptions) { return descriptions; } throw new NotSupportedException("Unable to get configured middleware."); } } } }