GVKun编程网logo

使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?

21

对于想了解使用.NETCore的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?的读者,本文将是一篇不可错过的文章,并且为您提供关于.net5.0集成测试,未找到运行状况检查端点

对于想了解使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?的读者,本文将是一篇不可错过的文章,并且为您提供关于.net 5.0 集成测试,未找到运行状况检查端点、.Net Core实现记录接口执行时间的中间件、ASP.NET Core 3.0中使用动态控制器路由、ASP.NET Core 中的中间件的有价值信息。

本文目录一览:

使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?

使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?

如何解决使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势??

我正在读一本“ Asp.net Core 3和Angular 9”书,其中有一个.NET Core Health检查的示例用法。 微软网站上也对此进行了描述:https://docs.microsoft.com/en-US/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.0 我找不到真正使用它的理由,而不仅仅是在某些ping外部地址的控制器中创建路由。

一本书中的代码如下:

通过Configure(Startup.cs)方法添加它:

app.UseHealthChecks("/hc",new CustomHealthCheckOptions());

ConfigureServices方法:

services.AddHealthChecks()
    .AddCheck("ICMP_01",new ICMPHealthCheck("www.ryadel.com",100))
    .AddCheck("ICMP_02",new ICMPHealthCheck("www.google.com",100))
    .AddCheck("ICMP_03",new ICMPHealthCheck("www.does-notexist.com",100));

创建ICMPHealthCheck.cs文件:

using Microsoft.Extensions.Diagnostics.HealthChecks;
using System;
using System.Net.networkinformation;
using System.Threading;
using System.Threading.Tasks;

namespace HealthCheck
{
    public class ICMPHealthCheck : IHealthCheck
    {
        private string Host { get; set; }
        private int Timeout { get; set; }

        public ICMPHealthCheck(string host,int timeout)
        {
            Host = host;
            Timeout = timeout;
        }

        public async Task<HealthCheckResult> CheckHealthAsync(
            HealthCheckContext context,CancellationToken cancellationToken = default)
        {
            try
            {
                using (var ping = new Ping())
                {
                    var reply = await ping.SendPingAsync(Host);

                    switch (reply.Status)
                    {
                        case IPStatus.Success:
                            var msg = String.Format(
                                "IMCP to {0} took {1} ms.",Host,reply.roundtripTime);

                            return (reply.roundtripTime > Timeout)
                            ? HealthCheckResult.Degraded(msg)
                            : HealthCheckResult.Healthy(msg);
                        default:
                            var err = String.Format(
                                "IMCP to {0} Failed: {1}",reply.Status);

                            return HealthCheckResult.Unhealthy(err);
                    }
                }
            }
            catch (Exception e)
            {
                var err = String.Format(
                    "IMCP to {0} Failed: {1}",e.Message);
                return HealthCheckResult.Unhealthy(err);
            }
        }
    }
}

创建CustomHealthCheckOptions.cs文件:

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
namespace HealthCheck
{
    public class CustomHealthCheckOptions : HealthCheckOptions
    {
        public CustomHealthCheckOptions() : base()
        {
            var jsonSerializerOptions = new JsonSerializerOptions()
            {
                WriteIndented = true
            };
            ResponseWriter = async (c,r) =>
            {
                c.Response.ContentType =
                MediaTypeNames.Application.Json;
                c.Response.StatusCode = StatusCodes.Status200OK;
                var result = JsonSerializer.Serialize(new
                {
                    checks = r.Entries.Select(e => new
                    {
                        name = e.Key,responseTime = e.Value.Duration.TotalMilliseconds,status = e.Value.Status.ToString(),description = e.Value.Description
                    }),totalStatus = r.Status,totalResponseTime =
                    r.TotalDuration.TotalMilliseconds,},jsonSerializerOptions);
                await c.Response.WriteAsync(result);
            };
        }
    }
}

所以它只能ping 3个地址,我看不到使用Microsoft.AspNetCore.Diagnostics.HealthChecks库的优势。那是错误的例子吗?

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

