本文主要目标:记录Middleware的运行原理流程,并绘制流程图。

目录结构:

1、运行环境

2、Demo实践

3、源码追踪

4、AspnetCore内置middleware

一、运行环境

Visual Studio Community 2019 版本 16.8.5

.Net Sdk Version: 5.0.103

二、Demo实践

讲解或学习一个东西的时候,最方便的方式是先写一个Demo。基于此,我写以一个中间件的记录请求输出的实践Demo来理解Middleware。

实体:

    public class RequestResponseLog
{
public string Id { get; set; }
public DateTime CreateTime { get; set; }
public string RequestJson { get; set; }
public string ResponseJson { get; set; }
} public class Student
{
public string Id { get; set; }
public string Name { get; set; }
/// <summary>
/// 学校
/// </summary>
public string School { get; set; }
/// <summary>
/// 班级
/// </summary>
public string Class { get; set; }
/// <summary>
/// 年级
/// </summary>
public string Grade { get; set; }
}

Controller:用于接收请求

    [Route("api/[controller]")]
[ApiController]
public class StudentController : Controller
{
[HttpGet("GetStudent")]
public IActionResult GetStudent()
{
var student = new Student()
{
Id = Guid.NewGuid().ToString(),
Class = "321",
Grade = "23",
Name = "Name001",
School = "School002"
};
return Ok(student);
}
}

Middleware 中间件(记录Request和Response):

public class RequestResponseLoggingMiddleware
{
private RequestDelegate _next;
public RequestResponseLoggingMiddleware(RequestDelegate next)
{
this._next = next;
} /// <summary>
///
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{ //First, get the incoming request
var request = await FormatRequest(context.Request); var body = context.Response.Body; //Copy a pointer to the original response body stream
var originalBodyStream = context.Response.Body; //Create a new memory stream...
using (var responseBody = new MemoryStream())
{
//...and use that for the temporary response body
context.Response.Body = responseBody; //Continue down the Middleware pipeline, eventually returning to this class
await _next(context); //Format the response from the server
var response = await FormatResponse(context.Response); //TODO: Save log to chosen datastore,临时使用
DemoQueueBlock<RequestResponseLog>.Add(new RequestResponseLog()
{
Id=Guid.NewGuid().ToString(),
CreateTime = DateTime.Now,
ResponseJson = response,
RequestJson = request
}); //Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
await responseBody.CopyToAsync(originalBodyStream);
}
}

为了防止实时存储数据库压力过大,仓储部分用了BlockingCollection实现的简易队列。

blockingcollection-1.getconsumingenumerable

https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.blockingcollection-1.getconsumingenumerable?view=net-5.0

 public static void Consume(Action<T> func)
{
Task.Factory.StartNew(() =>
{
foreach (var item in Colls.GetConsumingEnumerable())
{
func(item);
Console.WriteLine(string.Format("---------------: {0}", item));
}
});
}

消费队列时入库:

public class DemoConsume
{
private readonly MysqlDbContext _dbContext;
public DemoConsume(MysqlDbContext dbContext)
{
_dbContext = dbContext;
}
public bool Consume()
{
DemoQueueBlock<RequestResponseLog>.Consume(async (log)=> {
await _dbContext.AddAsync(log);
await _dbContext.SaveChangesAsync();
});
return true;
}
}

StartUp文件AddConsume和

app.UseMiddleware();

   public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var connection = Configuration.GetConnectionString("MysqlConnection");
services.AddDbContext<MysqlDbContext>(options => options.UseMySQL(connection),ServiceLifetime.Scoped);
services.AddConsume();
} public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Sql语句:

CREATE TABLE `request_response_log`  (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`request_json` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`response_json` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

运行程序效果:

可以看到该Demo提供了一个记录Http请求和输出日志的功能。

这里面和Middleware有关的功能为:

1、定义了RequestResponseLoggingMiddleware类

RequestDelegate向下转发请求,

Invoke方法

2、StartUp的app.UseMiddleware()。

这些方法具体怎么流转运行的呢?我们来搜一下源码可以确认下。

三、源码跟踪

所以我们可以看下UseMiddlewareExtensions

 public static class UseMiddlewareExtensions
{
internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";
/// <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>
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
{
if (typeof(IMiddleware).IsAssignableFrom(middleware))
{
// IMiddleware doesn't support passing args directly since it's
// activated from the container
if (args.Length > 0)
{
throw new 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)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
} if (invokeMethods.Length == 0)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
} var methodInfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
} var parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
} var ctorArgs = new object[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)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
} return factory(instance, context, serviceProvider);
};
});
}

这里面用了

UseMiddleware(),进而调用

UseMiddleware(type TMiddleware)

进行了如下判断:

1、如果TMiddleware是继承了IMiddleware,则执行UseMiddlewareInterface方法。利用IMiddlewareFactory提供中间件的工厂创建方式,Microsoft.AspNetCore.Http提供了IMiddlewareFactory的默认实现MiddlewareFactory。

return app.Use(next =>
{
return async context =>
{
var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));
if (middlewareFactory == null)
{
// No middleware factory
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
} var middleware = middlewareFactory.Create(middlewareType);
if (middleware == null)
{
// The factory returned null, it's a broken implementation
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
} try
{
await middleware.InvokeAsync(context, next);
}
finally
{
middlewareFactory.Release(middleware);
}
};
});

2、如果没有继承Middleware,则执行以下操作:

1、根据Invoke或InvokeAsync查找方法

2、验证只存在一个方法

3、验证返回类型为Task

4、验证第一个参数必须是HttpContext

5、ActivatorUtilities.CreateInstance 创建实例

6、如果只有一个参数,返回一个RequestDelegate类型的方法委托?

7、多个参数继续执行如下操作。Compile方法和参数。

var factory = Compile(methodInfo, parameters);

return context =>

{

var serviceProvider = context.RequestServices ?? applicationServices;

if (serviceProvider == null)

{

throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));

}

                return factory(instance, context, serviceProvider);
};

8、Compile演示了Lamuda表达式的编译方式,以后可作参考

 private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters)
{
// If we call something like
//
// public class Middleware
// {
// public Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
// {
//
// }
// }
// // We'll end up with something like this:
// Generic version:
//
// Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider)
// {
// return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
// } // Non generic version:
//
// Task Invoke(object instance, HttpContext httpContext, IServiceProvider provider)
// {
// return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
// } var middleware = typeof(T); var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
var instanceArg = Expression.Parameter(middleware, "middleware"); var methodArguments = new Expression[parameters.Length];
methodArguments[0] = httpContextArg;
for (int i = 1; i < parameters.Length; i++)
{
var parameterType = parameters[i].ParameterType;
if (parameterType.IsByRef)
{
throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
} var parameterTypeExpression = new Expression[]
{
providerArg,
Expression.Constant(parameterType, typeof(Type)),
Expression.Constant(methodInfo.DeclaringType, typeof(Type))
}; var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
} Expression middlewareInstanceArg = instanceArg;
if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T))
{
middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);
} var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments); var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg); return lambda.Compile();
}

从上面我们可以看到这个扩展方法主要做了两件事:

判断是IMiddleware,然后采用不同的处理方式。

文章刚开始我们已经实践了非继承的模式,下面我们来实践下继承IMiddleware的模式。

public class TestMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Console.WriteLine("TestMiddleware");
await next(context);
// throw new NotImplementedException();
}
}

StartUp

(由于

MiddlewareFactory通过_serviceProvider.GetRequiredService(middlewareType) as IMiddleware获取中间件,所以需要在ConfigureServices里面注入TestMiddleware,不然会报错):

  public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<TestMiddleware>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<TestMiddleware>();
}

效果如下:

以上搜查暂时告一段落。

