// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Builder
{
publicclass ApplicationBuilder : IApplicationBuilder
{
privateconststring ServerFeaturesKey = "server.Features";
privateconststring ApplicationServicesKey = "application.Services";
privatereadonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
public ApplicationBuilder(IServiceProvider serviceProvider)
{
Properties = new Dictionary<string, object>(StringComparer.Ordinal);
ApplicationServices = serviceProvider;
}
public ApplicationBuilder(IServiceProvider serviceProvider, object server)
: this(serviceProvider)
{
SetProperty(ServerFeaturesKey, server);
}
private ApplicationBuilder(ApplicationBuilder builder)
{
Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);
}
public IServiceProvider ApplicationServices
{
get
{
return GetProperty<IServiceProvider>(ApplicationServicesKey);
}
set
{
SetProperty<IServiceProvider>(ApplicationServicesKey, value);
}
}
public IFeatureCollection ServerFeatures
{
get
{
return GetProperty<IFeatureCollection>(ServerFeaturesKey);
}
}
public IDictionary<string, object> Properties { get; }
private T GetProperty<T>(string key)
{
object value;
return Properties.TryGetValue(key, out value) ? (T)value : default(T);
}
privatevoid SetProperty<T>(string key, T value)
{
Properties[key] = value;
}
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
returnthis;
}
public IApplicationBuilder New()
{
returnnew ApplicationBuilder(this);
}
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.";
thrownew InvalidOperationException(message);
}
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
}
}
其中 RequestDelegate 委托的声明,如下:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http
{
///<summary>/// A function that can process an HTTP request.
///</summary>///<param name="context">The <see cref="HttpContext"/> for the request.</param>///<returns>A task that represents the completion of request processing.</returns>publicdelegate Task RequestDelegate(HttpContext context);
}
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.";
thrownew InvalidOperationException(message);
}
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
// 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.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder
{
///<summary>/// Extension methods for adding middleware.
///</summary>publicstaticclass UseExtensions
{
///<summary>/// Adds a middleware delegate defined in-line to the application's request pipeline.
///</summary>///<param name="app">The <see cref="IApplicationBuilder"/> instance.</param>///<param name="middleware">A function that handles the request or calls the given next function.</param>///<returns>The <see cref="IApplicationBuilder"/> instance.</returns>publicstatic IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{
return app.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}
}
}
///<summary>/// Adds a middleware type to the application's request pipeline.
///</summary>///<param name="app">The <see cref="IApplicationBuilder"/> instance.</param>///<param name="middleware">The middleware type.</param>///<param name="args">The arguments to pass to the middleware type instance's constructor.</param>///<returns>The <see cref="IApplicationBuilder"/> instance.</returns>publicstatic IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, paramsobject[] args)
{
if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
{
// IMiddleware doesn't support passing args directly since it's
// activated from the containerif (args.Length > 0)
{
thrownew NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
}
return UseMiddlewareInterface(app, middleware);
}
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m =>
string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray();
if (invokeMethods.Length > 1)
{
thrownew InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
}
if (invokeMethods.Length == 0)
{
thrownew InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
}
var methodInfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
thrownew InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
}
var parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
thrownew InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
}
var ctorArgs = newobject[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
}
var factory = Compile<object>(methodInfo, parameters);
return context =>
{
var serviceProvider = context.RequestServices ?? applicationServices;
if (serviceProvider == null)
{
thrownew InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
}
return factory(instance, context, serviceProvider);
};
});
}
其中 ActivatorUtilities 类的源码如下:
// 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.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;
#if ActivatorUtilities_In_DependencyInjection
using Microsoft.Extensions.Internal;
namespace Microsoft.Extensions.DependencyInjection
#elsenamespace Microsoft.Extensions.Internal
#endif
{
///<summary>/// Helper code for the various activator services.
///</summary>#if ActivatorUtilities_In_DependencyInjection
public#else// Do not take a dependency on this class unless you are explicitly trying to avoid taking a
// dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions.internal#endifstaticclass ActivatorUtilities
{
privatestaticreadonly MethodInfo GetServiceInfo =
GetMethodInfo<Func<IServiceProvider, Type, Type, bool, object>>((sp, t, r, c) => GetService(sp, t, r, c));
///<summary>/// Instantiate a type with constructor arguments provided directly and/or from an <see cref="IServiceProvider"/>.
///</summary>///<param name="provider">The service provider used to resolve dependencies</param>///<param name="instanceType">The type to activate</param>///<param name="parameters">Constructor arguments not provided by the <paramref name="provider"/>.</param>///<returns>An activated object of type instanceType</returns>publicstaticobject CreateInstance(IServiceProvider provider, Type instanceType, paramsobject[] parameters)
{
int bestLength = -1;
var seenPreferred = false;
ConstructorMatcher bestMatcher = default;
if (!instanceType.GetTypeInfo().IsAbstract)
{
foreach (var constructor in instanceType
.GetTypeInfo()
.DeclaredConstructors)
{
if (!constructor.IsStatic && constructor.IsPublic)
{
var matcher = new ConstructorMatcher(constructor);
var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false);
var length = matcher.Match(parameters);
if (isPreferred)
{
if (seenPreferred)
{
ThrowMultipleCtorsMarkedWithAttributeException();
}
if (length == -1)
{
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
}
}
if (isPreferred || bestLength < length)
{
bestLength = length;
bestMatcher = matcher;
}
seenPreferred |= isPreferred;
}
}
}
if (bestLength == -1)
{
var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.";
thrownew InvalidOperationException(message);
}
return bestMatcher.CreateInstance(provider);
}
///<summary>/// Create a delegate that will instantiate a type with constructor arguments provided directly
/// and/or from an <see cref="IServiceProvider"/>.
///</summary>///<param name="instanceType">The type to activate</param>///<param name="argumentTypes">/// The types of objects, in order, that will be passed to the returned function as its second parameter
///</param>///<returns>/// A factory that will instantiate instanceType using an <see cref="IServiceProvider"/>/// and an argument array containing objects matching the types defined in argumentTypes
///</returns>publicstatic ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes)
{
FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, outint?[] parameterMap);
var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray");
var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray);
var factoryLamda = Expression.Lambda<Func<IServiceProvider, object[], object>>(
factoryExpressionBody, provider, argumentArray);
var result = factoryLamda.Compile();
return result.Invoke;
}
///<summary>/// Instantiate a type with constructor arguments provided directly and/or from an <see cref="IServiceProvider"/>.
///</summary>///<typeparam name="T">The type to activate</typeparam>///<param name="provider">The service provider used to resolve dependencies</param>///<param name="parameters">Constructor arguments not provided by the <paramref name="provider"/>.</param>///<returns>An activated object of type T</returns>publicstatic T CreateInstance<T>(IServiceProvider provider, paramsobject[] parameters)
{
return (T)CreateInstance(provider, typeof(T), parameters);
}
///<summary>/// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly.
///</summary>///<typeparam name="T">The type of the service</typeparam>///<param name="provider">The service provider used to resolve dependencies</param>///<returns>The resolved service or created instance</returns>publicstatic T GetServiceOrCreateInstance<T>(IServiceProvider provider)
{
return (T)GetServiceOrCreateInstance(provider, typeof(T));
}
///<summary>/// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly.
///</summary>///<param name="provider">The service provider</param>///<param name="type">The type of the service</param>///<returns>The resolved service or created instance</returns>publicstaticobject GetServiceOrCreateInstance(IServiceProvider provider, Type type)
{
return provider.GetService(type) ?? CreateInstance(provider, type);
}
privatestatic MethodInfo GetMethodInfo<T>(Expression<T> expr)
{
var mc = (MethodCallExpression)expr.Body;
return mc.Method;
}
privatestaticobject GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
{
var service = sp.GetService(type);
if (service == null && !isDefaultParameterRequired)
{
var message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'.";
thrownew InvalidOperationException(message);
}
return service;
}
privatestatic Expression BuildFactoryExpression(
ConstructorInfo constructor,
int?[] parameterMap,
Expression serviceProvider,
Expression factoryArgumentArray)
{
var constructorParameters = constructor.GetParameters();
var constructorArguments = new Expression[constructorParameters.Length];
for (var i = 0; i < constructorParameters.Length; i++)
{
var constructorParameter = constructorParameters[i];
var parameterType = constructorParameter.ParameterType;
var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, outvar defaultValue);
if (parameterMap[i] != null)
{
constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i]));
}
else
{
var parameterTypeExpression = new Expression[] { serviceProvider,
Expression.Constant(parameterType, typeof(Type)),
Expression.Constant(constructor.DeclaringType, typeof(Type)),
Expression.Constant(hasDefaultValue) };
constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression);
}
// Support optional constructor arguments by passing in the default value
// when the argument would otherwise be null.if (hasDefaultValue)
{
var defaultValueExpression = Expression.Constant(defaultValue);
constructorArguments[i] = Expression.Coalesce(constructorArguments[i], defaultValueExpression);
}
constructorArguments[i] = Expression.Convert(constructorArguments[i], parameterType);
}
return Expression.New(constructor, constructorArguments);
}
privatestaticvoid FindApplicableConstructor(
Type instanceType,
Type[] argumentTypes,
out ConstructorInfo matchingConstructor,
outint?[] parameterMap)
{
matchingConstructor = null;
parameterMap = null;
if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap) &&
!TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap))
{
var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.";
thrownew InvalidOperationException(message);
}
}
// Tries to find constructor based on provided argument typesprivatestaticbool TryFindMatchingConstructor(
Type instanceType,
Type[] argumentTypes,
ref ConstructorInfo matchingConstructor,
refint?[] parameterMap)
{
foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors)
{
if (constructor.IsStatic || !constructor.IsPublic)
{
continue;
}
if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, outint?[] tempParameterMap))
{
if (matchingConstructor != null)
{
thrownew InvalidOperationException($"Multiple constructors accepting all given argument types have been found in type '{instanceType}'. There should only be one applicable constructor.");
}
matchingConstructor = constructor;
parameterMap = tempParameterMap;
}
}
return matchingConstructor != null;
}
// Tries to find constructor marked with ActivatorUtilitiesConstructorAttributeprivatestaticbool TryFindPreferredConstructor(
Type instanceType,
Type[] argumentTypes,
ref ConstructorInfo matchingConstructor,
refint?[] parameterMap)
{
var seenPreferred = false;
foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors)
{
if (constructor.IsStatic || !constructor.IsPublic)
{
continue;
}
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false))
{
if (seenPreferred)
{
ThrowMultipleCtorsMarkedWithAttributeException();
}
if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, outint?[] tempParameterMap))
{
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
}
matchingConstructor = constructor;
parameterMap = tempParameterMap;
seenPreferred = true;
}
}
return matchingConstructor != null;
}
// Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters.
// Returns true if each given parameter type is assignable to a unique; otherwise, false.privatestaticbool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type[] argumentTypes, outint?[] parameterMap)
{
parameterMap = newint?[constructorParameters.Length];
for (var i = 0; i < argumentTypes.Length; i++)
{
var foundMatch = false;
var givenParameter = argumentTypes[i].GetTypeInfo();
for (var j = 0; j < constructorParameters.Length; j++)
{
if (parameterMap[j] != null)
{
// This ctor parameter has already been matchedcontinue;
}
if (constructorParameters[j].ParameterType.GetTypeInfo().IsAssignableFrom(givenParameter))
{
foundMatch = true;
parameterMap[j] = i;
break;
}
}
if (!foundMatch)
{
returnfalse;
}
}
returntrue;
}
privatestruct ConstructorMatcher
{
privatereadonly ConstructorInfo _constructor;
privatereadonly ParameterInfo[] _parameters;
privatereadonlyobject[] _parameterValues;
public ConstructorMatcher(ConstructorInfo constructor)
{
_constructor = constructor;
_parameters = _constructor.GetParameters();
_parameterValues = newobject[_parameters.Length];
}
publicint Match(object[] givenParameters)
{
var applyIndexStart = 0;
var applyExactLength = 0;
for (var givenIndex = 0; givenIndex != givenParameters.Length; givenIndex++)
{
var givenType = givenParameters[givenIndex]?.GetType().GetTypeInfo();
var givenMatched = false;
for (var applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex)
{
if (_parameterValues[applyIndex] == null &&
_parameters[applyIndex].ParameterType.GetTypeInfo().IsAssignableFrom(givenType))
{
givenMatched = true;
_parameterValues[applyIndex] = givenParameters[givenIndex];
if (applyIndexStart == applyIndex)
{
applyIndexStart++;
if (applyIndex == givenIndex)
{
applyExactLength = applyIndex;
}
}
}
}
if (givenMatched == false)
{
return -1;
}
}
return applyExactLength;
}
publicobject CreateInstance(IServiceProvider provider)
{
for (var index = 0; index != _parameters.Length; index++)
{
if (_parameterValues[index] == null)
{
var value = provider.GetService(_parameters[index].ParameterType);
if (value == null)
{
if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], outvar defaultValue))
{
thrownew InvalidOperationException($"Unable to resolve service for type '{_parameters[index].ParameterType}' while attempting to activate '{_constructor.DeclaringType}'.");
}
else
{
_parameterValues[index] = defaultValue;
}
}
else
{
_parameterValues[index] = value;
}
}
}
#if NETCOREAPP
return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null);
#elsetry
{
return _constructor.Invoke(_parameterValues);
}
catch (TargetInvocationException ex) when (ex.InnerException != null)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
// The above line will always throw, but the compiler requires we throw explicitly.throw;
}
#endif
}
}
privatestaticvoid ThrowMultipleCtorsMarkedWithAttributeException()
{
thrownew InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}.");
}
privatestaticvoid ThrowMarkedCtorDoesNotTakeAllProvidedArguments()
{
thrownew InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types.");
}
}
}
// 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.Http;
using Microsoft.AspNetCore.Builder.Extensions;
namespace Microsoft.AspNetCore.Builder
{
///<summary>/// Extension methods for the <see cref="MapMiddleware"/>.
///</summary>publicstaticclass MapExtensions
{
///<summary>/// Branches the request pipeline based on matches of the given request path. If the request path starts with
/// the given path, the branch is executed.
///</summary>///<param name="app">The <see cref="IApplicationBuilder"/> instance.</param>///<param name="pathMatch">The request path to match.</param>///<param name="configuration">The branch to take for positive path matches.</param>///<returns>The <see cref="IApplicationBuilder"/> instance.</returns>publicstatic IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
{
if (app == null)
{
thrownew ArgumentNullException(nameof(app));
}
if (configuration == null)
{
thrownew ArgumentNullException(nameof(configuration));
}
if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal))
{
thrownew ArgumentException("The path must not end with a '/'", nameof(pathMatch));
}
// create branchvar branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapOptions
{
Branch = branch,
PathMatch = pathMatch,
};
return app.Use(next => new MapMiddleware(next, options).Invoke);
}
}
}
其中 ApplicationBuilder 类的源码,如下所示:
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Builder
{
publicclass ApplicationBuilder : IApplicationBuilder
{
privateconststring ServerFeaturesKey = "server.Features";
privateconststring ApplicationServicesKey = "application.Services";
privatereadonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
public ApplicationBuilder(IServiceProvider serviceProvider)
{
Properties = new Dictionary<string, object>(StringComparer.Ordinal);
ApplicationServices = serviceProvider;
}
public ApplicationBuilder(IServiceProvider serviceProvider, object server)
: this(serviceProvider)
{
SetProperty(ServerFeaturesKey, server);
}
private ApplicationBuilder(ApplicationBuilder builder)
{
Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);
}
public IServiceProvider ApplicationServices
{
get
{
return GetProperty<IServiceProvider>(ApplicationServicesKey);
}
set
{
SetProperty<IServiceProvider>(ApplicationServicesKey, value);
}
}
public IFeatureCollection ServerFeatures
{
get
{
return GetProperty<IFeatureCollection>(ServerFeaturesKey);
}
}
public IDictionary<string, object> Properties { get; }
private T GetProperty<T>(string key)
{
object value;
return Properties.TryGetValue(key, out value) ? (T)value : default(T);
}
privatevoid SetProperty<T>(string key, T value)
{
Properties[key] = value;
}
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
returnthis;
}
public IApplicationBuilder New()
{
returnnew ApplicationBuilder(this);
}
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.";
thrownew InvalidOperationException(message);
}
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
}
}
// 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.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder.Extensions
{
///<summary>/// Represents a middleware that maps a request path to a sub-request pipeline.
///</summary>publicclass MapMiddleware
{
privatereadonly RequestDelegate _next;
privatereadonly MapOptions _options;
///<summary>/// Creates a new instance of <see cref="MapMiddleware"/>.
///</summary>///<param name="next">The delegate representing the next middleware in the request pipeline.</param>///<param name="options">The middleware options.</param>public MapMiddleware(RequestDelegate next, MapOptions options)
{
if (next == null)
{
thrownew ArgumentNullException(nameof(next));
}
if (options == null)
{
thrownew ArgumentNullException(nameof(options));
}
_next = next;
_options = options;
}
///<summary>/// Executes the middleware.
///</summary>///<param name="context">The <see cref="HttpContext"/> for the current request.</param>///<returns>A task that represents the execution of this middleware.</returns>publicasync Task Invoke(HttpContext context)
{
if (context == null)
{
thrownew ArgumentNullException(nameof(context));
}
PathString matchedPath;
PathString remainingPath;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
{
// Update the pathvar path = context.Request.Path;
var pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matchedPath);
context.Request.Path = remainingPath;
try
{
await _options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
}
else
{
await _next(context);
}
}
}
}
在前两篇文章中我们已经介绍过,中间件的注册和管道的构建都是通过 ApplicationBuilder 进行的。因此要构建一个分支管道,需要一个新的 ApplicationBuilder ,并用它来注册中间件,构建管道。为了在分支管道中也能够共享我们在当前 ApplicationBuilder 中注册的服务(或者说共享依赖注入容器,当然共享的并不止这些),在创建新的 ApplicationBuilder 时并不是直接 new 一个全新的,而是调用当前 ApplicationBuilder 的 New 方法在当前的基础上创建新的,共享了当前 ApplicationBuilder 的 Properties(其中包含了依赖注入容器)。
// 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.Http;
using Microsoft.AspNetCore.Builder.Extensions;
namespace Microsoft.AspNetCore.Builder
{
using Predicate = Func<HttpContext, bool>;
///<summary>/// Extension methods for the <see cref="MapWhenMiddleware"/>.
///</summary>publicstaticclass MapWhenExtensions
{
///<summary>/// Branches the request pipeline based on the result of the given predicate.
///</summary>///<param name="app"></param>///<param name="predicate">Invoked with the request environment to determine if the branch should be taken</param>///<param name="configuration">Configures a branch to take</param>///<returns></returns>publicstatic IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
if (app == null)
{
thrownew ArgumentNullException(nameof(app));
}
if (predicate == null)
{
thrownew ArgumentNullException(nameof(predicate));
}
if (configuration == null)
{
thrownew ArgumentNullException(nameof(configuration));
}
// create branchvar branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
// put middleware in pipelinevar options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch,
};
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}
}
}
其中 MapWhenMiddleware 类的源码,如下所示:
// 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.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder.Extensions
{
///<summary>/// Represents a middleware that runs a sub-request pipeline when a given predicate is matched.
///</summary>publicclass MapWhenMiddleware
{
privatereadonly RequestDelegate _next;
privatereadonly MapWhenOptions _options;
///<summary>/// Creates a new instance of <see cref="MapWhenMiddleware"/>.
///</summary>///<param name="next">The delegate representing the next middleware in the request pipeline.</param>///<param name="options">The middleware options.</param>public MapWhenMiddleware(RequestDelegate next, MapWhenOptions options)
{
if (next == null)
{
thrownew ArgumentNullException(nameof(next));
}
if (options == null)
{
thrownew ArgumentNullException(nameof(options));
}
_next = next;
_options = options;
}
///<summary>/// Executes the middleware.
///</summary>///<param name="context">The <see cref="HttpContext"/> for the current request.</param>///<returns>A task that represents the execution of this middleware.</returns>publicasync Task Invoke(HttpContext context)
{
if (context == null)
{
thrownew ArgumentNullException(nameof(context));
}
if (_options.Predicate(context))
{
await _options.Branch(context);
}
else
{
await _next(context);
}
}
}
}
// 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.Http;
namespace Microsoft.AspNetCore.Builder
{
using Predicate = Func<HttpContext, bool>;
///<summary>/// Extension methods for <see cref="IApplicationBuilder"/>.
///</summary>publicstaticclass UseWhenExtensions
{
///<summary>/// Conditionally creates a branch in the request pipeline that is rejoined to the main pipeline.
///</summary>///<param name="app"></param>///<param name="predicate">Invoked with the request environment to determine if the branch should be taken</param>///<param name="configuration">Configures a branch to take</param>///<returns></returns>publicstatic IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
if (app == null)
{
thrownew ArgumentNullException(nameof(app));
}
if (predicate == null)
{
thrownew ArgumentNullException(nameof(predicate));
}
if (configuration == null)
{
thrownew ArgumentNullException(nameof(configuration));
}
// Create and configure the branch builder right away; otherwise,
// we would end up running our branch after all the components
// that were subsequently added to the main builder.var branchBuilder = app.New();
configuration(branchBuilder);
return app.Use(main =>
{
// This is called only when the main application builder
// is built, not per request. branchBuilder.Run(main);
var branch = branchBuilder.Build();
return context =>
{
if (predicate(context))
{
return branch(context);
}
else
{
return main(context);
}
};
});
}
}
}