.net 5.0 集成测试,未找到运行状况检查端点

.net 5.0 集成测试,未找到运行状况检查端点

如何解决.net 5.0 集成测试,未找到运行状况检查端点?

我正在尝试测试运行状况检查端点。

我已经使用 .net core、WebApplicationFactory 和 TestServer 实施了集成测试,以测试我的 API 端点。

我编写了测试用例 (xUnit) 来测试我的 API 端点,简单的 API 控制器工作得很好,但没有找到健康检查端点 - 它不是控制器,而是一个可配置的端点

这里是服务配置

services.AddHealthChecks().AddCheck<PingHealthCheck>("ping_health_check");

这里是配置

  endpoints.MapHealthChecks("/health",new HealthCheckOptions()
    {
        // Prevent response caching
        AllowCachingResponses = false,ResponseWriter = (context,report) => context.Response
            //Return an object instead of a plain text
            .WriteAsync(JsonConvert.SerializeObject(new {status= report.Status.ToString() })),ResultStatusCodes =
            {
                [HealthStatus.Healthy] = StatusCodes.Status200OK,[HealthStatus.Degraded] = StatusCodes.Status200OK,[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
            }
    })

这里是测试用例

[Fact]
public async Task Call_Health_Return_Healthy()
{
    //Arrange 
    //Act
    var response = await _factory.TestHttpClient
        .GetAsync("/health");
    //Assert
    response.StatusCode.Should().BeEquivalentTo(HttpStatusCode.OK);
}

解决方法

我成功解决了我的问题。原因是配置允许哪些主机访问运行状况端点。

问题是,我将访问(在本地主机中)限制为端口 5001,但 ApplicationFactory 中的 TestServer 没有任何端口。

这里是配置代码

        /// <summary>
        /// Configures the endpoint.
        /// </summary>
        /// <param name="builder">The application builder. <see cref="IApplicationBuilder"/></param>
        /// <param name="configuration">The IConfiguration builder instance</param>
        public static void ConfigureEndpoint(this IApplicationBuilder builder,IConfiguration configuration)
        {
            var healthConfiguration = configuration.GetSection(nameof(HealthConfiguration)).Get<HealthConfiguration>();
            builder.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHealthChecks("/health",new HealthCheckOptions()
                {
                    // Prevent response caching
                    AllowCachingResponses = false,ResponseWriter = (context,report) => context.Response
                        //Return an object instead of a plain text
                        .WriteAsync(JsonConvert.SerializeObject(new {status = report.Status.ToString()})),ResultStatusCodes =
                    {
                        [HealthStatus.Healthy] = StatusCodes.Status200OK,[HealthStatus.Degraded] = StatusCodes.Status200OK,[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
                    }
                }).RequireHost(string.Join(",",healthConfiguration.Hosts.Select(host=>host)));
            });
        }

而配置部分更改为

 "HealthConfiguration": {
    "Hosts": ["localhost"] 
  },

.Net Core实现记录接口执行时间的中间件

.Net Core实现记录接口执行时间的中间件

原文: .Net Core实现记录接口执行时间的中间件

  项目中有时接口访问时间过长,但是通过浏览器F12查看时,接口访问时间很正常,所以就很奇怪,于是写一个中间件,记录所有接口访问时间的中间件。

一、中间件

  中间件是应用程序处理管道中的组件,用来处理请求和响应。如下图,请求来之后,第一个中间件处理,处理完后调用下一个中间件(当然也可以选择不调用下一个中间件),这样形成一个请求处理管道。每一个中间件通过一个名为RequestDelegate的委托调用下一个中间件。当所有的中间件处理完请求后,再依次返回Response。

  微软提供的中间件有:Authentication(认证)、Cors(跨域资源共享)、Session StaticFiles(静态文件)、Caching(缓存)、MVC等等。

