百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

ASP.NET Core静态文件中间件「1」搭建文件服务器

haoteby 2025-02-08 11:04 6 浏览

虽然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的文章请点这里]

目录
一、发布物理文件
二、呈现目录结构
三、显示默认页面
四、映射媒体类型

一、发布物理文件

我们创建的演示实例是一个简单的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();
    }
}

作者:蒋金楠
微信公众账号:大内老A
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

相关推荐

手机如何检测是否被安装木马程序?如何防止路由器被黑客重置?

黑客攻击无线路由器有3种途径:...

盈盈可握的娇媚——全能美物ORICO WRE-30

由于工作的关系经常出差,在酒店除了一个RJ45接头,通常都没有无线网络可以提供,不可能自己携带太大的无线路由器,便携式的也买过几个,但是功能上大打折扣实在无法忍受,一直期盼能有既便携也功能丰富强大的产...

安卓重大锁屏密码漏洞,国产手机有几个中招了?

上周,一条新闻吸引了托尼注意。只用一张SIM卡,1分钟不到就能解锁你的安卓手机?...

零代码+免费+联网搜索:用DeepSeek+AnythingLLM搭建专属AI知识库

引言在信息爆炸的时代,如何高效管理私有数据并借助AI能力实现精准问答?本地私有知识库成为解决数据安全与智能化的最佳方案。本文将手把手教你使用开源工具AnythingLLM(项目地址:...

iOS越狱更轻松?黑客破解Lightning连接器

IT之家(www.ithome.com):iOS越狱更轻松?黑客破解Lightning连接器近日,德国黑客StefanEsser,也就是人们熟知的i0n1c在他Twitter上表示,黑客已成功破解了...

如何在 Windows 11 中更改 PIN

#寻找数码点评派#打开Windows设置,转到帐户登录选项,然后选择PIN(WindowsHello)...

2019年终黑客工具盘点-最佳篇

2019已经匆匆溜走,在2020伊始,小兮为大家带来了2019年终工具盘点的最佳篇,将分成三个部分为大家推荐工具,分别是Windows最佳工具、Linux最佳工具和手机最佳工具。话不多说,开整!Win...

磁盘被 BitLocker 锁住了怎么办?教你轻松解决

如果你的磁盘被BitLocker锁住,通常是因为系统检测到潜在的安全风险(如硬件改动、多次密码错误等)或丢失了密钥。以下是分步解决方案:一、确认被锁原因①硬件改动:更换主板、TPM芯片或启动顺序变化可...

风靡全球的安全应用AppLock,同样可能泄露隐私

安全研究人员发现,DoMobileLtd.公司开发的知名的安卓安全应用AppLock存在多个漏洞,容易受到黑客攻击。AppLock应用锁简介AppLock在超过50个国家拥有1亿多用户,它自身支持2...

安卓5.1.1前所有版本曝密码漏洞,轻松乱码即可破解锁屏

据德州大学研究人员发现代号棒棒糖的Android5.x存在一个严重的软件漏洞,只要攻击者能拿到机子的情况下,手机若设置的是数字密码解锁方式,只要输入足够长的乱码就能绕过屏幕锁定,进入到HOME主页取...

手机里有钱的,这5项设置要打开,就算丢了别人也偷不走

随着手机支付时代的到来,可恨的坏人也紧跟支付方式的变化,改为盯上了我们的手机。如果你手机里有钱的,那么一定不要掉以轻心,做好以下5项设置,让手机里的钱的更安全。设置SIM卡锁定设置SIM卡锁定,其实就...

原来破解邻居家的WiFi这么难?还是用万能钥匙吧

我们中的许多人认为,入侵wifi就像用铁锤打破塑料锁一样,并且使用以下提到的工具也是如此。入侵无线网络只是从防御性安全转移到攻击性安全的开始部分。入侵wifi包括捕获连接的握手并使用字典攻击等各种攻击...

电脑开机PIN码忘记了怎么办?教你不用重装系统也可以重置

在使用电脑的时候,我们往往会为了保护电脑的安全,从而设置开机密码。但是总会出现PIN码忘记导致无法开机使用,特别是许多用户反复的输入错误密码导致登录次数过多或者重复的开关机,登录选项被禁用,请使用其他...

送你个使用锦囊 防止蓝牙耳机被“策反”

你每天戴的蓝牙耳机可能被定位跟踪?近日有报道称,部分蓝牙耳机存在安全漏洞,可被不法分子快速植入具有定位功能的代码,从而实现远程跟踪,甚至监听。这一话题迅速登上微博热搜榜,不少网友惊呼:自己身边居然潜伏...

系统小技巧:无懈可击 Windows组策略管理系统密码

为了保护自己的系统安全,我们一般都会为系统设置密码。不过很多人为了记忆方便,设置的都是类似“123456”这样的简单密码,或者即使设置了较为复杂的密码,但是使用的时间很长也不变化。这些密码策略其实都有...