>前言

最近发表的ef core貌似有点多,可别误以为我只专攻ef core哦,私下有时间也是一直在看asp.net core的内容,所以后续会穿插讲ef core和asp.net core,别认为你会用asp.net core就自认为你很了解asp.net core,虽说是基础系列但也是也有你不知道的asp.net core。

usestaticfiles、usedefaultfiles、usedirectorybrowser、usefileserver

当我们创建默认.net core web应用程序时,.net core默认为我们注入了staticfiles从而可使用wwwroot目录下的静态文件,请注意这里注入staticfiles是基于wwwroot目录下的静态文件,此时我们如下通过使用usedefaultfiles启用默认静态文件。

app.usedefaultfiles();
app.usestaticfiles();

 

在此之前呢,我们在wwwroot目录下创建了四个静态html文件,如下:

根据官方文档说明,我们创建如上四个静态html,同时也会根据如上顺序在wwwroot目录下查找静态html,查找到了default.htm,所以此时如上显示对应内容,若我们删除第一个html,则会查找default.html,以此类推。要是我们将注入顺序颠倒会这样呢?如下:

app.usestaticfiles();
app.usedefaultfiles();

此时会出现页面404找不到页面,这是为何呢?官方文档强调必须将注入默认文件放在注入静态文件前面,主要是因为注入默认文件只是进行url重写,告诉路由我要到wwwroot目录下查找静态文件,但是实际上提供静态文件的是staticfiles,所以这也是为什么必须将注入默认文件放在注入静态文件前面。但是如果我们非要将注入默认文件放在注入静态文件前面,我们该如何做呢?接下来通过使用usefileserver,usefileserver是usedefaultfiles和usestaticfiles的组合体,既然是组合体,我们将usefileserver放在第一位不就这个问题了吗,我们来试试,如下:

app.usefileserver();
app.usestaticfiles();
app.usedefaultfiles();

结果将会呈现默认静态html,这里我就不再演示了,有兴趣的童鞋可自行研究。接下来我们再来看看启用目录浏览,启用目录浏览和我们在iis上启用目录浏览一样,如下:

app.usedirectorybrowser();
app.usefileserver();
app.usestaticfiles();
app.usedefaultfiles();

这里就不用我再多说,那么问题来了:要是我们将启用目录浏览放到使用mvc路由后面会怎样呢?此时启用目录浏览会覆盖mvc路由?不会,可自行验证。

app.usemvc(routes =>
 {
 routes.maproute(
 name: "default",
 template: "{controller=home}/{action=index}/{id?}");
 });
 
 app.usedirectorybrowser();

 自定义默认文件目录

关于修改默认文件名称等基础,官方文档有详细说明,这里就不再演示,浪费篇幅,接下来我们来重点讲解不一样的。比如默认启用静态文件,是放在wwwroot根目录下,要是我们想将静态文件放在所给第一张图中dist文件夹下呢?此时我们应该如何做呢?因为.net core默认将wwwroot目录作为静态文件目录,所以此时我们需要改变其目录到wwwroot目录下的dist目录,通过使用usewebroot方法,将web静态目录更改到wwwroot下的dist目录,当然同时启用默认文件(usedefaultfiles)如下:

.usewebroot(path.combine(directory.getcurrentdirectory(), "wwwroot", "dist"))

 

那么问题又来了,此时假设我想将默认静态文件放在外部即项目根目录,此时我们应该如何做呢?比如访问如下静态html文件。

此时我们可利用usedefaultfiles方法的重载,将目录更换到项目根目录下的outdefaulthtml目录,如下:

var fileprovider = new physicalfileprovider(path.combine(env.contentrootpath, 
 "outdefaulthtml"));

 app.usedefaultfiles(new defaultfilesoptions()
 {
 fileprovider = fileprovider,
 defaultfilenames = new [] { "outdefault.html" }
 });

因为我们更换了查找静态html的目录,同时最终提供默认文件的是usestaticfiles,所以我们也需要通过usestaticfiles方法的重载切换目录不再是wwwroot,如下:

app.usestaticfiles(new staticfileoptions()
 {
 fileprovider = fileprovider
 });