二、实现记录接口执行时间中间件

  首先中间件不需要继承什么接口,也没有什么限制。我们可以仿照微软提供的中间件起名建一个 CalculateExecutionTimeMiddleware和 CalculateExecutionTimeMiddlewareExtensions,如果中间件中涉及配置相关的参数,可以建一个Option。此中间件没有配置参数就没有Option。还有就是此中间件必须放在第一位,这样才能尽可能记录请求时间。

public class CalculateExecutionTimeMiddleware
    {
        private readonly RequestDelegate _next;//下一个中间件
        private readonly ILogger _logger;
        Stopwatch stopwatch;
        public CalculateExecutionTimeMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }
            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }
            this._next = next;
            _logger = loggerFactory.CreateLogger<CalculateExecutionTimeMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            stopwatch = new Stopwatch();
            stopwatch.Start();//在下一个中间价处理前,启动计时器
            await _next.Invoke(context);

            stopwatch.Stop();//所有的中间件处理完后,停止秒表。
            _logger.LogInformation($@"接口{context.Request.Path}耗时{stopwatch.ElapsedMilliseconds}ms");
        }
    }

 拓展方法 将中间件加入到请求处理通道中。

public static class CalculateExecutionTimeMiddlewareExtensions
    {
        public static IApplicationBuilder UseCalculateExecutionTime(this IApplicationBuilder app)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }
            return app.UseMiddleware<CalculateExecutionTimeMiddleware>(); 
        }
    }

使用:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IDataProtectionProvider dataProtectionProvider)
        {
            app.UseCalculateExecutionTime();//只需在此添加
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseSession();
            app.UseMvc();
        }

三、总结

  此中间件只记录了程序处理请求的时间,不能记录网络传输时间,所以记录的时间比浏览器中的时间短一点,但不影响我们找长时间相应的接口。源码在https://github.com/MicroHeartWangZheng/ExecutionTime。

  

ASP.NET Core 3.0中使用动态控制器路由

ASP.NET Core 3.0中使用动态控制器路由

原文: ASP.NET Core 3.0中使用动态控制器路由

原文:Dynamic controller routing in ASP.NET Core 3.0
作者:Filip W
译文:https://www.cnblogs.com/lwqlun/p/11461657.html
译者:Lamond Lu

译者注

今天在网上看到了这篇关于ASP.NET Core动态路由的文章,感觉蛮有意思的,给大家翻译一下,虽然文中的例子不一定会在日常编码中出现,但是也给我们提供了一定的思路。

前言

相对于ASP.NET MVC以及ASP.NET Core MVC中的旧版本路由特性, 在ASP.NET Core 3.0中新增了一个不错的扩展点,即程序获取到路由后,可以将其动态指向一个给定的controller/action.

这个功能有非常多的使用场景。如果你正在使用从ASP.NET Core 3.0 Preview 7及更高版本,你就可以在ASP.NET Core 3.0中使用它了。

PS: 官方没有在Release Notes中提到这一点。

下面就让我们一起来看一看ASP.NET Core 3.0中的动态路由。

背景

当我们使用MVC路由的时候,最典型的用法是,我们使用路由特性(Route Attributes)来定义路由信息。使用这种方法,我们需要要为每个路由进行显式的声明。

public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   public IActionResult Index()
   {
      return View();
   }
}

相对的,你可以使用中心化的路由模型,使用这种方式,你就不需要显式的声明每一个路由 - 这些路由会自动被所有发现的控制器的自动识别。 然而,这样做的前提是,所有的控制器首先必须存在。

以下是ASP.NET Core 3.0中使用新语法Endpoint Routing的实现方式。

app.UseEndpoints(
    endpoints =>
    {
        endpoints.MapControllerRoute("default", 
                  "{controller=Home}/{action=Index}/{id?}");
    }
);

以上两种方式的共同点是,所有的路由信息都必须在应用程序启动时加载。

但是,如果你希望能够动态定义路由, 并在应用程序运行时添加/删除它们,该怎么办?

