你所不知道的ASP.NET Core MVC/WebApi基础系列(一)

前言

最近发表的EF Core貌似有点多,可别误觉得我只专攻EF Core哦,私下有时间也是一直在看ASP.NET Core的内容,因此后续会穿插讲EF Core和ASP.NET Core,别认为你会用ASP.NET Core就自认为你很了解ASP.NET Core,虽然说是基础系列但也是也有你不知道的ASP.NET Core。html

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放在第一位不就这个问题了吗,咱们来试试,以下:mvc

            app.UseFileServer();
            app.UseStaticFiles();
            app.UseDefaultFiles();

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

            app.UseDirectoryBrowser();
            app.UseFileServer();
            app.UseStaticFiles();
            app.UseDefaultFiles();

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

            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文件。this

此时咱们可利用UseDefaultFiles方法的重载,将目录更换到项目根目录下的OutDefaultHtml目录,以下:spa

            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这样一个枚举来进行设置,再也不经过字符串的形式来设置,这样不容易出错且方便,上述对于请求头中缓存控制的设置有以下两种方式皆可。

           app.UseStaticFiles(new StaticFileOptions()
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),
                RequestPath = "/outfiles",
                OnPrepareResponse = ctx => 
                {
                    const int cacheControll = 60;
                    ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + cacheControll;
                }
            });

           或者

            app.UseStaticFiles(new StaticFileOptions()
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),
                RequestPath = "/outfiles",
                OnPrepareResponse = ctx =>
                {
                    const int cacheControll = 60;
                    var headers = ctx.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new CacheControlHeaderValue()
                    {
                        MaxAge = TimeSpan.FromSeconds(cacheControll)
                    };  
                }
            });

在响应头中添加缓存控制有什么实际做用?此时就要谈到缓存控制的原理了。上述缓存控制设置的过时时间为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来使用。

相关文章
相关标签/搜索