文章目录

  • 介绍
  • 具体案例
    • 临时访问服务
    • 以委托形式定义中间件
    • 带参数中间件
    • IMiddleware中间件的用途
    • 让 HTTP 管道“短路”
    • 中间件的分支映射
    • 文件服务
  • 总结

介绍

随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。

本文主要介绍 .net core 相关的依赖注入和中间件案例。

具体案例

临时访问服务

【导语】

有些服务类型可能要在应用程序初始化的过程中临时使用,一般在 Main 方法中,比较经典的用途就是数据库初始化。在启动 WebHost 之前,程序代码可能要检查数据库是否存在,如果不存在就创建新的数据库,然后写入一些初始数据。

当应用程序在初始化过程中需要临时访问服务类型时,可调用 CreateScope 扩展方法(被扩展类型为 IServiceProvider)创建一个基于临时作用域的 IServiceScope 对象,然后在通过临时的 IServiceScope 对象获取服务类型的实例。由于服务类型访问完成后要释放掉 IServiceScope 对象,因此创建将 CreateScope 扩展方法的调用写在 using 代码块中,以便自动清理。

本实例将演示在应用程序初始化过程中,临时获取 IHostingEnvironment 访问实例,然后修改 ApplicationName 属性。由于 IHostingEnvironment 访问在容器注册的是单例实例,所以修改 ApplicationName 属性后,在应用程序的其他地方进行依赖注入也能获取 ApplicationName 属性的最新值。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:在 Main 方法中,使用 WebHost.CreateDefaultBuilder 方法快速创建 IWebHostBuilder,然后创建 WebHost 实例。

var builder = WebHost.CreateDefaultBuilder(args);
var host = builder.UseStartup<Startup>().Build();

步骤3:临时提取 IHostingEnvironment 实例,修改 ApplicationName 属性。

using(IServiceScope scope = host.Services.CreateScope())
{IHostingEnvironment env = scope.ServiceProvider.GetRequiredService<IHostingEnvironment>();env.ApplicationName = "【示例应用程序】";
}

步骤4:启动 WebHost 实例。

host.Run();

步骤5:在 Startup.Configure 方法中,可以通过依赖注入从参数中获取 IHostingEnvironment 实例,然后由 Response 对象向客户端回发响应消息,响应消息中包含 ApplicationName 属性的值。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{app.Run(async (context) =>{context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync($"正在运行{env.ApplicationName}");});
}

步骤6:运行应用程序,在浏览器中得到的返回结果如下。

以委托形式定义中间件

【导语】

应用程序对 HTTP 请求的处理过程进行划分,每个环境成为中间件,将各个中间件串联起来,就形成了 HTTP 管道,大致的流出可参考下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWPAGjAP-1620917686209)(./middle.png)]

执行中间件的顺序与它们添加到 HTTP 管道的顺序相同,即先添加的中间件会先执行,在 HTTP 管道中添加中间件有三种方法:

(1)委托。中间件专用的委托类型是 RequestDelegate,对应的方法结构就是带 HttpContext 类型的输入参数,并返回 Task 对象。一般来说,委托方式适用于代码较少、处理逻辑比较简单的中间件。

(2)基于约定的中间件。主要的约定在于类的方法,基于约定的中间件类型必须包含 InvokeInvokeAsync 方法,输入参数为一个 HttpContext 对象,并返回 Task 对象。中间件类可以通过构造函数的依赖注入来获取下一个中间件的 InvokeInvokeAsync 方法引用。

(3)实现 IMiddleware 接口。该接口同样包含 InvokeAsync 方法,需要HttpContext对象作为输入参数,并返回 Task 对象。用这种方式定义得到中间件需要在代码中显示将其添加到服务容器中,因此此种中间件的生命周期可以被改变(前面两种方式所定义的中间件都是单例模式,在应用程序生命周期内仅创建一次实例,而实现了 IMiddleware 接口的中间件在添加到服务容器时可以手动设置它的生命周期)。