下面我给大家列举几个动态定义路由的使用场景。

  • 多语言路由,以及使用新语言时,针对那些新语言路由的修改。
  • 在CMS类型的系统中,我们可能会动态添加一些新页面,这些新页面不需要创建的控制器或者在源码中硬编码路由信息。
  • 多租户应用中,租户路由可以在运行时动态激活或者取消激活。

这个问题的处理过程应该相当的好理解。我们希望尽早的拦截路由处理,检查已为其解析的当前路由值,并使用例如数据库中的数据将它们“转换”为一组新的路由值,这些新的路由值指向了一个实际存在的控制器。

实例问题 - 多语言翻译路由问题

在旧版本的ASP.NET Core MVC中, 我们通常通过自定义IRouter接口,来解决这个问题。然而在ASP.NET Core 3.0中这种方式已经行不通了,因为路由已经改由上面提到的Endpoint Routing来处理。值得庆幸的是,ASP.NET Core 3.0 Preview 7以及后续版本中,我们可以通过一个新特性MapDynamicControllRoute以及一个扩展点DynamicRouteValueTransformer, 来支持我们的需求。下面让我们看一个具体的例子。

想象一下,在你的项目中,有一个OrderController控制器,然后你希望它支持多语言翻译路由。

public class OrdersController : Controller
{
    public IActionResult List()
    {
        return View();
    }
}

我们可能希望的请求的URL是这样的,例如

  • 英语 - /en/orders/list
  • 德语 - /de/bestellungen/liste
  • 波兰语 - /pl/zamowienia/lista

使用动态路由处理多语言翻译路由问题

那么我们现在该如何解决这个问题呢?我们可以使用新特性MapDynamicControllerRoute来替代默认的MVC路由, 并将其指向我们自定义的DynamicRouteValueTransformer类, 该类实现了我们之前提到的路由值转换 。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);

        services.AddSingleton<TranslationTransformer>();
        services.AddSingleton<TranslationDatabase>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDynamicControllerRoute<TranslationTransformer>("{language}/{controller}/{action}");
        });
    }
}

这里我们定义了一个TranslationTransformer类,它继承了DynamicRouteValueTransformer类。这个新类将负责将特定语言路由值,转换为可以在我们应用可以匹配到controller/action的路由值字典,而这些值通常不能直接和我们应用中的任何controller/action匹配。所以这里简单点说,就是在德语场景下,controller名会从“Bestellungen”转换成"Orders", action名"Liste"转换成"List"。

TranslationTransformer类被作为泛型类型参数,传入MapDynamicControllerRoute方法中,它必须在依赖注入容器中注册。这里,我们还需要注册一个TranslationDatabase类,但是这个类仅仅为了帮助演示,后面我们会需要它。

public class TranslationTransformer : DynamicRouteValueTransformer
{
    private readonly TranslationDatabase _translationDatabase;

    public TranslationTransformer(TranslationDatabase translationDatabase)
    {
        _translationDatabase = translationDatabase;
    }

    public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext
    , RouteValueDictionary values)
    {
        if (!values.ContainsKey("language") 
            || !values.ContainsKey("controller") 
            || !values.ContainsKey("action")) return values;

        var language = (string)values["language"];
        var controller = await _translationDatabase.Resolve(language, 
            (string)values["controller"]);
            
        if (controller == null) return values;
        values["controller"] = controller;

        var action = await _translationDatabase.Resolve(language, 
            (string)values["action"]);
            
        if (action == null) return values;
        values["action"] = action;

        return values;
    }
}

在这个转换器中,我们需要尝试提取3个路由参数, language, controller,action,然后我们需要在模拟用的数据库类中,找到其对应的翻译。正如我们之前提到的,你通常会希望从数据库中查找对应的内容,因为使用这种方式,我们可以在应用程序生命周期的任何时刻,动态的影响路由。为了说明这一点,我们将使用TranslationDatabase类来模拟数据库操作,这里你可以把它想象成一个真正的数据库仓储服务。

