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

转自博客:https://www.cnblogs.com/CreateMyself/p/9235968.html

前言

最近发表的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,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,  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,    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来使用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


本文将从上往下,循序渐进的介绍一系列相关.NET的概念,先从类型系统开始讲起,我将通过跨语言操作这个例子来逐渐引入一系列.NET的相关概念,这主要包括:CLS、...
基于 .NET 的一个全新的、好用的 PHP SDK + Runtime: PeachPie 来啦!
.NET 异步工作原理介绍。
引子 .NET 6 开始初步引入 PGO。PGO 即 Profile Guided Optimization,通过收集运行时信息来指导 JIT 如何优化代码,相比以前没有 PGO 时可以做更多以前难以
前言 2021/4/8 .NET 6 Preview 3 发布,这个版本的改进大多来自于底层,一起来看看都有什么新特性和改进吧。 库改进 新增值类型作为字典值时更快的处理方法 .NET 6 Previ
前言 开头防杠:.NET 的基础库、语言、运行时团队从来都是相互独立各自更新的,.NET 6 在基础库、运行时上同样做了非常多的改进,不过本文仅仅介绍语言部分。 距离上次介绍 C# 10 的特性已经有
直接使用 CIL - .NET 上的汇编语言编写 .NET Standard 类库
前言 不知不觉中,.NET Framework 已经更新到 4.8,.NET Core 也更新到了 3.0 版本。那么 .NET 的未来怎么样呢? 计划 2019 年 Build 大会上,微软宣布下一
本文带你穿越到未来一起看看未来的 C# 到底长什么样子。
前言 TypedocConverter 是我先前因帮助维护 monaco-editor-uwp 但苦于 monaco editor 的 API 实在太多,手写 C# 的类型绑定十分不划算而发起的一个项
前言 在 2021 年 3 月 11 日, .NET 6 Preview 2 发布,这次的改进主要涉及到 MAUI、新的基础库和运行时、JIT 改进。 .NET 6 正式版将会在 2021 年 11
前言 命名空间已经在 .NET 中使用了多年,一直追溯到 .NET Framework 1.1。它在 .NET 实施本身的数百个位置中使用,并且直接被成千上万个应用程序使用。在所有这些方面,它也是 C
.NET 上的统一跨平台 UI 框架来啦
使用 F# 手写一个 Typedoc 转 C# 代码生成器,方便一切 C# 项目对 TypeScript 项目的封装。
LINQ + SelectMany = Monad!
C# 10 主要特性一览
C# 的编译期反射终于来啦!
前言 2021 年 2 月 17 日微软发布了 .NET 6 的 Preview 1 版本,那么来看看都有什么新特性和改进吧,由于内容太多了因此只介绍一些较为重点的项目。ASP.NET Core 6
前言 有一个东西叫做鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子。 C 里面其实也暗藏了很多类似鸭子类型的东西,但是很多开发者并不知道,因此也就没法好好利用这些东西,
经过五年半的持续维护,Senparc.Weixin SDK 逐步丰满和完善,在升级的过程中,我们为基础库(Senparc.Weixin.dll)加入了许多通用的功能,例如加密/解密算法、通用缓存方法等