在每个中间件的实现代码中都会通过输入参数获得下一个中间件的引用,这样开发人员可以灵活控制:是先执行当前中间件的代码,还是先执行下一个中间件的代码,或者不执行下一个中间件而直接向客户端返回响应消息。

本实例将演示通过委托的方式来定义中间件。实例创建了三个中间件,并在每个中间件的代码中产生一个字符串(临时存储在 HttpContext 对象的 Items 属性中),在最后一个中间件中,将三个字符串拼接并发回给客户端。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:在 Startup.Configure 方法中,调用Use扩展方法定义三个中间件。

app.Use(async (context, next) =>
{context.Items["line1"] = "第一环节,完成";await next();
})
.Use(async (context, next) =>
{context.Items["line2"] = "第二环节,完成";await next();
})
.Use(async (context, next) =>
{context.Items["line3"] = "第三环节,完成";// 拼接回应消息var parts = (from o in context.Items select o.Value.ToString()).AsEnumerable();string responseMessage = string.Join("<br/>", parts);// 设置编码context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync(responseMessage);await Task.CompletedTask;
});

第三个中间件的代码中,可以不调用 next 委托引用,因为该中间件已经是 HTTP 管道的最后一个环节了。

注意:Response.Write 方法一般是在最后一个中间件里面调用,即在所有的 HTTP 通信环节都处理完毕后才调用该方法。一旦调用 Write 方法,就开始向客户端回写消息了,这个会导致 HTTP 消息中某些内容无法被修改(例如 HTTP 头),进而引发异常,所以最佳做法是待所有环节都处理完成了再将消息回写到客户端。当然,任何中间件的代码中都可以同访问 HasStarted 属性来确定 HTTP 响应是否已经开始发回给客户端,如果值为 true,就不应该再修改 HTTP 头了。

步骤3:运行应用程序,浏览器输出的结果如下。

带参数中间件

【导语】

一般来说,中间件类的命名可以带上“Middleware”后缀,这虽然不是语法要求的,但是有规律的命名方式可以方便其他人识别该类是中间件。

中间件类的约定中必须包含 InvokeInvokeAsync 方法,此方法的声明如下:

public Task InvokeAsync(HttpContext context);
public Task Invoke(HttpContext context);

除了 HttpContext 类型的参数外,还可以在该方法的后面定义其他参数,而且参数支持依赖注入。

中间件允许使用参数,但并不是调用参数,而且仅能在中间件注册时使用,即在中间件生命周期内,参数只能传递一次。中间件的参数是通过构造函数传递的,即在定义中间件类的构造函数时,第一个参数是 HTTP 管道中下一个中间件的引用(RequestDelegate 委托),从第二个参数开始可以定义中间件的参数。

当在 Startup.Configure 方法中通过 UseMiddleware 方法注册中间件时,可以传递参数。如果中间件是基于约定所定义的类,那么传递的参数值尽量不要使用服务容器中非单实例模式的服务类型,因为这种中间件在应用程序运行期间只实例化一次,中间件实例始终会保留对参数的引用,这会使暂时服务或作用域服务的实例强制变成单实例服务。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:定义一个中间件类。

public class CalcuMiddleware
{readonly RequestDelegate _next;readonly int _a, _b;public CalcuMiddleware(RequestDelegate next, int a, int b){_next = next;_a = a;_b = b;}public async Task InvokeAsync(HttpContext context){int result = _a * _b;context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync($"{_a}×{_b}={result}");await _next(context);}
}

该中间件通过构造函数来接收两个 int 类型的参数,并在 InvokeAsync 方法中计算两个参数的乘积,并将结果回写到客户端。

步骤3:在 Startup.Configure 方法中注册中间件,并传递参数值。

app.UseMiddleware<CalcuMiddleware>(15, 7);

步骤4:运行应用程序,浏览器输出的结果如下。

IMiddleware中间件的用途

【导语】