public class TranslationDatabase
{
    private static Dictionary<string, Dictionary<string, string>> Translations 
        = new Dictionary<string, Dictionary<string, string>>
    {
        {
            "en", new Dictionary<string, string>
            {
                { "orders", "orders" },
                { "list", "list" }
            }
        },
        {
            "de", new Dictionary<string, string>
            {
                { "bestellungen", "orders" },
                { "liste", "list" }
            }
        },
        {
            "pl", new Dictionary<string, string>
            {
                { "zamowienia", "order" },
                { "lista", "list" }
            }
        },
    };

    public async Task<string> Resolve(string lang, string value)
    {
        var normalizedLang = lang.ToLowerInvariant();
        var normalizedValue = value.ToLowerInvariant();
        if (Translations.ContainsKey(normalizedLang) 
            && Translations[normalizedLang]
                .ContainsKey(normalizedValue))
        {
            return Translations[normalizedLang][normalizedValue];
        }

        return null;
    }
}

到目前为止,我们已经很好的解决了这个问题。这里通过在MVC应用中启用这个设置,我们就可以向我们之前定义的3个路由发送请求了。

  • 英语 - /en/orders/list
  • 德语 - /de/bestellungen/liste
  • 波兰语 - /pl/zamowienia/lista

每个请求都会命中OrderController控制器和List方法。当前你可以将这个方法进一步扩展到其他的控制器。但最重要的是,如果新增一种新语言或者新的路由别名映射到现有语言中的controller/actions,你是不需要做任何代码更改,甚至重启项目的。

请注意,在本文中,我们只关注路由转换,这里仅仅是为了演示ASP.NET Core 3.0中的动态路由特性。如果你希望在应用程序中实现本地化,你可能还需要阅读ASP.NET Core 3.0的本地化指南, 因为你可以需要根据语言的路由值设置正确的CurrentCulture

最后, 我还想再补充一点,在我们之前的例子中,我们在路由模板中显式的使用了{controller}{action}占位符。这并不是必须的,在其他场景中,你还可以使用"catch-all"路由通配符,并将其转换为controller/action路由值。

"catch-all"路由通配符是CMS系统中的典型解决方案,你可以使用它来处理不同的动态“页面”路由。

它看起来可能类似:

endpoints.MapDynamicControllerRoute<PageTransformer>("pages/{**slug}");

然后,你需要将pages之后的整个URL参数转换为现有可执行控制器的内容 - 通常URL/路由的映射是保存在数据库中的。

希望你会发现这篇文章很有用 - 所有的演示源代码都可以在Github上找到。

ASP.NET Core 中的中间件

ASP.NET Core 中的中间件

前言

  由于是第一次写博客,如果您看到此文章,希望大家抱着找错误、批判的心态来看。 sky!

何为中间件?

在 ASP.NET Framework 中应该都知道请求管道。可参考:浅谈 ASP.NET 的内部机制 系列,个人感觉超详细。

题外话: 说到请求管道,就想以前还是超菜鸟时有次面试被问到这个问题,一脸懵逼只说了 Controller→Action→View。脸红啊!!

ASP.NET Core 中的中间件就是.net framework 请求管道的实现。下图演示了 Middlerware 的概念。 沿黑色箭头执行。

每一个中间件(Middleware1、Middleware2...)都是一个<font color="#dd0000">委托</font>,这一系列委托就组成了整个管道。