但里面还有个IApplicationBuilder的use方式尚没有看到使用方式,还需要继续探查。

IApplicationBuilder接口:

定义一个用于配置应用程序请求管道的类

public interface IApplicationBuilder
{
/// <summary>
/// Gets or sets the <see cref="IServiceProvider"/> that provides access to the application's service container.
/// </summary>
IServiceProvider ApplicationServices { get; set; } /// <summary>
/// Gets the set of HTTP features the application's server provides.
/// </summary>
IFeatureCollection ServerFeatures { get; } /// <summary>
/// Gets a key/value collection that can be used to share data between middleware.
/// </summary>
IDictionary<string, object?> Properties { get; } /// <summary>
/// Adds a middleware delegate to the application's request pipeline.
/// </summary>
/// <param name="middleware">The middleware delegate.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); /// <summary>
/// Creates a new <see cref="IApplicationBuilder"/> that shares the <see cref="Properties"/> of this
/// <see cref="IApplicationBuilder"/>.
/// </summary>
/// <returns>The new <see cref="IApplicationBuilder"/>.</returns>
IApplicationBuilder New(); /// <summary>
/// Builds the delegate used by this application to process HTTP requests.
/// </summary>
/// <returns>The request handling delegate.</returns>
RequestDelegate Build();
}

通过查看引用,我们可以看到提供了以下扩展:AspNetCore.Http.Abstractions\Extension

图片

通过翻看源码,可以看出这些扩展都是调用的IApplicationBuilder的use,我们只需要继续关注这个Use就行了。通过继续追溯源码,可以搜到IApplicationBuilderFactory的默认实现ApplicationBuilderFactory,它是一个创建ApplicationBuilder的工厂类。

 public class ApplicationBuilderFactory : IApplicationBuilderFactory
{
private readonly IServiceProvider _serviceProvider; /// <summary>
/// Initialize a new factory instance with an <see cref="IServiceProvider" />.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> used to resolve dependencies and initialize components.</param>
public ApplicationBuilderFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} /// <summary>
/// Create an <see cref="IApplicationBuilder" /> builder given a <paramref name="serverFeatures" />.
/// </summary>
/// <param name="serverFeatures">An <see cref="IFeatureCollection"/> of HTTP features.</param>
/// <returns>An <see cref="IApplicationBuilder"/> configured with <paramref name="serverFeatures"/>.</returns>
public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
{
return new ApplicationBuilder(_serviceProvider, serverFeatures);
}
}

关注一下 ApplicationBuilder的重点部分:

 public class ApplicationBuilder : IApplicationBuilder
{
private const string ServerFeaturesKey = "server.Features";
private const string ApplicationServicesKey = "application.Services"; private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new(); /// <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)
{
Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
ApplicationServices = serviceProvider;
} /// <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)
: this(serviceProvider)
{
SetProperty(ServerFeaturesKey, server);
} private ApplicationBuilder(ApplicationBuilder builder)
{
Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
} /// <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>
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);
return 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);
} context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
}; for (var c = _components.Count - 1; c >= 0; c--)
{
app = _components[c](app);
} return app;
}
}

从上面源码的实现来看Use的作用仅仅是将一个中间件添加到List<Func<RequestDelegate, RequestDelegate>> _components里面,换句话来讲就是将一个RequestDelegate的委托放到一个list里面。

流程图如下:

图片

四、Asp.netCore内置Middleware举例:

