ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件
虽然ASP.NET Core是一款“动态”的Web服务端框架,但是在很多情况下都需要处理针对静态文件的请求,最为常见的就是这对JavaScript脚本文件、CSS样式文件和图片文件的请求。针对不同格式的静态文件请求的处理,ASP.NET Core为我们提供了三个中间件,它们将是本系列文章论述的重点。不过在针对对它们展开介绍之前,我们照理通过一些简单的实例来体验一下如何在一个ASP.NET Core应用中发布静态文件。[本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、以Web的形式读取文件
二、浏览目录内容
三、显示默认页面
四、映射媒体类型
一、以Web的形式读取文件
我们创建的演示实例是一个简单的ASP.NET Core控制台应用,它具有如下图所示的项目结构。我们可以看到在默认作为WebRoot的目录(wwwroot)下,我们将JavaScript脚本文件、CSS样式文件和图片文件存放到对应的子目录(js、css和img)下,我们将把这个目录的所有文件以Web的形式发布出来,客户端可以访问相应的URL来获取这些文件。

针对静态文件的请求是通过一个名为StaticFileMiddleware的中间件来实现的,这个中间件类型定义在NuGet包“Microsoft.AspNetCore.StaticFiles”中,所以我们需要预先按照这个NuGet包。整个应用只包含如下所示的这几行代码,StaticFileMiddleware这个中间件的注册是通过调用ApplicationBuilder的扩展方法UseStaticFiles来完成的。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseContentRoot(Directory.GetCurrentDirectory())
7: .UseKestrel()
8: .Configure(app => app.UseStaticFiles())
9: .Build()
10: .Run();
11: }
12: }
除了注册必需的StaticFileMiddleware中间件之外,我们还调用了WebHostBuilder的UseContentRoot方法将当前项目的根目录作为ContentRoot目录。我们知道ASP.NET Core应用具有两个重要的根目录,它们分别是ContentRoot和WebRoot,后者也是对外发布的静态文件默认使用的根目录。由于WebRoot目录的默认路径就是“{contentroot}/wwwroot”,所示上面这段程序就是将项目中的这个wwwroot目录下的所有静态文件发布出来。
当这个程序运行之后,我们就可以通过向对应URL发送HTTP请求的方式来获取某个的文件,这个URL由文件相当于wwwroot目录的路径来决定。比如JPG文件“~/wwwroot/img/dophin1.jpg”对应的URL为“http://
localhost:5000/img/dophin1.jpg”。我们直接利用浏览器访问这个URL,目标图片会直接显示出来。

上面我们通过一个简单的实例将WebRoot所在目录下的所有静态文件直接发布出来。如果我们需要发布的静态文件存储在其他目录下呢?依旧是演示的这个应用,现在我们将一些文档存储在如下图所示的“~/doc/”目录下并以Web的形式发布出来,我们的程序又该如何编写呢?

我们知道ASP.NET Core应用大部分情况下都是利用一个FileProvider对象来读取文件的,它在处理针对静态文件的请求是也不例外。对于我们调用ApplicationBuilder的扩展方法UseStaticFiles方法注册的这个类型为StaticFileMiddleware的中间件,其内部具有一个FileProvider和请求路径的映射关系。如果调用UseStaticFiles方法没有指定任何的参数,那么这个映射关系的请求路径就是应用的基地址(PathBase),而FileProvider自然就是指向WebRoot目录的PhysicalFileProvider。
上述的这个需求可以通过显式注册这个映射的方式来实现,为此我们在现有程序的基础上额外添加了一次针对UseStaticFiles方法的调用,并通过指定的参数(是一个StaticFileOptions对象)显式指定了采用的FileProvider(针对“~/doc/”的PhysicalFileProvider)和请求路径(“/documents”)。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: new WebHostBuilder()
7: .UseContentRoot(contentRoot)
8: .UseKestrel()
9: .Configure(app => app
10: .UseStaticFiles()
11: .UseStaticFiles(new StaticFileOptions {
12: FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc")),
13: RequestPath = "/documents"
14: }))
15: .Build()
16: .Run();
17: }
18: }
按照上面这段程序指定的映射关系,对于存储在“~/doc/”目录下的这个PDF文件(“checklist.pdf”),发布在Web上的URL为“http://localhost:5000/documents/checklist.pdf”。当我们在浏览器上请求这个地址时,该PDF文件的内容将会按照如下图所示的形式显示在浏览器上。

二、浏览目录内容
注册的StaticFileMiddleware中间件只会处理针对某个具体静态文件的额请求,如果我们向针对某个目录的URL发送HTTP请求(比如“http://localhost:5000/img/”),得到的将是一个状态为404的响应。不过我们可以通过注册另一个名为DirectoryBrowserMiddleware的中间件来显示请求目录的内容。具体来说,这个中间件会返回一个HTML页面,请求目录下的所有文件将以表格的形式包含在这个页面中。对于我们演示的这个应用来说,我们可以按照如下的方式调用UseDirectoryBrowser方法来注册这个DirectoryBrowserMiddleware中间件。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(
7: Path.Combine(contentRoot, "doc"));
8: new WebHostBuilder()
9: .UseContentRoot(contentRoot)
10: .UseKestrel()
11: .Configure(app => app
12: .UseStaticFiles()
13: .UseStaticFiles(new StaticFileOptions {
14: FileProvider = fileProvider,
15: RequestPath = "/documents"
16: })
17: .UseDirectoryBrowser()
18: .UseDirectoryBrowser(new DirectoryBrowserOptions {
19: FileProvider = fileProvider,
20: RequestPath = "/documents"
21: }))
22: .Build()
23: .Run();
24: }
25: }
当上面这个应用启动之后,如果我们利用浏览器向针对某个目录的URL(比如“http://localhost:5000/”或者“http://localhost:5000/img/”),目标目录的内容(包括子目录和文件)将会以下图所示的形式显示在一个表格中。不仅仅如此,子目录和文件均会显示为链接,指向目标目录或者文件的URL。

三、显示默认页面
从安全的角度来讲,利用注册的UseDirectoryBrowser中间件显示一个目录浏览页面会将整个目标目录的接口和所有文件全部暴露出来,所以这个中间件需要根据自身的安全策略谨慎使用。对于针对目录的请求,另一种更为常用的响应策略就是显示一个保存在这个目录下的默认页面。按照约定,作为默认页面的文件一般采用如下四种命名方式:default.htm、default.html、index.htm或者index.html。针对目标目录下默认页面的呈现实现在一个名为DefaultFilesMiddleware的中间件中,我们演示的这个应用可以按照如下的方式调用UseDefaultFiles方法来注册这个中间件。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc"));
7:
8: new WebHostBuilder()
9: .UseContentRoot(contentRoot)
10: .UseKestrel()
11: .Configure(app => app
12: .UseDefaultFiles()
13: .UseDefaultFiles(new DefaultFilesOptions{
14: RequestPath = "/documents",
15: FileProvider = fileProvider,
16: })
17: .UseStaticFiles()
18: .UseStaticFiles(new StaticFileOptions
19: {
20: FileProvider = fileProvider,
21: RequestPath = "/documents"
22: })
23: .UseDirectoryBrowser()
24: .UseDirectoryBrowser(new DirectoryBrowserOptions
25: {
26: FileProvider = fileProvider,
27: RequestPath = "/documents"
28: }))
29: .Build()
30: .Run();
31: }
32: }
现在我们在“~/wwwroot/img/”目录下创建一个名为index.htm的默认页面,现在利用浏览器访问这个目录对应的URL(“http://localhost:5000/img/”),显示就时这个页面的内容。

我们必须在注册StaticFileMiddleware和DirectoryBrowserMiddleware之前注册DefaultFilesMiddleware,否则它起不了任何作用。由于DirectoryBrowserMiddleware和DefaultFilesMiddleware这两个中间件处理的均是针对目录的请求,如果DirectoryBrowserMiddleware先被注册,那么显示的总是目录的内容。若DefaultFilesMiddleware先被注册,在默认页面不存在情况下回显示目录的内容。至于为什么要先于StaticFileMiddleware之前注册DefaultFilesMiddleware,则是因为后者是通过采用URL重写的方式实现的,也就是说这个中间件会将针对目录的请求改写成针对默认页面的请求,而最终针对默认页面的请求还得依赖StaticFileMiddleware完成。
DefaultFilesMiddleware中间件在默认情况下总是以约定的名称(default.htm、default.html、index.htm或者index.html)在当前请求的目录下定位默认页面。如果我们希望作为默认页面的文件不能按照这样的约定命名(比如readme.htm),我们需要按照如下的方式显式指定默认页面的文件名。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc"));
7:
8: DefaultFilesOptions options1 = new DefaultFilesOptions();
9: DefaultFilesOptions options2 = new DefaultFilesOptions{
10: RequestPath = "/documents",
11: FileProvider = fileProvider
12: };
13: options1.DefaultFileNames.Add("readme.htm");
14: options2.DefaultFileNames.Add("readme.htm");
15:
16: new WebHostBuilder()
17: .UseContentRoot(contentRoot)
18: .UseKestrel()
19: .Configure(app => app
20: .UseDefaultFiles(options1)
21: .UseDefaultFiles(options2)
22: .UseStaticFiles()
23: .UseStaticFiles(new StaticFileOptions{
24: FileProvider = fileProvider,
25: RequestPath = "/documents"
26: })
27: .UseDirectoryBrowser()
28: .UseDirectoryBrowser(new DirectoryBrowserOptions{
29: FileProvider = fileProvider,
30: RequestPath = "/documents"
31: }))
32: .Build()
33: .Run();
34: }
35: }
四、映射媒体类型
通过上面演示的实例可以看出,浏览器能够正确的将请求的目标文件的内容正常的呈现出来。对HTTP协议具有基本了解的人都应该知道,响应的文件能够在支持的浏览器上呈现具有一个基本的前提,那就是响应消息通过Content-Type报头携带的媒体类型必须与内容一致。我们的实例演示了针对两种类型文件的请求,一种是JPG文件,另一种是PDF文件,对应的媒体类型分别是“image/jpg”和“application/pdf”,那么StaticFileMiddleware是如何正确解析出正确的媒体类型的呢?
StaticFileMiddleware针对媒体类型的解析是通过一个名为ContentTypeProvider的对象来实现的,而默认使用的则是一个FileExtensionContentTypeProvider对象。顾名思义,FileExtensionContentTypeProvider是根据文件的扩展命名来解析媒体类型的。FileExtensionContentTypeProvider内部预定了数百种常用文件扩展名与对应媒体类型之间的映射关系,所以如果我们发布的静态文件具有标准的扩展名,StaticFileMiddleware就能为对应的响应赋予正确的媒体类型。
那么如果某个文件的扩展名没有在这个预定义的映射之中,或者我们需要某个预定义的扩展名匹配不同的媒体类型,我们应该如何解决呢?还是针对我们演示的这个实例,想在我将“~/wwwroot/img/ dophin1.jpg”这个文件的扩展名改成“.img”,毫无疑问StaticFileMiddleware将能为针对该文件的请求解析出正确媒体类型。这个问题具有若干不同的解决方案,第一种方案就是让StaticFileMiddleware支持不能识别的文件类型,并为它们设置一个默认的媒体类型,如下所示了具体采用的编程方式。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseContentRoot(Directory.GetCurrentDirectory();)
7: .UseKestrel()
8: .Configure(app => app.UseStaticFiles(new StaticFileOptions {
9: ServeUnknownFileTypes = true,
10: DefaultContentType = "image/jpg"
11: }))
12: .Build()
13: .Run();
14: }
15: }
上述这种解决方案只能设置一种默认媒体类型,如果具有多种需要映射成不同媒体类型的非识别文件类型,采用这种方案就无能为力了,所以最根本的解决方案还是需要将不能识别的文件类型和对应的媒体类型进行映射。由于StaticFileMiddleware使用的ContentTypeProvider是可以定制的,我们可以按照如下的方式显式地为StaticFileMiddleware指定一个FileExtensionContentTypeProvider对象作为它的ContentTypeProvider,然后将取缺失的映射添加到这个FileExtensionContentTypeProvider对象上。
1: public class Program
2: {
3: public static void Main()
4: {
5: FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider();
6: contentTypeProvider.Mappings.Add(".img", "image/jpg");
7:
8: new WebHostBuilder()
9: .UseContentRoot(Directory.GetCurrentDirectory())
10: .UseKestrel()
11: .Configure(app => app.UseStaticFiles(new StaticFileOptions{
12: ContentTypeProvider = contentTypeProvider
13: }))
14: .Build()
15: .Run();
16: }
17: }
ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件
ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求
ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构
ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件的更多相关文章
- Web的形式发布静态文件
Web的形式发布静态文件 虽然ASP.NET Core是一款"动态"的Web服务端框架,但是在很多情况下都需要处理针对静态文件的请求,最为常见的就是这对JavaScript脚本文件 ...
- ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认文件,那么前面介绍的StaticFileMiddleware中间件会 ...
- ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构
和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FilePr ...
- ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为StaticFileMidd ...
- ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求
通过调用ApplicationBuilder的扩展方法UseStaticFiles注册的StaticFileMiddleware中间件帮助我们处理针对文件的请求.对于StaticFileMiddlew ...
- ASP.NET Core Razor中处理Ajax请求
如何ASP.NET Core Razor中处理Ajax请求 在ASP.NET Core Razor(以下简称Razor)刚出来的时候,看了一下官方的文档,一直没怎么用过.今天闲来无事,准备用Rozor ...
- ASP.NET Core-Docs:在 ASP.NET Core 中启用跨域请求(CORS)
ylbtech-ASP.NET Core-Docs:在 ASP.NET Core 中启用跨域请求(CORS) 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 1. ...
- 在 ASP.NET Core 中启用跨域请求(CORS)
本文介绍如何在 ASP.NET Core 的应用程序中启用 CORS. 浏览器安全可以防止网页向其他域发送请求,而不是为网页提供服务. 此限制称为相同源策略. 同一源策略可防止恶意站点读取另一个站点中 ...
- ASP.NET Core中如何调整HTTP请求大小的几种方式
一.前言 一般的情况下,我们都无需调用HTTP请求的大小,只有在上传一些大文件,或者使用HTTP协议写入较大的值时(如调用WebService)才可能会调用HTTP最大请求值. 在ASP.NET Co ...
随机推荐
- 理解Maven中的SNAPSHOT版本和正式版本
Maven中建立的依赖管理方式基本已成为Java语言依赖管理的事实标准,Maven的替代者Gradle也基本沿用了Maven的依赖管理机制.在Maven依赖管理中,唯一标识一个依赖项是由该依赖项的三个 ...
- SQL Server镜像自动生成脚本
SQL Server镜像自动生成脚本 镜像的搭建非常繁琐,花了一点时间写了这个脚本,方便大家搭建镜像 执行完这个镜像脚本之后,最好在每台机器都绑定一下hosts文件,不然的话,镜像可能会不work 1 ...
- 复杂的 Hash 函数组合有意义吗?
很久以前看到一篇文章,讲某个大网站储存用户口令时,会经过十分复杂的处理.怎么个复杂记不得了,大概就是先 Hash,结果加上一些特殊字符再 Hash,结果再加上些字符.再倒序.再怎么怎么的.再 Hash ...
- ASP.NET Core 1.1 简介
ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...
- eclipse 快捷键大全
注:因eclipse版本.电脑配置等原因 有些快捷键可能导致不可用(遇到些许问题可在下方评论) [Ct rl+T] 搜索当前接口的实现类 1. [ALT +/] 此快捷键为用户编辑的好帮手,能为 ...
- AngularJs之九(ending......)
今天继续angularJs,但也是最后一篇关于它的了,基础部分差不多也就这些,后续有机会再写它的提升部分. 今天要写的也是一个基础的选择列表: 一:使用ng-options,数组进行循环. <d ...
- [.NET] C# 知识回顾 - Event 事件
C# 知识回顾 - Event 事件 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6060297.html 序 昨天,通过<C# 知识回顾 - ...
- angular2系列教程(十一)路由嵌套、路由生命周期、matrix URL notation
今天我们要讲的是ng2的路由的第二部分,包括路由嵌套.路由生命周期等知识点. 例子 例子仍然是上节课的例子:
- Struts2实现ajax的两种方式
基于Struts2框架下实现Ajax有两种方式,第一种是原声的方式,另外一种是struts2自带的一个插件. js部分调用方式是一样的: JS代码: function testAjax() { var ...
- NGINX引入线程池 性能提升9倍
1. 引言 正如我们所知,NGINX采用了异步.事件驱动的方法来处理连接.这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求.为 ...