中间件类一般的实现方法是使用约定形式(包含公共的 InvokeInvokeAsync 方法),有时也考虑实现 IMiddleware 接口,该接口的原型如下:

public interface IMiddleware
{Task InvokeAsync(HttpContext context, RequestDelegate next);
}

IMiddleware 接口也包含 InvokeAsync 方法,但它有两个参数,context 参数表示请求的上下文数据,next 表示下一个中间件的引用。

基于约定的中间件类在应用程序运行期间只创建单个实例,而实现了 IMiddleware 接口的中间件类的生命周期就可以灵活控制。IMiddleware 接口方式实现的中间件,在 Startup.Configure 方法中调用 UseMiddleware 方法之前,必须在 Startup.ConfigureServices 方法中进行注册,服务的注册可以选择三种生命周期:暂时服务、作用域服务和单一实例服务,可以通过不同的服务注册方式来控制中间件类的生命周期,例如,对于不太常用的中间件,可以注册为暂时服务,减少内存占用。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:定义中间件类 TestMiddleware,并让它实现 IMiddleware 接口。

public class TestMiddleware : IMiddleware
{public TestMiddleware(){Console.WriteLine($"类 {GetType().Name} 的构造函数被调用");}public async Task InvokeAsync(HttpContext context, RequestDelegate next){// 添加两个响应头context.Response.Headers["item 1"] = "hello";context.Response.Headers["item 2"] = "hi";// 写入响应消息context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync("欢迎来到主页");}
}

TestMiddleware 类的构造函数中会输出一行文本,因此如果中间件在应用程序运行期间被多次创建实例,那么每次实例化的时候都会在控制台中输出一行文本,通过查看控制台的输出内容就可以验证中间件是否被多次实例化。

步骤3:在 Startup.ConfigureServices 方法中,对 TestMiddleware 中间件进行注册。

public void ConfigureServices(IServiceCollection services)
{services.AddTransient<TestMiddleware>();
}

注意:通过实现 IMiddleware 接口来定义中间,必须先在服务容器中注册才能在 HTTP 管道中使用。

步骤4:在 Startup.Configure 方法中,使用自定义中间件。

app.UseMiddleware<TestMiddleware>();

步骤5:运行应用程序,在浏览器中打开 URL 后,进行多次刷新。可以发现,每次刷新后控制台中都会输出一行文本,表明 TestMiddleware 中间件创建了新实例。

让 HTTP 管道“短路”

【导语】

直接调用 IApplicationBuilderRun 扩展方法会使整个 HTTP 请求管道发生“短路”————直接把响应消息发回给客户端,终止此次 HTTP 通信。例如以下代码调用了三次 Run 方法:

app.Run(async context =>
{await context.Response.WriteAsync("Hello");
});app.Run(async context =>
{await context.Response.WriteAsync("My");
});app.Run(async context =>
{await context.Response.WriteAsync("Friends");
});

应用程序在运行的时候,只有第一个 Run 方法的调用会被执行,后面两次调用都不会被执行,只是因为遇到了 Run 方法,意味着整个 HTTP 请求管道将被终止,并且将处理结果直接发回给客户端,不管 Run 后面还有没有新的插入的中间件,都不会被执行。读者可以参考以下 Run 扩展方法的源码。

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);
}

可以看到,使 HTTP 管道“短路”的实现原理非常简单,就是在管道中插入一个中间件(即传递给Run方法的委托实例),一旦整个中间件执行之后,就不会去调用下一个中间件,从而使 HTTP 管道终结,即Run方法中添加的中间件成为 HTTP 请求处理流出中的最后环节。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:项目模板在 Startup.Configure 方法中已经生产了一条调用 Run 方法的代码语句。为了演示,可以对其做以下修改。

app.Run(async context =>
{context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync("你好,世界");
});

步骤3:以下代码也能实现与 Run 方法类似的效果。

app.Use(async (context, next) =>
{context.Response.ContentType = "text/html;charset=UTF-8";await context.Response.WriteAsync("你好,世界");
});

