一文说通Dotnet Core的中间件

前几天,公众号后台有朋友在问Core的中间件,所以专门抽时间整理了这样一篇文章。

一、前言

中间件(Middleware)最初是一个机械上的概念,说的是两个不同的运动结构中间的连接件。后来这个概念延伸到软件行业,大家把应用操作系统和电脑硬件之间过渡的软件或系统称之为中间件,比方驱动程序,就是一个典型的中间件。再后来,这个概念就泛开了,任何用来连接两个不同系统的东西,都被叫做中间件。

所以,中间件只是一个名词,不用太在意,实际代码跟他这个词,也没太大关系。

中间件技术,早在.Net framework时期就有,只不过,那时候它不是Microsoft官方的东西,是一个叫OWIN的三方框架下的实现。到了.Net core,Microsoft才把中间件完整加到框架里来。

感觉上,应该是Core参考了OWIN的中间件技术(猜的,未求证)。在实现方式上,两个框架下的中间件没什么区别。

下面,我们用一个实际的例子,来理清这个概念。

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13038419.html

二、开发环境&基础工程

这个Demo的开发环境是:Mac + VS Code + Dotnet Core 3.1.2。

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.201
 Commit:    b1768b4ae7

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

Host (useful for support):
  Version: 3.1.3
  Commit:  4a9f85e9f8

.NET Core SDKs installed:
  3.1.201 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

首先,在这个环境下建立工程:

  1. 创建Solution
% dotnet new sln -o demo
The template "Solution File" was created successfully.
  1. 这次,我们用Webapi创建工程
cd demo
% dotnet new webapi -o demo
The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
  Restore completed in 179.13 ms for demo/demo.csproj.

Restore succeeded.
  1. 把工程加到Solution中
% dotnet sln add demo/demo.csproj

基础工程搭建完成。

三、创建第一个中间件

我们先看下Demo项目的Startup.cs文件:

namespace demo
{
    public class Startup
    {

        public Startup(IConfiguration configuration)
        
{
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        /* This method gets called by the runtime. Use this method to add services to the container. */
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }

        /* This method gets called by the runtime. Use this method to configure the HTTP request pipeline. */
        Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

这是Startup默认生成后的样子(注意,不同的操作系统下生成的代码会略有不同,但本质上没区别)。

其中,Configure是中间件的运行定义,ConfigureServices是中间件的参数设置注入。

我们在Configure方法里,加入一个简单的中间件:

app.UseAuthorization();
/* 下面是加入的代码 */
app.Use(async (context, next) =>
{
    /* your code */
    await next.Invoke();
    /* your code */
});
/********************/
app.UseEndpoints(endpoints =>
{
        endpoints.MapControllers();
});

在这个代码中,app.Use是引入中间件的方式,而真正的中间件,是async (context,next),这是一个delegate方法。

中间件方法的两个参数,context是上下文HttpContextnext指向下一个中间件。

其中,next参数很重要。中间件采用管道的形式执行。多个中间件,通过next进行调用。

四、中间件的短路

在前一节,我们看到了中间件的标准形式。

有时候,我们希望中间件执行完成后就退出执行,而不进入下一个中间件。这时候,我们可以把await next.Invoke()从代码中去掉。变成下面的形式:

app.Use(async (context,1); word-wrap: inherit !important; word-break: inherit !important">/* your code */
});

对于这种形式,Microsoft给出了另一个方式的写法:

app.Run(async context =>
{
    /* your code */
});

这两种形式,效果完全一样。

这种形式,我们称之为短路,就是说在这个中间件执行后,程序即返回数据给客户端,而不执行下面的中间件。

五、中间件的映射

有时候,我们需要把一个中间件映射到一个Endpoint,用以对外提供简单的API处理。这种时间,我们需要用到映射:

app.Map("/apiname", apiname => {
    app.Use(async (context, next) =>
    {
        /* your code */
        await next.Invoke();
    });  
});

此外,映射支持嵌套:

"/router", router => {
        router.Map("/api1name", api1Name => {
        app.Use(async (context, next) =>
        {
            /* your code */
            await next.Invoke();
        }); 
        });
      router.Map("/api2name", api2Name => {
        app.Use(async (context, next) =>
        {
            /* your code */
            await next.Invoke();
        });     
    });
})

对于这两个嵌套的映射,我们访问的Endpoint分别是/router/api1name/router/api2name

以上部分,就是中间件的基本内容。

但是,这儿有个问题:为什么我们从各处文章里看到的中间件,好像都不是这么写的?