除了上述通过联合使用usedefaultfiles和usestaticfiles之外,是否还有更简洁的方式呢?当然是有的,当默认静态文件放在wwwroot目录下不再满足我们的需求时,我们需要自定义默认静态文件所放置目录时,推荐使用二者的联合体即usefileserver。上述我们可修改成如下:

var fileprovider = new physicalfileprovider(path.combine(env.contentrootpath,"outdefaulthtml"));
 var fileserveroptions = new fileserveroptions();
 fileserveroptions.defaultfilesoptions.defaultfilenames = new[] { "outdefault.html"};
 fileserveroptions.fileprovider = fileprovider;

 app.usefileserver(fileserveroptions);

 usestaticfiles详解

在大部分情况下,我们都将静态文件放在wwwroot目录下,但是有那么百分之十的情况下会将静态文件放在项目根目录,那么此时使用默认注入的usestaticfiles方法就不再适用,此时我们需要用到其重载方法。比如我们要访问如下图中的mvc_course.gif,我们该如何做呢?

上面已经讲过,需要使用usestaticfiles方法的重载,第一个参数将目录切换到静态文件所在目录,第二个参数是虚拟路径用来访问静态文件,为了不对外暴露实际物理路径,如下:

app.usestaticfiles(new staticfileoptions()
 {
 fileprovider = new physicalfileprovider(path.combine(env.contentrootpath, "outstaticfiles")),
 requestpath = "/outfiles"
 });
 
 或者

 //app.usestaticfiles(new staticfileoptions()
 //{
 // fileprovider = new physicalfileprovider(path.combine(env.contentrootpath, "outstaticfiles")),
 // requestpath = new pathstring("/outfiles")
 //});

该重载方法还有一个委托参数onprepareresponse,这个主要用来缓存静态文件,接下来我们来重点讲讲,其实本文都是重点,哈哈,简单的大家直接去看官网吧。

app.usestaticfiles(new staticfileoptions()
 {
 fileprovider = new physicalfileprovider(path.combine(env.contentrootpath, "outstaticfiles")),
 requestpath = "/outfiles",
 onprepareresponse = ctx => 
 {
 const int cachecontroll = 60;
 ctx.context.response.headers["cache-control"] = "public,max-age=" + cachecontroll;
 }
 });

在官方文档上是进行如上设置,但实际上官方文档api已经过时,对于请求头的设置直接有headernames这样一个枚举来进行设置,不再通过字符串的形式来设置,这样不容易出错且方便,上述对于请求头中缓存控制的设置有如下两种方式皆可。

在响应头中添加缓存控制有什么实际作用?此时就要谈到缓存控制的原理了。上述缓存控制设置的过期时间为60秒。当第一次请求时返回200,在此间隙即60秒内反复刷新都会是200,同时从浏览器缓存中读取,一旦过了60秒,再刷新此时会再去读取服务器上的图片,发现图片未发生改变返回304未修改。那么问题来了,如果我们在此间隙内修改了图片的内容,然后再刷新图片的内容是否会发生改变呢?答案是:不会,只要在缓存间隙时间内,即使我们修改了图片的内容,再刷新还是显示原来的图片(除非进行ctrl+f5强制刷新才行)。好了讲了这么多,我们继续拓展一下,再来看看asp.net core中taghelper特性:asp-append-version特性。该特性和缓存控制原理是一样的么,接下来我们来谈谈asp-append-version以及其原理。

asp-append-version详解及其原理

我们以wwwroot目录下images文件下的图片为例,然后在页面上访问图片加上asp-append-version看看,如下:

<img src="~/images/mvc_course.gif" asp-append-version="true" />

 