中间件的分支映射

【导语】

添加到 HTTP 管道中的中间件是默认响应根 URL 请求的,但在实际开发中,有时候需要在根 URL 下面通过子路径来区分不同的功能,即根据不同的子 URL 来调用不同的中间件。

分支映射有两种比较常见的使用场景:一种用法是错误处理,例如根URL是 http://abc.org,可以将 http://abc.org/errors 专用于错误处理,调用向客户端返回错误信息的中间件;另一种用法是 Web Socket,例如 http://abc.org/ws 分支可专用于 Web Socket 通信。

本实例将在根 URL 的基础上划分三个分支,当访问 /home 路径时返回文本“主页”,当访问 /about 路径时返回文件“关于本站”,访问 /news 路径时返回文本“新闻列表”。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:首先在 Startup.Configure 方法中定义一个 HTTP 管道主路上的中间件,作用是将回写文本的字符编码设置为 UTF-8

app.Use(async (context, next) =>
{context.Response.ContentType = "text/html;charset=UTF-8";await next();
});

注意:在设置完字符编码后,必须调用 next 委托,这样接下来的各个分支中的中间件才能被调用。因为如果不调用 next 委托,就会直接终结 HTTP 管道(与在主路上调用 Run 方法结果相同)。

步骤3:接下来是三个分支,分别调用 Run 方法向客户端返回文本内容, HTTP 管道中的处理过程结束。

app.Map("/home", _app =>
{_app.Run(async context =>{await context.Response.WriteAsync("主页");});
})
.Map("/about", _app =>
{_app.Run(async context =>{await context.Response.WriteAsync("关于本站");});
})
.Map("/news", _app =>
{_app.Run(async context =>{await context.Response.WriteAsync("新闻列表");});
});

步骤4:运行应用程序,可以分别输入以下 URL 来进行测试。

http://localhost:3125/home
http://localhost:3125/about
http://localhost:3125/news

文件服务

【导语】

当应用项目既需要浏览目录结构,又需要访问静态文件时,可以考虑使用文件服务功能。

UseFileServer 方法综合了 UseStaticFiles 方法和 UseDirectoryBrowser 方法的功能,参数可以通过 FileServerOptions 类来设置。

【操作流程】

步骤1:新建一个空白的 ASP.NET Core Web 应用程序项目。

步骤2:在项目模板生成的 wwwroot 目录下新建六个文本文件,并向每个文件中随意输入一些内容,这些文件仅用于稍后测试。

步骤3:在 Startup.ConfigureServices 方法中调用 AddDirectoryBrowser 方法。

public void ConfigureServices(IServiceCollection services)
{services.AddDirectoryBrowser();
}

步骤4:在 Startup.Configure 方法中调用 UseFileServer 方法,并配置好相关选项。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseFileServer(new FileServerOptions{FileProvider = new PhysicalFileProvider(env.WebRootPath),RequestPath = "/files",EnableDirectoryBrowsing = true});app.Run(async (context) =>{await context.Response.WriteAsync("Hello World!");});
}

注意:将 EnableDirectoryBrowsing 属性设置为 true 才会提供浏览目录的服务,如果不设置该属性,就相当于提供静态文件服务,不能浏览目录结构。

步骤5:运行应用程序,结果如下。

总结

本文到这里就结束了,下一篇将介绍应用配置和数据库访问的知识案例。

.net core精彩实例分享 -- 依赖注入和中间件相关推荐

  1. .net core精彩实例分享 -- 应用启动

    文章目录 介绍 具体案例 配置Web服务器的URL 配置Web项目的调试方案 基于方法约定的Startup类 使用非预定义环境 总结 介绍 随着.net core越来越流行,对.net core 基础 ...

  2. .net core精彩实例分享 -- 应用配置和数据库访问

    文章目录 介绍 具体案例 自定义环境变量的命名前缀 自定义命令行参数映射 使用JSON文件来配置选项类 在应用程序运行期间创建SQLite数据库 总结 介绍 随着.net core越来越流行,对.ne ...

  3. .net core精彩实例分享 -- 反射与Composition

    文章目录 介绍 具体案例 用Activator类创建类型实例 检查类型上所应用的自定义Attribute 通过协定来约束导出类型 导入多个类型 封装元数据 总结 介绍 随着.net core越来越流行 ...

  4. .net core精彩实例分享 -- 网络编程

    文章目录 介绍 具体案例 从Web服务器上下载图片 使用HttpClient类向Web服务器提交数据 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关 ...

  5. .net core精彩实例分享 -- 异步和并行

    文章目录 介绍 具体案例 等待线程信号--ManualResetEvent 等待线程信号--AutoResetEvent 多个线程同时写一个文件 串联并行任务 使用Parallel类执行并行操作 为每 ...

  6. .net core精彩实例分享 -- 序列化

    文章目录 介绍 具体案例 将类型实例序列号危机JSON格式 将数据协定序列化为JSON格式 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应 ...

  7. .net core精彩实例分享 -- 文件与I/O

    文章目录 介绍 具体案例 创建Zip压缩文件 使用GZipStream类压缩文件 实现本地进程之间的通信 单向管道通信 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了 ...

  8. .net core精彩实例分享 -- LINQ

    文章目录 介绍 具体案例 将对象转为字典集合 将原始序列进行分组 按员工所属部门 DefaultIfEmpty方法的作用 将分组后的序列重新排序 使用并行LINQ 总结 介绍 随着.net core越 ...

  9. .net core精彩实例分享 -- 泛型和集合

    文章目录 介绍 具体案例 限制泛型参数只能使用值类型 泛型参数的输入和输出 将抽象类作为类型约束 使用Span提升处理字符串的性能 多个Task同时操作ConcurrenBag集合 跨线程访问Bloc ...

最新文章

  1. linux系统被***后处理经历
  2. linux备忘录-vi和vim
  3. 开源怎么挣钱(转帖收藏)
  4. Spring框架集成mybatis框架的配置(笔记)
  5. ubuntu 安装星际译王词典
  6. ios 点生成线路 百度地图_iOS SDK | 百度地图API SDK
  7. maven 按业务拆分模块_gradle|springboot+gradle多模块化应用
  8. html密码框输入内容隐藏,密码框显示提示文字的功能实现
  9. 有哪些关于iPhone使用的小技巧?
  10. 在Linux 安装Python3.5.6详细文档!!!!
  11. Laravel 5.x 启动过程分析 [转]
  12. FFmpeg之x264/x265转码去掉B帧(二十六)
  13. Python之类的构造(面向对象)
  14. nyoj--496--巡回赛(拓扑排序)
  15. 管理感悟:不谈态度,只谈做法
  16. echarts使用_做数据可视化,为什么我们不再直接使用D3.js、Echarts
  17. iSlide系列教程视频简介——PPT的简化神器
  18. 汽车电子设计之SBC芯片简单认识
  19. C#实现百度地图附近搜索调用JavaScript函数
  20. 已解决在向有外键表插入数据提示“foreign key constraint fails”

热门文章

  1. 2017年12月计算机一级c,2017年12月计算机二级《C语言》强化模拟题(1)
  2. 如何插卡虚拟机 mysql_怎么在虚拟机中搭建mysql服务器
  3. 怎样通过vb设置透视表多项选择_四个操作带你玩转数据透视表,秒杀Excel函数,提升你的工作效率...
  4. formrules 表单验证限制最大值_HTML5表单
  5. android+5+镜像,1 下载AOSP(Android)镜像
  6. Adobe Illustrator的教程等距购物移动应用程序
  7. UI素材干货模板|线框图wireframe线框图iOS设计稿
  8. mysql exists依赖查询_MySQL EXISTS 和 NOT EXISTS 子查询
  9. C++ 虚函数,纯虚函数,抽象类整理
  10. Linux启动管理:主引导目录(MBR)结构及作用详解