嗯,答案其实很简单,我们看到的方式,也都是中间件。只不过,那些代码,是这个中间件的最基本样式的变形。

下面,我们就来说说变形。

六、变形1: 独立成一个类

大多数情况下,我们希望中间件能独立成一个类,方便控制,也方便程序编写。

这时候,我们可以这样做:先写一个类:

TestMiddleware
{
    private readonly RequestDelegate _next;

    TestMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        /* your code */
        await _next.Invoke(context);
    }
}

这个类里contextnext和上面简单形式下的两个参数,类型和意义是完全一样的。

下面,我们把这个类引入到Configure中:

app.UseMiddleware<TestMiddleware>();

注意,引入Middleware类,需要用app.UseMiddleware而不是app.Use

app.Use引入的是方法,而app.UseMiddleware引入的是类。就这点区别。

如果再想高大上一点,可以做个Extensions:

static TestMiddlewareExtensions
{

    static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<TestMiddleware>();
    }
}

然后,在Configure中,就可以换成:

app.UseTestMiddleware();

看着高大上了有没有?

七、变形2: 简单引入参数

有时候,我们需要给在中间件初始化时,给它传递一些参数。

看类:

private readonly RequestDelegate _next;
    private static object _parameter

    (RequestDelegate next, object parameter)
    
{
        _next = next;
        _parameter = parameter;
    }

    (HttpContext context)
    {
        /* your code */
        await _next.Invoke(context);
    }
}

那相应的,我们在Configure中引入时,需要写成:

app.UseMiddleware<TestMiddleware>(new object());

同理,如果我们用Extensions时:

this IApplicationBuilder app, object parameter)
    {
        return app.UseMiddleware<TestMiddleware>(parameter);
    }
}

同时,引入变为:

app.UseTestMiddleware(new object());

八、变形3: 依赖注入参数

跟前一节一样,我们需要引入参数。这一节,我们用另一种更优雅的方式:依赖注入参数。

先创建一个interface:

public interface IParaInterface
{
    someFunction();
}

再根据interface创建一个实体类:

ParaClass : IParaInterface
{
    ()
    {
    }
}

参数类有了。下面建立中间件:

static IParaInterface _(HttpContext context)
    {
        /* your code */
        /* Example: _parameter.someFunction(); */

        await _next.Invoke(context);
    }
}

因为我们要采用注入而不是传递参数,所以Extensions不需要关心参数:

return app.UseMiddleware<TestMiddleware>();
    }
}

最后一步,我们在StartupConfigureServices中加入注入代码:

services.AddTransient<IParaInterface, ParaClass>();

完成 !

这个方式是Microsoft推荐的方式。

我在前文Dotnet core使用JWT认证授权最佳实践中,在介绍JWT配置时,实际使用的也是这种方式。

  1. 中间件
app.UseAuthentication();

这是Microsoft已经写好的认证中间件,我们只简单做了引用。

  1. 注入参数
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(option =>
            {
                option.RequireHttpsMetadata = false;
                option.SaveToken = true;

                var token = Configuration.GetSection("tokenParameter").Get<tokenParameter>();

                option.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
                    ValidIssuer = token.Issuer,
                    ValidateIssuer = false,
                    ClockSkew = TimeSpan.Zero,
                };
            });

这部分代码,是我们注入的参数设置。其中,几个方法又是Microsoft库提供的Builder。

Builder是注入参数的另一种变形。我会在关于注入和依赖注入中详细说明。

九、中间件的引入次序

中间件的引入次序,从代码上来说没有那么严格。就是说,某些类型的中间件交换次序不会有太大问题。

一般来说,使用中间件的时候,可以考虑以下规则:

  1. 实现Endpoint的中间件,应该放在最后,但要放在控制器引入的中间件之前。通常Endpoint中间件提供的是API或类似的内容,它会有Response的返回。而中间件在Response返回后,就不会调用Next了。
  2. 具有数据过滤内容的中间件,应该往前放,而且有个规则:当有过滤到规则外的情况时,应该越早返回越好。

以这两个规则来决定中间件的引入次序,就足够了。

(全文完)

 

 