中间件的写法

  1. 直接在Startup.cs类的Configure方法里写

    app.Use(async (context, next) =>
    {
        logger.LogInformation("中间件开始...");
        await next.Invoke(); //执行下一个中间件
        logger.LogInformation("中间件完成...");
    });
    

    结合上图:

    <font color="#e07070">//logic</font>对应<font color="#3c713d">logger.LogInformation("中间件开始...");</font>

    <font color="#e07070">next();</font>对应<font color="#3c713d">await next.Invoke();</font>

    <font color="#e07070">//more logic</font>对应<font color="#3c713d">logger.LogInformation("中间件完成...");</font>

    其中<font color="#e07070">//logic</font>(即请求)是顺序执行。即:Middleware1→Middleware2→...→Middleware<font color="#e07070">n</font>

    而<font color="#e07070">//more logic</font>(即响应)是倒序执行。即:Middleware<font color="#e07070">n</font>→...→Middleware2→Middleware1

  2. 同 1,只是不用 Use 而是用 Run:

     app.Run(async context =>
     {
         await context.Response.WriteAsync("请求终止了,下一步将会执行已执行过的Middleware的 //more logic");
     });
    

    Run 会终止请求,即管道中最后一个中间件,后面详细剖析!

  3. 下面这种写法应该是比较合理的,也是比较优雅的

    新建一个类如下(该类是有强制规范的,详细见下文):

    public class RequestTestMiddleware
    {
        private readonly RequestDelegate _next;
        public RequestTestMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            //中间件开始 logic
            await _next(context);//执行下一个中间件
            //中间件完成 more logic
        }
    }
    

    Startup.cs类的Configure方法里添加如下代码,效果和 1 相同:

    app.UseMiddleware<RequestTestMiddleware>();
    //app.UseMiddleware<RequestTestMiddleware>(params object[] parameters);//参数说明见下面
    

    不知发现了没,上面的InvokeAsync方法不是用的打印日志,而是用的注释。 因为我们没有引用logger对象,了解过 ASP.NET Core 的肯定知道依赖注入,我们只需要把ILogger注入进来就行了,改造如下:

    public class RequestTestMiddleware
     {
         private readonly RequestDelegate _next;
         public RequestTestMiddleware(RequestDelegate next)
         {
             _next = next;
         }
         public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
         {
             logger.LogInformation("中间件开始 logic");
             await _next(context);
             logger.LogInformation("中间件完成 more logic");
         }
     }
    
  4. 通过依赖注入方法添加中间件: 新建类 TestMiddleware.cs <font color="#e07070">注意依赖注入的位置和 3 不同</font>

    public class TestMiddleware : IMiddleware
     {
         private readonly ILogger _logger;
         public TestMiddleware(ILogger<TestMiddleware> logger)
         {
             _logger = logger;
         }
         public async Task InvokeAsync(HttpContext context, RequestDelegate next)
         {
             _logger.LogInformation("中间件开始");
             await next(context);
             _logger.LogInformation("中间件完成");
         }
     }
    

    Startup.cs类的ConfigureServices方法里添加如下代码:

    services.AddTransient<TestMiddleware>();
    

    Startup.cs类的Configure方法里添加如下代码:

    app.UseMiddleware<TestMiddleware>();
    
  5. 还有一种第三方容器激活中间件

