ASP.NET Core 框架本质学习

本文作为学习过程中的一个记录。

学习文章地址:

https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html

一. ASP.NET Core 框架上的 Hello World程序

public class Program
{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app
=> app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}

WebHost :承载Web应用的宿主;

WebHostBuilder :WebHost的构建者;

而 在WebHostBuilder在调用 Build方法之前,调用的 两个方法:

UseKestrel :旨在注册一个名为Kestrel的服务器

Configure:为了注册一个用来处理请求的中间件

在上面的代码中,中间件在响应的主体内容中写入了一个 Hello World 的文本。

当我们在调用Run方法启动作为应用宿主的 WebHost的时候,WebHost会利用WebHostBuilder提供的服务器和中间件构建一个请求处理管道。 

而下面主要讲的就是 这个管道是如何被构建起来的,以及该管道采用怎样的请求处理流程。

640?wx_fmt=jpeg

二. 在我们自己的ASP.NET Core Mini上面开发的 Hello World

本文作为学习过程中的一个记录。

学习文章地址:

https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html

一. ASP.NET Core 框架上的 Hello World程序

public class Program
{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app
=> app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}

WebHost :承载Web应用的宿主;

WebHostBuilder :WebHost的构建者;

而 在WebHostBuilder在调用 Build方法之前,调用的 两个方法:

UseKestrel :旨在注册一个名为Kestrel的服务器

Configure:为了注册一个用来处理请求的中间件

在上面的代码中,中间件在响应的主体内容中写入了一个 Hello World 的文本。

当我们在调用Run方法启动作为应用宿主的 WebHost的时候,WebHost会利用WebHostBuilder提供的服务器和中间件构建一个请求处理管道。 

而下面主要讲的就是 这个管道是如何被构建起来的,以及该管道采用怎样的请求处理流程。

640?wx_fmt=jpeg

二. 在我们自己的ASP.NET Core Mini上面开发的 Hello World

640?wx_fmt=png


代码说明:

在创建出 WebHostBuilder 之后,我们调用了它的扩展方法 UseHttpListener 注册了一个自定义的基于 HttpListener的服务器;

随后针对 Configure 方法的调用中,我们注册了三个中间件。

由于中间件最终是通过 Delegate对象来体现的,所以我们可以将中间件定义成与Delegate类型具有相同签名的方法。

程序运行后,得到的输出结果:

640?wx_fmt=jpeg

三. 自定义的ASP.NET Core Mini框架讲解

下面主要是对 ASP.NET Core Mini框架的构建过程中关键部分的讲解。

主要涉及 HttpContext、RequestDelegate、Middleware、ApplicationBuilder、Server、WebHost、WebHostBuilder 等七个对象;

另外 会讲到 HttpContext与Server之间的适配;HttpListenerServer等;

1. 第一个对象:HttpContext

关于 HttpContext的本质,还得从请求处理管道的层面来讲。

对于由一个服务器和多个中间件构建的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应;

当它接收到客户端发送的请求后,需要将它分发给后续中间件进行处理。

对于某个中间件来说,当我们完成了自身的请求处理任务之后,在大部分情况下,也需要将请求分发给后续的中间件。

请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。

640?wx_fmt=jpeg

( 如上图,当服务器接收到请求之后,会创建一个通过HttpContext表示的上下文对象,所有中间件都是在这个上下文中处理请求的;

那么一个HttpContext对象究竟携带了怎样的上下文信息呢?

我们知道一个HTTP事务具有非常清晰的界定,即接收请求、发送响应;

所以请求和响应是两个基本的要素,也是HttpContext承载的最核心的 上下文信息。)

故,HttpContext的核心要素:请求和响应


640?wx_fmt=png


2. 第二个对象:RequestDelegate

这是一个委托,也需要从管道的角度才能充分理解这个委托对象的本质。

 2.1 管道的设计

可以总结为 Pipeline = Server + Middlewares  ,再精简写的话,可以写为 Pipeline = Server + HttpHandler . 

640?wx_fmt=jpeg

2.2 那么,我们如何来表达HttpHandler呢?

既然针对当前请求的所有输入和输出都通过HttpContext来表示,那么 HttpHandler就可以表示成一个 Action<HttpContext>对象。

那么HttpHandler在ASP.NET Core中时通过 Action<HttpContext>来表示的吗?

其实不是的,原因很简单:Action<HttpContext>只能表示针对请求的 同步的处理操作,但是针对 HTTP 请求既可以是同步的,也可以是异步的,更多的其实是异步的。

那么在 .NET Core的世界中如何来表示一个同步或者异步操作呢?就是Task对象,那么 HttpHandler自然可以表示为一个 Func<HttpContext,Task>对象。

由于这个委托对象实在太重要了,所以我们将它定义成一个独立的类型:delegate Task RequestDelegate(HttpContext context) 。

640?wx_fmt=jpeg

3. 第三个对象:Middleware

中间件在ASP.NET Core 中被表示成一个 Func<RequestDelegate,RequestDelegate>对象,即它的输入和输出都是一个RequestDelegate。

640?wx_fmt=jpeg

为什么采用一个Func<RequestDelegate,RequestDelegate>对象来表示中间件。是因为这样的考虑:

对于管道中的某一个中间件来说,由后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将由后续中间件构成的RequestDelegate作为输入。

即:上一个中间件的输出需要可以作为下一个中间件的输入,所以设计为Func<RequestDelegate,RequestDelegate>对象

4. 第四个对象:ApplicationBuilder

ApplicationBuilder 是用来构建 Application的。

既然 Pipeline = Server + HttpHandler , 可以看出HttpHandler承载了当前应用的所有职责,那么 HttpHandler就等于 Application。

由于 HttpHandler通过RequestDelegate表示,那么由ApplicationBuilder构建的Application就是一个RequestDelegate对象。(职责1)

640?wx_fmt=jpeg

由于表示HttpHandler的RequestDelegate是由注册的中间件来构建的,所以ApplicationBuilder还具有注册中间件的功能。(职责2)

基于ApplicationBuilder具有的这两个基本职责,我们可以将对应的接口定义为如下形式。

Use 方法用来注册提供的中间件,Builder方法则将注册的中间件构建成一个RequestDelegate对象。

public interface  IApplicationBuilder
{
IApplicationBuilder Use(Func
<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}

下面是针对这个接口的具体实现。

我们用一个列表保存注册的中间件,所以Use方法只需要将提供的中间件添加到这个列表中即可。

当Build方法被调用后,我们只需要按照与注册相反的顺序依次执行表示中间件的Func<RequestDelegate,RequestDelegate>对象,就能最终构建出代表HttpHandler的RequestDelegate对象。


640?wx_fmt=png


在调用第一个中间件(最后注册)的时候,我们创建了一个RequestDelegate作为输入,后者会将响应状态码设置为404。

所以如果ASP.NET Core应用在没有注册任何中间件的情况下,总是返回一个404响应。

如果所有中间件在完成了自身的请求处理任务之后都选择将请求向后分发,同样会返回一个404响应。

总结:对于上面的四个对象,从后向前依次对前一个进行包装。

5. 第五个对象:Server

当我们运行(Run)作为应用宿主的WebHost的时候,服务器它被自动启动。

启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建出代表上下文的HttpContext对象,

并将该上下文作为输入,调用由所有注册中间件构建而成的RequestDelegate对象。

640?wx_fmt=jpeg

简单起见,我们使用如下简写的IServer接口来表示服务器。

我们通过定义在IServer接口的唯一方法 StartAsync启动服务器,

作为参数的 handler 正是由所有中间件共同构建而成的RequestDelegate对象

public interface IServer
{
Task StartAsync(RequestDelegate handler);
}

6. HttpContext和Server之间的适配

面向应用层的HttpContext对象是对请求和相应的封装,但是请求最初来源于服务器,针对HttpContext的任何响应操作也必须作用于当前的服务器才能真正起作用。

现在问题来了,所有的ASP.NET Core应用使用的都是同一个HttpContext类型,但是却可以注册不同类型的服务器,我们必须解决两者之间的适配问题。

640?wx_fmt=jpeg

同一个HttpContext类型与不同服务器类型之间的适配可以通过添加一个抽象层来解决,我们定义该层的对象为Feature。

如上图,我们可以定义一系列的Feature接口来为HttpContext提供上下文信息,其中最重要的就是提供请求的 IRequestFeature和完成响应的IResponseFeature接口。

那么具体的服务器只需要实现这些Feature接口就可以了。

640?wx_fmt=jpeg

下面是一些代码片段。我们定义了一个IFeatureCollection接口来表示存放Feature对象的集合。

为了编程上的便利,我们定义了两个扩展方法 Set<T>和Get<T>来设置和获取Feature对象。


640?wx_fmt=png


如下,用来提供请求的IHttpRequestFeature和提供响应IHttpResponseFeature接口的定义,可以看出它们具有和HttpRequest和HttpResponse完全一致的成员定义。

640?wx_fmt=png


接下来,我们来看看HttpContext的具体实现。

ASP.NET Core Mini的HttpContext只包含Request和Response两个属性成员,对应的类型分别为HttpRequest和HttpResponse,下面是这两个类型的具体实现。

其中,HttpRequest和HttpResponse都是通过一个IFeatureCollection对象构建而成的,它们对应的属性成员均由包含在这个Feature集合中的IHttpRequestFeature和IHttpResponseFeature对象来提供。

640?wx_fmt=png


HttpContext的实现就更加简单了。我们在创建一个HttpContext对象时同样会提供一个IFeatureCollection对象,

我们利用该对象创建对应的HttpRequest和HttpResponse对象,并作为其对应的属性值。

640?wx_fmt=png


总结:在HttpContext中传入 IFeatureCollection对象,HttpContext中的成员对象HttpRequest和HttpResponse会利用这个IFeatureCollection来被构建。从而完成HttpContext的构建。当然,其中少不了需要Server部分,下面会讲。

7. HttpListenerServer 服务器

在对服务器和它与HttpContext的适配原理有清晰的认识之后,我们尝试着定义一个服务器。

在前面,我们利用WebHostBuilder的扩展方法UseHttpListener注册了一个HttpListenerServer,我们现在来看看这个采用HttpListener作为监听器的服务器类型是如何实现的。

由于所有的服务器都需要有自己的Feature实现来为HttpContext提供对应的上下文信息,所以我们得先来为HttpListenerServer定义相应的接口。

对HttpListener监听器稍微了解的朋友应该知道它在接收到请求之后同时会创建一个自己的上下文对象,对应的类型为HttpListenerContext。

如果采用HttpListenerServer作为应用的服务器,意味着HttpContext承载的上下文信息最初来源于这个HttpListenerContext,所以Feature的目的旨在解决这两个上下文之间的适配问题。

总结:Feature实际上解决的就是HttpContext和HttpListenerContext之间的适配问题。

下面的HttpListenFeature就是我们为HttpListenerServer定义的Feature。HttpListenerFeature同时实现了IHttpRequestFeature和IHttpResponseFeature,实现的 6 个属性最初都来源于创建该对象提供的HttpListenerContext对象。

640?wx_fmt=png


当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时我们只需要据此创建一个HttpListenerFeature对象,

并且它分别以IHttpRequestFeature和IHttpResponseFeature接口类型注册到创建FeatureCollection集合上。

我们最终利用这个FeatureCollection对象创建出代表上下文的HttpContext,然后将它作为参数调用由所有中间件公共构建的RequestDelegate对象即可。

8. 第六个对象:WebHost

到目前为止,我们已经知道了由一个服务器和多个中间件构成的管道是如何完整针对请求的监听、接收、处理和最终响应的,接下来讨论这样的管道是如何被构建出来的。

管道是在作为应用宿主的WebHost对象启动的时候被构建出来的,在ASP.NET Core Mini 中,

我们将表示应用宿主的IWebHost接口简写成如下形式:

只包含一个StartAsync方法来启动应用程序。

public interface IWebHost
{
Task StartAsync();
}

由于由WebHost构建的管道由Server和HttpHandler构成,我们在默认实现的WebHost类型中,我们直接提供这两个对象。

在实现的StartAsync方法中,我们只需要将后者作为参数调用前者的StartAsync方法将服务器启

640?wx_fmt=png


9. 第七个对象:WebHostBuilder

WebHost的作用:就是创建作为应用宿主的WebHost。

由于在创建WebHost的时候,需要提供注册的服务器和由所有注册中间件构建而成的RequestDelegate,

所以在对应接口IWebHostBuilder中,我们为它定义了三个核心方法。

public interface IWebHostBuilder
{
IWebHostBuilder UseServer(IServer server);
IWebHostBuilder Configure(Action
<IApplicationBuilder> configure);
IWebHost Build();
}

除了用来创建WebHost的Build方法之外,我们提供了用来注册服务器的UseServer方法和用来注册中间件的Configure方法。

Configure方法提供了一个类型为 Action<IApplicationBuilder>的参数,

意味着我们针对中间件的注册时利用上面介绍的IApplicationBuilder对象来完成的。

如下代码,WebHostBuilder是针对IWebHostBuilder接口的默认实现,

它具有两个字段分别用来保存注册的中间件和调用Configure方法提供的Action<IApplicationBuilder>对象。

当Build方法被调用之后,我们创建一个ApplicationBuilder对象,并将它作为参数调用这些Action<IApplicationBuilder>委托,

进而将所有中间件全部注册到这个ApplicationBuilder对象上。

我们最终调用它的Build方法得到所有中间件共同构建的RequestDelegate对象,并利用它和注册的服务器构建作为应用宿主的WebHost对象。

640?wx_fmt=png


至此,本篇结束。

补充:

这里补充的是按照这里的学习,实现这个程序的过程。

1. 首先 创建 Feature.cs 文件

里面定义了请求和响应里面需要的一些内容。

2. 创建 FeatureCollection.cs 文件

这个应该就是 用来装 请求和响应 的。

注:1,2两个文件中的接口或者类,不依赖其他自定义类

3. 创建 HttpContext.cs 文件


这个里面定义了 请求实体类,响应实体类,共享上下文(HttpContext), 以及响应的扩展方法输出内容。

4.创建 RequestDelegate.cs 文件


这个用来承载 共享上下文 HttpContext 的。

5. 创建 ApplicationBuilder.cs 文件


这个是用来承载 RequestDelegate 的,并且存放中间件及使之串起来的。

6. 创建 IServer.cs 文件


这个是用来定义服务器接口的。

7.创建 HttpListenerFeature.cs 文件


我们的共享上下文信息最初来源于这个类中的 HttpListenerContext。它是在服务器中,用来接收 HttpListener 中的上下文对象的(即HttpListenerContext)。

这里把 它按 请求和响应的接口定义进行拆分。

8. 创建 HttpListenerServer.cs 文件


这个是服务器文件,其中定义了监听器,监听的url集合,及启动监听,创建共享上下文,及使用RequestDelegate类型的处理器承载 上下文等操作。

9. 创建 WebHost.cs 文件


这里是用来创建宿主的,用来把服务器和中间件服务连接起来的。

10. 创建 WebHostBuilder.cs 文件


这个是宿主构建者,用来设置服务器,配置中间件,以及 Build 出宿主WebHost的。

11. 最后,创建 Program.cs 文件


这其中包括 创建宿主构建者,设置服务器,配置中间件, Build成宿主,及启动宿主等。

代码结束!!

12. 上面的代码实际已经结束了,但是发现编译的时候报错。

C# 7.0 不支持Program.cs中的用法。

怎么修改呢?

右键项目---> 属性----> 生成 ----> 高级 ----> 

然后在 常规 下的语言版本中,选择 c#最新次要版本 

如下

640?wx_fmt=png

13. 重新编译,成功

14. 运行,然后在 浏览器中 输入 http://localhost:5000

可以在代码中打上断点,观察执行过程。

这里,把我自己写的也上传到github了,方便自己查阅,有疑问的小伙伴可以自己去原文学习

我的github地址:https://github.com/Vincent-yuan/asp.net_core_mini

原文链接:https://www.cnblogs.com/Vincent-yuan/p/11318718.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于C#实现的轻量级多线程队列

工作中我们经常会遇到一些一些功能需要实现造作日志&#xff0c;数据修改日志&#xff0c;对于这种业务需求如果我们以同步的方式实现&#xff0c;难免会影响到系统的性能。如下我列出集中解决方案。使用Thread异步处理。使用线程池或Task异步处理。以上两种方案确实能解决我们…

【活动】厦门.NET俱乐部 省上云开发者专场

十年磨一剑&#xff0c;厦门.NET俱乐部诚挚邀请您相约软件园二期创驿站&#xff0c;参加云重启|厦门.NET俱乐部省上云开发者专场。活动干货满满&#xff0c;更有精美礼品&#xff0c;厦门.NET俱乐部期待与您“厦门论剑”。详情请点击图片或直接阅读原文报名

腾讯物联TencentOS tiny上云初探

2017年中旬曾写过一篇关于物联网平台的文章《微软最完善&#xff0c;百度最“小气” 看微软阿里百度三大物联网云平台对比》。现在已经过去两年了&#xff0c;物联网的格局又发生了不少的变化。不过针对腾讯来说&#xff0c;其物联网平台发轫的时间绝不算晚&#xff0c;基本就是…

ASP.NET Core on K8S深入学习(3-2)DaemonSet与Job

本篇已加入《.NET Core on K8S学习实践系列文章索引》&#xff0c;可以点击查看更多容器化技术相关系列文章。上一篇《3-1 Deployment》中介绍了Deployment&#xff0c;它可以满足我们大部分时候的应用部署&#xff08;无状态服务类容器&#xff09;&#xff0c;但是针对一些特…

Asp.Net Core WebAPI+PostgreSQL部署在Docker中

PostgreSQL是一个功能强大的开源数据库系统。它支持了大多数的SQL:2008标准的数据类型&#xff0c;包括整型、数值值、布尔型、字节型、字符型、日期型、时间间隔型和时间型&#xff0c;它也支持存储二进制的大对像&#xff0c;包括图片、声音和视频。PostgreSQL对很多高级开发…

博客园升级有感一点建议

实践出真知这几天在园子里面最热闹的事情各位都知道吧&#xff1f;没错&#xff0c;我说的就是博客园升级事件&#xff0c;有不熟悉的朋友吗&#xff0c;没关系&#xff0c;我给你搬运好了&#xff0c;请回顾一下Powered by .NET Core 系列博文&#xff1a;【故障公告】发布 .N…

.Net Core2.1 秒杀项目一步步实现CI/CD(Centos7)系列二:k8s高可用集群搭建总结以及部署API到k8s...

前言&#xff1a;本系列博客又更新了&#xff0c;是博主研究很长时间&#xff0c;亲自动手实践过后的心得&#xff0c;k8s集群是购买了5台阿里云服务器部署的&#xff0c;这个集群差不多搞了一周时间&#xff0c;关于k8s的知识点&#xff0c;我也是刚入门&#xff0c;这方面的知…

使用Asp.net Core3Blazor 的全栈式网站开发体验

最新的微软视频&#xff1a; Full stack web development with ASP.NET Core 3.0 and Blazor - BRK3017 以下是重要步骤截图配注解&#xff0c;注意图多杀猫&#xff1a;此图是.Net Core3的全栈解决方案示意图。话说此图的第一部分Client 是可以灵活替换的&#xff0c;哪怕它是…

k8s集群部分常见问题处理

目录部分常见问题处理Coredns CrashLoopBackOff 导致无法成功添加工作节点的问题添加工作节点时提示token过期kubectl 执行命令报“The connection to the server localhost:8080 was refused”网络组件flannel无法完成初始化部分节点无法启动pod最后部分常见问题处理结合我们上…

Hyper-V + CentOS7 网络设置(视频教程)

Hyper-V Centos7 网络设置 本文目标&#xff1a;1、 设置虚拟机固定IP&#xff1a;无论物理机的网络环境怎么变化&#xff0c;都需要保持虚拟机的IP地址不变&#xff0c;保证本机使用xshell等终端访问始终用同一个IP地址2、物理机可访问虚拟机&#xff0c;虚拟机是否可访问网络…

Consul的反熵

熵熵是衡量某个体系中事物混乱程度的一个指标&#xff0c;是从热力学第二定律借鉴过来的。熵增原理孤立系统的熵永不自动减少&#xff0c;熵在可逆过程中不变&#xff0c;在不可逆过程中增加。熵增加原理是热力学第二定律的又一种表述&#xff0c;它更为概括地指出了不可逆过程…

通过Blazor使用C#开发SPA单页面应用程序(2)

今天我们尝试创建一个默认的Blazor应用。.Net Core 3.0需要Visual Studio 2019 的支持。安装.Net Core 3.0 预览版 SDK版本&#xff0c;注意预览版对应的VS版本&#xff0c;我这里安装的是v3.0.0-preview6。一定要开启预览选项才能使用Net Core Preview&#xff0c;在工具> …

通过Blazor使用C#开发SPA单页面应用程序(1)

2019年9月23——25日 .NET Core 3.0即将在.NET Conf上发布! .NET Core的发布及成熟重燃了.net程序员的热情和希望&#xff0c;一些.net大咖也在积极的为推动.NET Core而不懈的努力。在这次.NET Core 3.0中一项新的技术也首次出现在人们的视野&#xff0c;这就是Blazor。说起Bla…

基于 WPF 模块化架构下的本地化设计实践

背景描述最近接到一个需求&#xff0c;就是要求我们的 WPF 客户端具备本地化功能&#xff0c;实现中英文多语言界面。刚开始接到这个需求&#xff0c;其实我内心是拒绝的的&#xff0c;但是没办法&#xff0c;需求是永无止境的。所以只能想办法解决这个问题。首先有必要说一下我…

你会轻易打破规则吗?

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「86」篇原创敬上俗话说的好&#xff0c;不以规矩&#xff0c;不成方圆。但是有些时候&#xff0c;可能破坏规则反而是一个更有效的方式&#xff0c;这个时候到底该…

架构杂谈《十》

常用开发模式一、瀑布式开发瀑布式开发是在1970年提出的软件开发模型&#xff0c;是一种较老的计算机软件开发模式&#xff0c;也是典型的预见性的开发模式&#xff0c;在瀑布式开发中&#xff0c;开发严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤进行&…

如何删除GIT仓库中的敏感信息

1. 前言正常Git仓库中应该尽量不包含数据库连接/AWS帐号/巨大二进制文件&#xff0c;否则一旦泄漏到Github&#xff0c;这些非常敏感信息会影响客户的信息安全已经公司的信誉。公司可能其它还有相关规定&#xff0c;如禁止私人邮件加入GIT仓库。如果违反这些规定&#xff0c;可…

ASP.NET Core on K8S深入学习(4)你必须知道的Service

本篇已加入《.NET Core on K8S学习实践系列文章索引》&#xff0c;可以点击查看更多容器化技术相关系列文章。前面几篇文章我们都是使用的ClusterIP供集群内部访问&#xff0c;每个Pod都有一个自己的IP地址&#xff0c;那么问题来了&#xff1a;当控制器使用新的Pod替代发生故障…

博客园翻车启示录

开发者的日常作为一名996的开发者&#xff0c;我几乎每天只有两件事&#xff0c;制造bug和解决bug&#xff0c;这两件事&#xff0c;既替我解决了温饱问题、也替产品经理、测试工程师等一票人解决了吃穿问题。嗯&#xff0c;有人为我这种程序员评了一个等级&#xff0c;我大概是…

asp.net core 从单机到集群

asp.net core 从单机到集群Intro这篇文章主要以我的活动室预约的项目作为示例&#xff0c;看一下一个 asp.net core 应用从单机应用到集群部署需要做什么。示例项目活动室预约提供了两个版本&#xff0c;集群版和 单机版单机版方便部署&#xff0c;不依赖其他环境&#xff0c;数…