以ConcurrencyLimiterMiddleware为例,传入的请求进行排队处理,避免线程池的不足.

 public class ConcurrencyLimiterMiddleware
{
private readonly IQueuePolicy _queuePolicy;
private readonly RequestDelegate _next;
private readonly RequestDelegate _onRejected;
private readonly ILogger _logger; /// <summary>
/// Creates a new <see cref="ConcurrencyLimiterMiddleware"/>.
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param>
/// <param name="queue">The queueing strategy to use for the server.</param>
/// <param name="options">The options for the middleware, currently containing the 'OnRejected' callback.</param>
public ConcurrencyLimiterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IQueuePolicy queue, IOptions<ConcurrencyLimiterOptions> options)
{
if (options.Value.OnRejected == null)
{
throw new ArgumentException("The value of 'options.OnRejected' must not be null.", nameof(options));
} _next = next;
_logger = loggerFactory.CreateLogger<ConcurrencyLimiterMiddleware>();
_onRejected = options.Value.OnRejected;
_queuePolicy = queue;
} /// <summary>
/// Invokes the logic of the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the request leaves.</returns>
public async Task Invoke(HttpContext context)
{
var waitInQueueTask = _queuePolicy.TryEnterAsync(); // Make sure we only ever call GetResult once on the TryEnterAsync ValueTask b/c it resets.
bool result; if (waitInQueueTask.IsCompleted)
{
ConcurrencyLimiterEventSource.Log.QueueSkipped();
result = waitInQueueTask.Result;
}
else
{
using (ConcurrencyLimiterEventSource.Log.QueueTimer())
{
result = await waitInQueueTask;
}
} if (result)
{
try
{
await _next(context);
}
finally
{
_queuePolicy.OnExit();
}
}
else
{
ConcurrencyLimiterEventSource.Log.RequestRejected();
ConcurrencyLimiterLog.RequestRejectedQueueFull(_logger);
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
await _onRejected(context);
}
}

需要注意的是有两个:

1、IQueuePolicy,asp.netCore内置了两种实现QueuePolicy和StackPolicy,这里就不贴代码了,主要是关于堆和栈的实现逻辑。

2、ConcurrencyLimiterOptions

QueuePolicyServiceCollectionExtensions

 public static class QueuePolicyServiceCollectionExtensions
{
/// <summary>
/// Tells <see cref="ConcurrencyLimiterMiddleware"/> to use a FIFO queue as its queueing strategy.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="configure">Set the options used by the queue.
/// Mandatory, since <see cref="QueuePolicyOptions.MaxConcurrentRequests"></see> must be provided.</param>
/// <returns></returns>
public static IServiceCollection AddQueuePolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, QueuePolicy>();
return services;
} /// <summary>
/// Tells <see cref="ConcurrencyLimiterMiddleware"/> to use a LIFO stack as its queueing strategy.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="configure">Set the options used by the queue.
/// Mandatory, since <see cref="QueuePolicyOptions.MaxConcurrentRequests"></see> must be provided.</param>
/// <returns></returns>
public static IServiceCollection AddStackPolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, StackPolicy>();
return services;
}
} public class QueuePolicyOptions
{
/// <summary>
/// Maximum number of concurrent requests. Any extras will be queued on the server.
/// This option is highly application dependant, and must be configured by the application.
/// </summary>
public int MaxConcurrentRequests { get; set; } /// <summary>
/// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailable'.
/// This option is highly application dependant, and must be configured by the application.
/// </summary>
public int RequestQueueLimit { get; set; }
}

通过源码可以大概看出使用方式了吧,这里就不做实践了。

今天的分享到此结束,谢谢观看。

由于排版问题,原文请参考:https://mp.weixin.qq.com/s/nm8Pa-q3oOInX0LIw9swNA