源代码分析(部分)

  1. RunUse的实现

    直接放出源代码:

     public static void Run(this IApplicationBuilder app, RequestDelegate handler)
     {
         if (app == null)
         {
             throw new ArgumentNullException(nameof(app));
         }
         if (handler == null)
         {
             throw new ArgumentNullException(nameof(handler));
         }
         app.Use(_ => handler);
     }
    
     public static 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);
             };
         });
     }
    

    2 个方法最终调用的都是<font color="#e07070">app.Use()</font>,我们看下代码:

    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        _components.Add(middleware);
        return this;
    }
    

    _componentsIList<Func<RequestDelegate, RequestDelegate>>类型,其实就是把我们的Middleware添加到 _components 中,继续看代码:

    public RequestDelegate Build()
    {
        RequestDelegate app = context =>
        {
            context.Response.StatusCode = 404;
            return Task.CompletedTask;
        };
        foreach (var component in _components.Reverse())
        {
            app = component(app);
        }
        return app;
    }
    

    该方法会在Program.csMain方法的 CreateWebHostBuilder(args).Build().Run();Run() 方法执行。

    此方法把我们所有的Middleware再次组装成 1 个新的RequestDelegate,最终的顺序将会是:

    Middleware1()
    {
        next()=>Middleware2()
                {
                    next()=>Middleware3()
                            {
                                next()=>最后的那个返回404的委托
                            }
                }
    }
    

    不知道写清楚了没( ╯□╰ ). 其中<font color="#e07070">next()=>Middleware2()</font>的意思为:<font color="#e07070">next()</font>就是 <font color="#e07070">Middleware2()</font>

  2. 继承 IMiddleware 和没继承 IMiddleware(根据规范必须要有 InvokeAsync 或 Invoke 方法等)的区别:

    按功能实现方面来说是没区别的,但按性能方面应该是继承了 IMiddleware 的方式要好很多,因为没继承 IMiddleware 的方式会用到反射。(未测试,由于继承 IMiddleware 还需要用依赖注入这里只是猜测)

    代码见: Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法。

  3. 未继承 IMiddleware 时的约定,直接看代码吧:

    //1.在middleware中必须存在public且有返回值的方法
    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
    
    //2.必须有‘Invoke’或‘InvokeAsync’方法
    var invokeMethods = methods.Where(m =>
        string.Equals(m.Name, "Invoke", StringComparison.Ordinal)
        || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal)
        ).ToArray();
    
    //3.‘Invoke’和‘InvokeAsync’只能有1个
    if (invokeMethods.Length > 1) {}
    
    //4.‘Invoke’和‘InvokeAsync’必须要存在
    if (invokeMethods.Length == 0) {}
    var methodInfo = invokeMethods[0];
    
    //5.返回结果类型必须为Task
    if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){}
    
  4. 中间件传参 直接上代码:

    public class RequestTestMiddleware
    {
        private readonly RequestDelegate _next;
        private int _i;
        public RequestTestMiddleware(RequestDelegate next, int i)
        {
            _next = next;
            _i = i;
        }
        public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
        {
            logger.LogInformation($"通过参数传递i值:{_i}");
            logger.LogInformation("中间件开始");
            await _next(context);
            logger.LogInformation("中间件完成");
        }
    }
    

    Startup.cs类的Configure方法里:

    //参数类型为: params object[] args
    app.UseMiddleware<RequestTestMiddleware>(1);
    

    具体实现方式同样在 Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法中

高级用法 Map MapWhen

  1. Map

    app.Map("/map", _app =>
    {
        _app.Run(async context =>
        {
            await context.Response.WriteAsync("Test Map!");
        });
    });
    

    当访问https://localhost:5001/map时将返回 Test Map!

    这里说一下,代码中并没有 MapController....

  2. MapWhen

    app.MapWhen(context => context.Request.Query.ContainsKey("branch"), _app =>
    {
        _app.Run(async context =>
        {
            await context.Response.WriteAsync("Test Map!");
        });
    });
    

    看源代码会发现,MapWhen 的第二个参数(委托)并不是上面Use()next(),而是存在MapOptionsBranch属性中,也是RequestDelegate委托

其他说明

  1. Middleware 的执行的有顺序的,在合适的 Middleware 返回请求可时管道更短,速度更快。 比如 UseStaticFiles(),静态资源不必走验证、MVC 中间件,所以该方法在中间件的前面执行。

  2. 我们看到有很多内置的中间件的用法是*Use**,其实是加了个扩展:

    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestCulture(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestCultureMiddleware>();
        }
    }
    

总结

  第一次写博客,最大的感触就是<font color="#e07070">慢</font>,然后就是思维逻辑有点混乱,总想用最简单的语言来表达,就是掌握不好。最后看起来还是太啰嗦了点。最后说明,以上很可能有错误的说法,希望大家以批判的角度来看,有任何问题可在留言区留言!Thanks!

关于使用.NET Core的中间件“运行状况检查”是否比仅通过控制器路由执行ping操作具有优势?的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于.net 5.0 集成测试,未找到运行状况检查端点、.Net Core实现记录接口执行时间的中间件、ASP.NET Core 3.0中使用动态控制器路由、ASP.NET Core 中的中间件的相关知识,请在本站寻找。

本文标签: