虽然ASP.NET Core是一款“动态”的Web服务端框架,但是由它接收并处理的大部分是针对静态文件的请求,最常见的是开发Web站点使用的3种静态文件(JavaScript脚本、CSS样式和图片)。ASP.NET Core提供了3个中间件来处理针对静态文件的请求,利用它们不仅可以将物理文件发布为可以通过HTTP请求获取的Web资源,还可以将所在的物理目录的结构呈现出来。通过HTTP请求获取的Web资源大部分来源于存储在服务器磁盘上的静态文件。对于ASP.NET Core应用来说,如果将静态文件存储到约定的目录下,绝大部分文件类型都是可以通过Web的形式对外发布的。基于静态文件的请求由3个中间件负责处理,它们均定义在NuGet包“Microsoft.AspNetCore.StaticFiles”中,利用这3个中间件完全可以搭建一个基于Web的文件服务器,下面做相关的实例演示。

目录
一、发布物理文件

二、呈现目录结构

三、显示默认页面

四、映射媒体类型

一、发布物理文件

我们创建的演示实例是一个简单的ASP.NET Core应用,它的项目结构如下图所示。在默认作为WebRoot的“wwwroot”目录下,可以将JavaScript脚本文件、CSS样式文件和图片文件存放到对应的子目录(js、css和img)下。WebRoot目录下的所有文件将自动发布为Web资源,客户端可以访问相应的URL来读取对应文件的内容。

针对具体某个静态文件的请求是通过一个名为StaticFileMiddleware的中间件来处理的。如下面的代码片段所示,承载ASP.NET Core应用的程序中调用IApplicationBuilder接口的UseStaticFiles扩展方法注册的就是这样一个中间件。

public class Program
{
public static void Main()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder=>builder.Configure(app => app.UseStaticFiles()))
.Build()
.Run();
}
}

上述程序运行之后,就可以通过GET请求的方式来读取对应文件的内容。请求采用的URL由目标文件的路径决定。具体来说,目标文件相对于WebRoot目录的路径就是对应URL的路径,如JPG图片文件“~/wwwroot/img/dolphin1.jpg”对应的URL路径为“/img/dolphin1.jpg”。如果直接利用浏览器访问这个URL,目标图片就会直接以下图所示的形式显示出来。

上面通过一个简单的实例将WebRoot所在目录下的所有静态文件发布为Web资源,如果需要发布的静态文件存储在其他目录下呢?下面将上面演示的应用程序的一些文档存储在下图所示的“~/doc/”目录下,那么对应的程序又该如何编写?

ASP.NET Core应用在大部分情况下都是利用一个IFileProvider对象来读取文件的针对静态文件的读取请求也不例外。对于IApplicationBuilder接口的UseStaticFiles扩展方法注册的StaticFileMiddleware中间件来说,它的内部维护着一个IFileProvider对象和请求路径的映射关系。如果调用UseStaticFiles方法没有指定任何参数,那么这个映射关系的请求路径就是应用的基地址(PathBase),对应的IFileProvider对象自然就是指向WebRoot目录的PhysicalFileProvider对象。

上述需求可以通过显式定制这个映射关系的方式来实现。如下面的代码片段所示,我们在现有程序的基础上额外添加了一次针对UseStaticFiles扩展方法的调用,在本次调用中指定一个对应的Options对象(一个类型为StaticFileOptions的对象)作为参数来定制请求路径(“/documents”)与对应IFileProvider对象(针对路径“~/doc/”的PhysicalFileProvider对象)之间的映射关系。

public class Program
{
public static void Main()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
var options = new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(path),
RequestPath = "/documents"
};
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app
.UseStaticFiles()
.UseStaticFiles(options)))
.Build()
.Run();
}
}

按照上面这段程序指定的映射关系,对于存储在“~/doc/”目录下的这个PDF文件(checklist.pdf),对应URL的路径就应该是“/documents/checklist.pdf”。如果利用浏览器请求这个地址时,PDF文件的内容就会按照下图所示的形式显示在浏览器上。

二、呈现目录结构

上面的演示实例注册的StaticFileMiddleware中间件只会处理针对具体的某个静态文件的请求,如果利用浏览器发送一个针对目录的请求(如“http://localhost:5000/img/”),得到的将是一个状态为“404 Not Found”的响应。如果希望浏览器呈现出目标目录的结构,就可以注册另一个名为DirectoryBrowserMiddleware的中间件。这个中间件会返回一个HTML页面,请求目录下的结构会以表格的形式显示在这个页面中。我们演示的程序可以按照如下方式调用IApplicationBuilder接口的UseDirectoryBrowser扩展方法来注册DirectoryBrowserMiddleware中间件。

public class Program
{
public static void Main()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
var fileProvider = new PhysicalFileProvider(path); var fileOptions = new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
}; var diretoryOptions = new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
}; Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app
.UseStaticFiles()
.UseStaticFiles(fileOptions)
.UseDirectoryBrowser()
.UseDirectoryBrowser(diretoryOptions)))

.Build()
.Run();
}
}

当上面的应用启动之后,如果利用浏览器向针对某个目录的URL(如“http://localhost:5000/”或者“http://localhost:5000/img/”)发起请求,目标目录的内容(包括子目录和文件)就会以图14-5所示的形式显示在一个表格中。可以看出,在呈现的表格中,当前目录的子目录和文件均会显示为链接。

三、显示默认页面

从安全的角度来讲,利用注册的UseDirectoryBrowser中间件会将整个目标目录的结构和所有文件全部暴露出来,所以这个中间件需要根据自身的安全策略谨慎使用。对于针对目录的请求,更加常用的处理策略就是显示一个保存在这个目录下的默认页面。默认页面文件一般采用如下4种命名约定:default.htm、default.html、index.htm和index.html。针对默认页面的呈现实现在一个名为DefaultFilesMiddleware的中间件中,我们演示的这个应用就可以按照如下方式调用IApplicationBuilder接口的UseDefaultFiles扩展方法来注册这个中间件。

public class Program
{
public static void Main()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
var fileProvider = new PhysicalFileProvider(path); var fileOptions = new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
};
var diretoryOptions = new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
};
var defaultOptions = new DefaultFilesOptions
{
RequestPath = "/documents",
FileProvider = fileProvider,
}; Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app
.UseDefaultFiles()
.UseDefaultFiles(defaultOptions)

.UseStaticFiles()
.UseStaticFiles(fileOptions)
.UseDirectoryBrowser()
.UseDirectoryBrowser(diretoryOptions)))
.Build()
.Run();
}
}

下面在“~/wwwroot/img/”目录和“~/doc”目录下分别创建一个名为index.html的默认页面,并且在该.html文件的主体部分指定一段简短的文字(This is an index page!)。在应用启动之后,可以利用浏览器访问这两个目录对应的URL(“http://localhost:5000/img/”和“http://localhost:5000/documents/”),下图显示的就是这个默认页面的内容。

必须在注册StaticFileMiddleware中间件和DirectoryBrowserMiddleware中间件之前注册DefaultFilesMiddleware中间件,否则它无法发挥作用。这是因为DirectoryBrowserMiddleware中间件和DefaultFilesMiddleware中间件处理的均是针对目录的请求,如果先注册DirectoryBrowserMiddleware中间件,那么显示的总是目录的结构;如果先注册用于显示默认页面的DefaultFilesMiddleware中间件,那么在默认页面不存在的情况下它会将请求分发给后续中间件,而DirectoryBrowserMiddleware中间件会接收请求的处理并将当前目录的结构呈现出来。

要先于StaticFileMiddleware中间件之前注册DefaultFilesMiddleware中间件是因为后者是通过采用URL重写的方式实现的,也就是说,这个中间件会将针对目录的请求改写成针对默认页面的请求,而最终针对默认页面的请求还需要依赖StaticFileMiddleware中间件来完成。DefaultFilesMiddleware中间件在默认情况下总是以约定的名称(default.htm、default.html、index.htm和index.html)在当前请求的目录下定位默认页面。如果作为默认页面的文件没有采用这样的约定命名(如我们将默认页面命名为readme.html),就需要按照如下方式显式指定默认页面的文件名。

public class Program
{
public static void Main()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
var fileProvider = new PhysicalFileProvider(path);
var fileOptions = new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
};
var diretoryOptions = new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = "/documents"
};
var defaultOptions1 = new DefaultFilesOptions();
var defaultOptions2 = new DefaultFilesOptions
{
RequestPath = "/documents",
FileProvider = fileProvider,
}; defaultOptions1.DefaultFileNames.Add("readme.html");
defaultOptions2.DefaultFileNames.Add("readme.html");
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app
.UseDefaultFiles(defaultOptions1)
.UseDefaultFiles(defaultOptions2)
.UseStaticFiles()
.UseStaticFiles(fileOptions)
.UseDirectoryBrowser()
.UseDirectoryBrowser(diretoryOptions)))
.Build()
.Run();
}
}

四、映射媒体类型

通过上面演示的实例可以看出,浏览器能够准确地将请求的目标文件的内容正常呈现出来。对HTTP协议具有基本了解的读者应该都知道:响应文件能够在浏览器上被正常显示的基本前提是响应报文通过Content-Type报头携带的媒体类型必须与内容一致。我们的实例演示了针对两种文件类型的请求,一种是JPG文件,另一种是PDF文件,对应的媒体类型分别是image/jpg和application/pdf,那么用来处理静态文件请求的StaticFileMiddleware中间件是如何解析出对应的媒体类型的?

StaticFileMiddleware中间件针对媒体类型的解析是通过一个IContentTypeProvider对象来完成的,默认采用的是该接口的实现类型FileExtensionContentTypeProvider。顾名思义,FileExtensionContentTypeProvider根据文件的扩展命名来解析媒体类型。FileExtensionContentTypeProvider内部预定了数百种常用文件扩展名与对应媒体类型之间的映射关系,所以如果发布的静态文件具有标准的扩展名,那么StaticFileMiddleware中间件就能为对应的响应赋予正确的媒体类型。

如果某个文件的扩展名没有在预定义的映射之中,或者需要某个预定义的扩展名匹配不同的媒体类型,那么应该如何解决?同样是针对我们演示的这个实例,笔者将~/wwwroot/img/ dolphin1.jpg文件的扩展名改成.img,毫无疑问,StaticFileMiddleware中间件将无法为针对该文件的请求解析出正确的媒体类型。这个问题具有若干不同的解决方案,第一种方案就是按照如下方式让StaticFileMiddleware中间件支持不能识别的文件类型,并为它们设置一个默认的媒体类型。

public class Program
{
public static void Main()
{
var options = new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/jpg"
}; Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app.UseStaticFiles(options)))
.Build()
.Run();
}
}

上述解决方案只能设置一种默认媒体类型,如果具有多种需要映射成不同媒体类型的文件类型,采用这种方案就达不到目的,所以最根本的解决方案还是需要将不能识别的文件类型和对应的媒体类型进行映射。由于StaticFileMiddleware中间件使用的IContentTypeProvider对象是可以定制的,所以可以按照如下方式显式地为该中间件指定一个FileExtensionContentTypeProvider对象,然后将缺失的映射添加到这个对象上。

public class Program
{
public static void Main()
{
var contentTypeProvider = new FileExtensionContentTypeProvider();
contentTypeProvider.Mappings.Add(".img", "image/jpg");
var options = new StaticFileOptions
{
ContentTypeProvider = contentTypeProvider
};
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app.UseStaticFiles(options)))
.Build()
.Run();
}
}

ASP.NET Core静态文件中间件[1]: 搭建文件服务器
ASP.NET Core静态文件中间件[2]: 条件请求以提升性能
ASP.NET Core静态文件中间件[3]: 区间请求以提供部分内容
ASP.NET Core静态文件中间件[4]: StaticFileMiddleware
ASP.NET Core静态文件中间件[5]: DirectoryBrowserMiddleware & DefaultFilesMiddleware

ASP.NET Core静态文件中间件[1]: 搭建文件服务器的更多相关文章

  1. ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 静态文件 前几章节中,我们学习了 ASP.NET Core 的中间件 ...

  2. ASP.NET Core 静态文件

    静态文件(HTML,CSS,图片和Javascript之类的资源)会被ASP.NET Core应用直接提供给客户端. 静态文件通常位于网站根目录(web root) <content-root& ...

  3. 细说ASP.NET Core静态文件的缓存方式

    一.前言 我们在优化Web服务的时候,对于静态的资源文件,通常都是通过客户端缓存.服务器缓存.CDN缓存,这三种方式来缓解客户端对于Web服务器的连接请求压力的. 本文指在这三个方面,在ASP.NET ...

  4. ASP.NET Core静态文件处理源码探究

    前言     静态文件(如 HTML.CSS.图像和 JavaScript)等是Web程序的重要组成部分.传统的ASP.NET项目一般都是部署在IIS上,IIS是一个功能非常强大的服务器平台,可以直接 ...

  5. ASP.NET Core 静态文件及JS包管理器(npm, Bower)的使用

    在 ASP.NET Core 中添加静态文件 虽然ASP.NET主要大都做着后端的事情,但前端的一些静态文件也是很重要的.在ASP.NET Core中要启用静态文件,需要Microsoft.AspNe ...

  6. .Net Core静态文件中间件StaticFiles的使用

    以前,当我们的网站需要显示图片的时候,直接在网站目录下新建文件夹,把图片放在这个文件夹下,然后通过文件夹的路径就可以访问到. 但是在.net core中不可以这样,要通过中间件StaticFiles配 ...

  7. asp .net core 静态文件资源

    前言 对静态资源的简单的一个概况,在<重新整理.net core 计1400篇>系列后面会深入. 正文 我们在加入中间件是这样写的: app.UseStaticFiles(); 默认是给w ...

  8. 《ASP.NET Core 高性能系列》静态文件中间件

    一.概述 静态文件(如 HTML.CSS.图片和 JavaScript等文件)是 Web程序直接提供给客户端的直接加载的文件. 较比于程序动态交互的代码而言,其实原理都一样(走Http协议), ASP ...

  9. 15.ASP.NET Core 应用程序中的静态文件中间件

    在这篇文章中,我将向大家介绍,如何使用中间件组件来处理静态文件.这篇文章中,我们讨论下面几个问题: 在ASP.NET Core中,我们需要把静态文件存放在哪里? 在ASP.NET Core中 wwwr ...

随机推荐

  1. 打包错误:Failed to execute goal org.scala-tools:maven-scala-plugin:2.15.2:compile (default) on project MusicProject: wrap: org.apache.commons.exec.ExecuteException:

    错误:Failed to execute goal org.scala-tools:maven-scala-plugin:2.15.2:compile (default) on project Mus ...

  2. 攻克弹唱第九课(如何运用好G大调和弦)

    在本期文章中,笔者将使用guitar pro7软件与大家分享如何运用好G大调音阶的经验. 众所周知,在我们学习吉他的过程中,先从C大调开始,再以G大调为深入,然后才走过入门的阶段.很多朋友都觉得自己对 ...

  3. 三大Mac清理工具实用性测评,哪款好用?

    相信大多数MAC用户都较为了解,Mac虽然有着许多亮点的性能,但是让用户叫苦不迭的还其硬盘空间小的特色,至于很多人因为文件堆积以及软件缓存等,造成系统空间内存不够使用的情况.于是清理工具就成为了大多数 ...

  4. 多元Huffman编码变形—回溯法

    一.问题描述 描述 在一个操场的四周摆放着n堆石子.现要将石子有次序地合并成一堆.规定在合并过程中最多可以有m(k)次选k堆石子合并成新的一堆,2≤k≤n,合并的费用为新的一堆的石子数.试设计一个算法 ...

  5. 公平lock和非公平lock的区别

    可以看到区别在于,在lock时和tryAquire时,非公平锁不会去管队列中有没有线程在排队,直接尝试去获取锁,失败之后就和公平锁一样,乖乖去排队. 也就是说发生竞争的场景在于,尚未入队的线程之间和刚 ...

  6. 【Golang】基础-操作 csv 文件

    1. csv plugins,自带极简 1.1 写数据到csv文件 知识点:encoding/csv 库的 Write 方法使用[]string的切片格式追加方式写入数据 1.1.1 追加写入 pac ...

  7. linq 查询的结果会开辟新的内存吗?

    一:背景 1. 讲故事 昨天群里有位朋友问:linq 查询的结果会开辟新的内存吗?如果开了,那是对原序列集里面元素的深拷贝还是仅仅拷贝其引用? 其实这个问题我觉得问的挺好,很多初学 C# 的朋友或多或 ...

  8. 16_Android的数据存储_ SharedPreference、XML和JSON

    1. Android读写首选项 1.1 SharedPreferences SharedPreferences 是一种轻型的数据存储方式,它的本质是基于XML文件存储Key-Value键值对数据,通常 ...

  9. UML第二次作业(代码互评)

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...

  10. 我劝!这位年轻人不讲MVCC,耗子尾汁!

    目录 一.事物的隔离级别与MVCC? 二.Repeatable Read是如何实现的 本文是MySQL专题第15篇,全文近100篇(公众号首发) 三.Read Commited是如何实现的: 本文是M ...