微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了,百十行代码就展示了一个基本工作原理。然而,要将这样的解决方案运用到实际生产环境,还有很长的路要走。今天,我们就研究一下在事件处理器中,对象生命周期的管理问题。事实上,不仅仅是在事件处理器
上文已经介绍了Identity Service的实现过程。今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关。 回顾 《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)》 Weather API Weather
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序,并以Apache 2.0开源,开源地址是:https://github.com/daxnet/apworks-examples,目的是为了让大家更为方便地学习和使用.NET Core、最新的前端开发框架Angular,以及Apwork
HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度。很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅、通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现。接下来对于事件驱动型架构的讨论,就需要结合一个实际的架构案例来进行分析。在领域驱动设计的讨论范畴,CQRS架构本身就是事件驱动的,因此,
HAL,全称为Hypertext Application Language,它是一种简单的数据格式,它能以一种简单、统一的形式,在API中引入超链接特性,使得API的可发现性(discoverable)更强,并具有自描述的特点。使用了HAL的API会更容易地被第三方开源库所调用,并且使用起来也很方便
何时使用领域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你已经开始使用领域驱动设计了。领域驱动设计既不是架构风格(Architecture Style),也不是架构模式(Architecture Pattern),它也不是一种软件开发方法论,所以,是否应该使用领域驱动设计,以及什么时候使用
《在ASP.NET Core中使用Apworks快速开发数据服务》一文中,我介绍了如何使用Apworks框架的数据服务来快速构建用于查询和管理数据模型的RESTful API,通过该文的介绍,你会看到,使用Apworks框架开发数据服务是何等简单快捷,提供的功能也非常多,比如对Hypermedia的
在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证。在本讲中,我们会讨论在当前这种架构的应用程序中,如何完成用户授权。 回顾 《Angular SPA基于
Keycloak是一个功能强大的开源身份和访问管理系统,提供了一整套解决方案,包括用户认证、单点登录(SSO)、身份联合、用户注册、用户管理、角色映射、多因素认证和访问控制等。它广泛应用于企业和云服务,可以简化和统一不同应用程序和服务的安全管理,支持自托管或云部署,适用于需要安全、灵活且易于扩展的用
3月7日,微软发布了Visual Studio 2017 RTM,与之一起发布的还有.NET Core Runtime 1.1.0以及.NET Core SDK 1.0.0,尽管这些并不是最新版,但也已经从preview版本升级到了正式版。所以,在安装Visual Studio 2017时如果启用了
在上文中,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展一下设计,让我们自己设计的中间件变得更为通用,使其能够应用在不同的Route上。比如,我们可以设计一个通用的替换response body的中间件,然后将其应用在多个Route上。 O
不少关注我博客的朋友都知道我在2009年左右开发过一个名为Apworks的企业级应用程序开发框架,旨在为分布式企业系统软件开发提供面向领域驱动(DDD)的框架级别的解决方案,并对多种系统架构风格提供支持。这个框架的开发和维护我坚持了很久,一直到2015年,我都一直在不停地重构这个项目。目前这个项目在
好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ʺSP.NET Identity实现身份认证与授权》,然而如你所见,这样的名字实在是太长了。所以,我不得不缩写“单页面应用”几个字
在前面两篇文章中,我介绍了基于IdentityServer4的一个Identity Service的实现,并且实现了一个Weather API和基于Ocelot的API网关,然后实现了通过Ocelot API网关整合Identity Service做身份认证的API请求。今天,我们进入前端开发,设计
Ocelot是ASP.NET Core下的API网关的一种实现,在微服务架构领域发挥了非常重要的作用。本文不会从整个微服务架构的角度来介绍Ocelot,而是介绍一下最近在学习过程中遇到的一个问题,以及如何使用中间件(Middleware)来解决这样的问题。 问题描述 在上文中,我介绍了一种在Angu
在大数据处理和人工智能时代,数据工厂(Data Factory)无疑是一个非常重要的大数据处理平台。市面上也有成熟的相关产品,比如Azure Data Factory,不仅功能强大,而且依托微软的云计算平台Azure,为大数据处理提供了强大的计算能力,让大数据处理变得更为稳定高效。由于工作中我的项目
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容。下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了。其中绿色的部分就是上文中新实现的部分,包括一个简单的Event Store,一个事件处理器执行上下文的接
在之前《在ASP.NET Core中使用Apworks快速开发数据服务》一文的评论部分,.NET大神张善友为我提了个建议,可以使用Compile As a Service的Roslyn为语法解析提供支持。在此非常感激友哥给我的建议,也让我了解了一些Roslyn的知识。使用Roslyn的一个很大的好处
很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构。这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地订阅来自于某个渠道的事件消息,并对接收到的消息进行处理,于此同时,它还能够向该渠道发送事件消息,以便