此时响应返回链接地址为:http://localhost:63277/images/mvc_course.gif?v=y3f-lvd7xoqgqliwq_wsufn9popsjit1au6_0irrgwe,我们从如上图也可看到,此时在图片后面类似加了一个版本号v,我们反复刷新版本号后面的字符串一直未变,那么这个类似于哈希码的值是怎么得来的呢?基于请求url和图片内容计算出哈希码即版本号。也就说只要我们更改了图片的内容,当刷新或者再次访问此页面时内容相应会进行对应更新,这也就是我们所说的缓存击穿,相对于缓存控制而言,只要在缓存间隙时间内修改了图片内容,除非进行强制刷新,否则图片依然显示旧的图片,而asp-append-version特性则是你变,我变,你不变,我一成不变。是不是就这么简单呢?接下来我们访问一下项目根目录下的图片看看,通过usestaticfiles重载访问外部图片,同时加上asp-append-version特性。

<img src="/outfiles/mvc_course.gif" asp-append-version="true" />

wow,看到了什么没有,发现了什么没有,至此我们可以得出结论:asp-append-version特性实现图片缓存只是针对于webroot目录下的静态文件,而外部静态文件则无效。

那么既然问题已经很凸出了,asp-append-version主要是针对于webroot目录下的静态文件,而webroot里面只有wwwroot,所以我们可以称之为只对wwwroot目录下的静态文件才生效,所以我们是否可以尝试将外部文件目录也置于webroot目录呢?从而实现对外部静态文件的缓存呢?我们下面来做尝试,在startup.cs中默认注入usestaticfiles,我们搁置不变,这样对默认针对wwwroot下的样式、脚本、文件都不会发生任何改变,我们只是再来注入一个usestaticfiles而已,如下:

var compositeprovider = new compositefileprovider
 (
 env.webrootfileprovider,
 new physicalfileprovider(path.combine(directory.getcurrentdirectory(), "outstaticfiles"))
 );
 env.webrootfileprovider = compositeprovider;
 app.usestaticfiles(new staticfileoptions()
 {
 fileprovider = compositeprovider,
 requestpath = "/outfiles"
 });

 

如上针对默认的webroot即wwwroot保持不变,我们在此基础上添加外部目录从而作为复合fileprovider作为webroot,这样一切都未变。我们再来进行如下访问。

<img src="/mvc_course.gif" asp-append-version="true" />

如上是针对outstaticfiles作为webroot目录访问其静态文件,断不可加上outfiles虚拟路径,这样就当做是外部静态文件,从而不会有版本号出现,结果如下:

我们如何自定义实现对外部文件也添加类似于asp-append-version特性版本号的效果呢? 上述我们已经明确讲解到asp-append-version本质原理则是基于请求url和请求图片内容来计算版本号从而实现缓存,关于缓存我们大可借助imemorycache接口来进行缓存,请求的路径我们可以通过请求上下文获取到,同时也可通过环境变量拿到请求静态文件所在目录,所以接下来我们只需要实现视图的扩展方法即可。

视图扩展方法通过指向irazorpage接口,然后参数则是我们的文件路径,asp.net core有了依赖注入让我们甚为欢喜,我们通过视图中的视图上下文拿到请求上下文。然后拿到已经注入的imemorycache和ihostingenviroment接口,关于文件版本号,asp.net core给我们提供了fileversionprovider类,如下:

我们将参数传递到fileversionprovider构造函数中去,最后将得到的文件版本号添加到我们请求的文件路径尾巴上,代码如下:

public static class razorpageextension
 {
 public static string addappendversion(this irazorpage page, string path)
 {
 var context = page.viewcontext.httpcontext;

 var memorycache = context.requestservices.getservice(typeof(imemorycache)) as imemorycache;

 var hostingenviroment = context.requestservices.getservice(typeof(ihostingenvironment)) as ihostingenvironment;

 var fileversionprovider = new fileversionprovider(hostingenviroment.webrootfileprovider, memorycache, context.request.path);

 return fileversionprovider.addfileversiontopath(path);
 }
 }

我们利用上述自定义实现的razor视图扩展方法来访问图片从而得到版本号试试,如下:

<img src="@this.addappendversion("/mvc_course.gif")" 

总结

本文详细讲解了asp.net core mvc中静态文件以及缓存控制、asp-append-version本质原理,同时讲解了缓存控制和asp-append-version区别所在。默认情况下,asp-append-version只针对wwwroot有效,因为在webroot里面只存在wwwroot,要想对外部文件有效,可将外部文件所在目录也作为webroot来使用。