ASP.NET Core 中间件(Middleware)(一)的更多相关文章

  1. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  2. ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门

    一.前言 1.本教程主要内容 ASP.NET Core 中间件介绍 通过自定义 ASP.NET Core 中间件实现请求验签 2.本教程环境信息 软件/环境 说明 操作系统 Windows 10 SD ...

  3. ASP.NET Core 入门笔记10,ASP.NET Core 中间件(Middleware)入门

    一.前言 1.本教程主要内容 ASP.NET Core 中间件介绍 通过自定义 ASP.NET Core 中间件实现请求验签 2.本教程环境信息 软件/环境 说明 操作系统 Windows 10 SD ...

  4. ASP.NET Core -中间件(Middleware)使用

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  5. ASP.NET Core 中间件Diagnostics使用

    ASP.NET Core 中间件(Middleware)Diagnostics使用.对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware). Diagnost ...

  6. ASP.NET Core 中间件Diagnostics使用 异常和错误信息

    ASP.NET Core 中间件(Middleware)Diagnostics使用.对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware). Diagnost ...

  7. ASP.NET Core 中间件自定义全局异常处理

    目录 背景 ASP.NET Core过滤器(Filter) ASP.NET Core 中间件(Middleware) 自定义全局异常处理 .Net Core中使用ExceptionFilter .Ne ...

  8. [转帖]ASP.NET Core 中间件(Middleware)详解

    ASP.NET Core 中间件(Middleware)详解   本文为官方文档译文,官方文档现已非机器翻译 https://docs.microsoft.com/zh-cn/aspnet/core/ ...

  9. 在ASP.NET Core使用Middleware模拟Custom Error Page功能

    一.使用场景 在传统的ASP.NET MVC中,我们可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAtt ...

  10. [转]ASP.NET Core 中间件详解及项目实战

    本文转自:http://www.cnblogs.com/savorboard/p/5586229.html 前言 在上篇文章主要介绍了DotNetCore项目状况,本篇文章是我们在开发自己的项目中实际 ...

随机推荐

  1. docker mysql 设置忽略大小写

    使用docker 安装mysql时  Linux下是默认不忽略大小写,导致操作数据库的时候会报如下错误 为了解决上面的问题,我们在创建MySQL容器的时候就需要初始化配置  lower_case_ta ...

  2. Docker 如何动态修改容器端口映射

    Docker端口映射往往是Docker Run命令时通过-p将容器内部端口映射到宿主机的指定端口上,一般来说容器的端口所对应的端口是提前确定需要映射的.但是有些情况下不得不需要临时映射端口,例如Doc ...

  3. Windows下的python虚拟环境设置

    Windows下的python虚拟环境设置: virtualenv 在python开发中,我们可能会遇到一种情况:就是当前的项目依赖的是某一个版本,但是另一个项目依赖的是另一个版本,这样就会造成依赖冲 ...

  4. std::async的使用总结

    C++98标准中并没有线程库的存在,直到C++11中才终于提供了多线程的标准库,提供了管理线程.保护共享数据.线程间同步操作.原子操作等类.多线程库对应的头文件是#include <thread ...

  5. 浅析Linux用户空间中的Mmap

    一.MMap基础概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系.实现这样的映射关系后,进程就可以采 ...

  6. 阿姆达尔定律 Amdahl's law

    Amdahl's law - Wikipedia https://en.wikipedia.org/wiki/Amdahl%27s_law 阿姆达尔定律(英语:Amdahl's law,Amdahl' ...

  7. httpd反向代理实践(一)

    div.example { background-color: rgba(229, 236, 243, 1); color: rgba(0, 0, 0, 1); padding: 0.5em; mar ...

  8. 网页小实验——用canvas生成精灵动画图片

    实验目标:借助canvas把一张国际象棋棋子图片转换为一组适用于WebGL渲染的精灵动画图片,不借助其他图片处理工具,不引用其他库只使用原生js实现. 初始图片如下: 一.图片分割 将初始图片分割为六 ...

  9. 一文打尽端口复用 VS Haproxy端口复用

    出品|MS08067实验室(www.ms08067.com) 本文作者:Spark(Ms08067内网安全小组成员) 1.概述   Haproxy是一个使用c语言开发的高性能负载均衡代理软件,提供tc ...

  10. JavaWeb——过滤器及监听器

    什么是过滤器? 过滤器示意图 Filter是如何实现拦截的? Filter的生命周期 Filter的创建 Filter的销毁 FilterConfig接口 Servlet过滤器有关接口 过滤器配置 F ...