跟我一起学.NetCore之静态文件处理的那些事

前言

如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去);而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后端站点整合一起部署;通常当项目规模比较大的时候,分开站点部署是不错的选择,管理和维护清晰,而对于一些小型项目,整合在一起部署为一个站点就显得相对比较方便,毕竟有时候开发是你、部署是你、维护也是你;如果选择整合部署,或者是项目包含静态文件(如图片)的访问,接下来的内容就有用武之地了~~~

 

正文

Asp.NetCore的请求管道是根据需求通过注册中间件进行构造的(构造过程参考:跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建),而通过模板创建出来的项目,请求管道中默认只有关键的几个中间件,如果有其他需要,可以自己添加注册。其中静态文件中间件默认就没有,如下案例:

 

 

如上例运行结果,是访问不到添加的index.html,可能有小伙伴会说,那是因为没有加目录,然而并不是这个原因; 现在注册上静态文件中间件试试:

 

 

 

为什么要创建wwwroot目录呢?其他目录不行吗?

当注册静态文件中间件时,通过构造函数可以看出(看下面静态文件中间件的构造函数截图),可以指定对应的静态文件目录,当没有指定目录时,默认就会使用IHostingEnvironment中的WebRootFileProvider,而WebRootFileProvider默认就指定了wwwroot:

 

 

在IHostingEnvironment的扩展方法Initialize中指定;

 

 

这里就不一一去扒代码了,如果有兴趣的小伙伴,可以按照以下思路去扒:

 

 

 

那如何指定目录,在扒代码的过程中应该会看到,注册中间件的时候可以传参进行指定,如下:

 

 

 

根据需求可以注册多个静态文件中间件,如上所示,请求到请求管道时,会先到wwwroot目录中去找匹配文件,如果找不到继续下一个中间件,去指定的myFile目录中去匹配文件。

 

往往在开发过程中,会对相关静态文件进行分类,同时Url地址也要不同,通常会通过注册中间件时,将对应静态文件目录映射到指定Url目录,如下:

 

 

 

搞过IIS的小伙伴应该都知道设置默认文件的配置吧,通过现成的中间件也能实现,如下:

 

 

注册中间件实现,能减少配置当然也是不错的选择:

 

 

 

到这,小伙伴们应该尝试一下,将wwwroot目录下的index.html的名字改改,再运行一下,同样的访问Url地址肯定访问不了的,如果能,那估计是存在缓存,可以清清缓存再试; 那为什么呢?定位很精确,肯定是默认文件这个中间件再搞事情,来,看看里面咋实现的:

 // 定义默认文件中间件
 public class DefaultFilesMiddleware
 {
     // 选项配置
     private readonly DefaultFilesOptions _options;
     private readonly PathString _matchUrl;
     private readonly RequestDelegate _next;
     // 静态文件目录读取Provider,默认目录是wwwroot
     private readonly IFileProvider _fileProvider;
     // 构造函数,用于初始化对应的变量
     public DefaultFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions<DefaultFilesOptions> options)
     {
         // 校验参数
         if (next == null)
         {
             throw new ArgumentNullException(nameof(next));
         }
         if (hostingEnv == null)
         {
             throw new ArgumentNullException(nameof(hostingEnv));
         }
         if (options == null)
         {
             throw new ArgumentNullException(nameof(options));
         }
         _next = next;
         // 初始化配置信息
         _options = options.Value;
         // 如果没有指定对应的IFileProvider,就用IWebHostEnvironment的WebRootFileProvider,默认目录就wwwroot
         _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
         _matchUrl = _options.RequestPath;
     }
     // 默认文件中间件的关键方法
     public Task Invoke(HttpContext context)
     {
         if (context.GetEndpoint() == null &&
             Helpers.IsGetOrHeadMethod(context.Request.Method)
             && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath))
         {
             var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);
             if (dirContents.Exists)
             {
                 // 依次遍历默认文件,检查对应文件是否在指定目录中存在,这里是关键
                 for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++)
                 {
                     string defaultFile = _options.DefaultFileNames[matchIndex];
                     var file = _fileProvider.GetFileInfo(subpath.Value + defaultFile);
                     // TryMatchPath will make sure subpath always ends with a "/" by adding it if needed.
                     if (file.Exists)
                     {
                         // 如果路径与目录匹配,但没有以斜杠结尾,则重定向以添加斜杠.
                         // This prevents relative links from breaking.
                         if (!Helpers.PathEndsInSlash(context.Request.Path))
                         {
                             context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
                             var request = context.Request;
                             var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString);
                             context.Response.Headers[HeaderNames.Location] = redirect;
                             return Task.CompletedTask;
                         }
 ​
                         // 如果匹配找到,就重写请求地址,由下一个中间件处理,所以在个中间件的注册一定要在UseStaticFiles前面,否则会报错
                         context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
                         break;
                     }
                 }
             }
         }
         // 执行下一个中间件
         return _next(context);
     }
 }

在中间件Invoke方法中,遍历_options.DefaultFileNames进行匹配,但我们并没有指定,猜想应该是有默认设置,去看看对应的DefaultFilesOptions:

 public DefaultFilesOptions(SharedOptions sharedOptions)
            : base(sharedOptions)
 {
     // 果然,在构造函数中指定了默认列表
     DefaultFileNames = new List<string>
             {
                 "default.htm",
                 "default.html",
                 "index.htm",
                 "index.html",
             };
 }

果然在DefaultFilesOptions的构造函数有对应的默认列表,现在是不是豁然开朗了~~~;那如果一定要指定其他文件怎么办呢?老规矩,注册中间件时传参:

 

 

 

是不是很简单,再来个需求,比如想做一个在线文件管理系统,那肯定得访问目录吧,现在肯定不能访问的,小伙伴们可以试试;

通过注册中间又可以实现,是不是觉得中间件很是灵活,而且还很强大:

 

 

这里对于参数的设置就不一一举例了,用法和UseStaticFiles参数差不多一致,小伙伴感兴趣可私下试试。

 

其实微软早就想到一会要这么干,一会要那么干了,所以直接提供了一个全功能的中间件,直接UseFileServer即可,可以针对上面说到的每一项进行配置,如下:

 

 

其实内部就是整合以上说到的中间件,如下源码:

 

 

详细配置这里就不一一配置测试了,使用和单独注册中间件时一致,这里只是整合在一起而已。

 

总结

说好的偏应用,还是没忍住扒代码,但是感觉适当的扒扒能说的更清楚一些;下一节说说路由的最佳实践。

------------------------------------------------

CSDN:Code综艺圈

知乎:Code综艺圈

掘金:Code综艺圈

博客园:Code综艺圈

bilibili:Code综艺圈

------------------------------------------------

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

 

 

撸文不易,莫要白瞟,三连走起~~~~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值