您现在的位置是:网站首页> 编程资料编程资料
浅谈ASP.NET Core静态文件处理源码探究_实用技巧_
2023-05-24
401人已围观
简介 浅谈ASP.NET Core静态文件处理源码探究_实用技巧_
前言
静态文件(如 HTML、CSS、图像和 JavaScript)等是Web程序的重要组成部分。传统的ASP.NET项目一般都是部署在IIS上,IIS是一个功能非常强大的服务器平台,可以直接处理接收到的静态文件处理而不需要经过应用程序池处理,所以很多情况下对于静态文件的处理程序本身是无感知的。ASP.NET Core则不同,作为Server的Kestrel服务是宿主到程序上的,由宿主运行程序启动Server然后可以监听请求,所以通过程序我们直接可以处理静态文件相关。静态文件默认存储到项目的wwwroot目录中,当然我们也可以自定义任意目录去处理静态文件。总之,在ASP.NET Core我们可以处理静态文件相关的请求。
StaticFile三剑客
通常我们在说道静态文件相关的时候会涉及到三个话题分别是启用静态文件、默认静态页面、静态文件目录浏览,在ASP.NET Core分别是通过UseStaticFiles、UseDefaultFiles、UseDirectoryBrowser三个中间件去处理。只有配置了相关中间件才能去操作对应的处理,相信大家对这种操作已经很熟了。静态文件操作相关的源码都位于GitHub aspnetcore仓库中的https://github.com/dotnet/aspnetcore/tree/v3.1.6/src/Middleware/StaticFiles/src目录。接下来我们分别探究这三个中间件的相关代码,来揭开静态文件处理的神秘面纱。
UseStaticFiles
UseStaticFiles中间件使我们处理静态文件时最常使用的中间件,因为只有开启了这个中间件我们才能使用静态文件,比如在使用MVC开发的时候需要私用js css html等文件都需要用到它,使用的方式也比较简单
//使用默认路径,即wwwroot app.UseStaticFiles(); //或自定义读取路径 var fileProvider = new PhysicalFileProvider($"{env.ContentRootPath}/staticfiles"); app.UseStaticFiles(new StaticFileOptions { RequestPath="/staticfiles", FileProvider = fileProvider });我们直接找到中间件的注册类StaticFileExtensions[点击查看StaticFileExtensions源码]
public static class StaticFileExtensions { public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app) { return app.UseMiddleware(); } public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, string requestPath) { return app.UseStaticFiles(new StaticFileOptions { RequestPath = new PathString(requestPath) }); } public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options) { return app.UseMiddleware(Options.Create(options)); } } 一般我们最常用到的是无参的方式和传递自定义StaticFileOptions的方式比较多,StaticFileOptions是自定义使用静态文件时的配置信息类,接下来我们大致看一下具体包含哪些配置项[点击查看StaticFileOptions源码]
public class StaticFileOptions : SharedOptionsBase { public StaticFileOptions() : this(new SharedOptions()) { } public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions) { OnPrepareResponse = _ => { }; } /// /// 文件类型提供程序,也就是我们常用的文件名对应MimeType的对应关系 /// public IContentTypeProvider ContentTypeProvider { get; set; } /// /// 设置该路径下默认文件输出类型 /// public string DefaultContentType { get; set; } public bool ServeUnknownFileTypes { get; set; } /// /// 文件压缩方式 /// public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress; /// /// 准备输出之前可以做一些自定义操作 /// public Action OnPrepareResponse { get; set; } } public abstract class SharedOptionsBase { protected SharedOptionsBase(SharedOptions sharedOptions) { SharedOptions = sharedOptions; } protected SharedOptions SharedOptions { get; private set; } /// /// 请求路径 /// public PathString RequestPath { get { return SharedOptions.RequestPath; } set { SharedOptions.RequestPath = value; } } /// /// 文件提供程序,在.NET Core中如果需要访问文件相关操作可使用FileProvider文件提供程序获取文件相关信息 /// public IFileProvider FileProvider { get { return SharedOptions.FileProvider; } set { SharedOptions.FileProvider = value; } } } 我们自定义静态文件访问时,最常用到的就是RequestPath和FileProvider,一个设置请求路径信息,一个设置读取文件信息。如果需要自定义MimeType映射关系可通过ContentTypeProvider自定义设置映射关系
var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".myapp"] = "application/x-msdownload"; provider.Mappings[".htm3"] = "text/html"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider, //可以在输出之前设置输出相关 OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age=3600"); } });接下来我们步入正题直接查看StaticFileMiddleware中间件的代码[点击查看StaticFileMiddleware源码]
public class StaticFileMiddleware { private readonly StaticFileOptions _options; private readonly PathString _matchUrl; private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly IContentTypeProvider _contentTypeProvider; public StaticFileMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions options, ILoggerFactory loggerFactory) { _next = next; _options = options.Value; //设置文件类型提供程序 _contentTypeProvider = options.Value.ContentTypeProvider ?? new FileExtensionContentTypeProvider(); //文件提供程序 _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv); //匹配路径 _matchUrl = _options.RequestPath; _logger = loggerFactory.CreateLogger(); } public Task Invoke(HttpContext context) { //判断是够获取到终结点信息,这也就是为什么我们使用UseStaticFiles要在UseRouting之前 if (!ValidateNoEndpoint(context)) { } //判断HttpMethod,只能是Get和Head操作 else if (!ValidateMethod(context)) { } //判断请求路径是否存在 else if (!ValidatePath(context, _matchUrl, out var subPath)) { } //根据请求文件名称判断是否可以匹配到对应的MimeType,如果匹配到则返回contentType else if (!LookupContentType(_contentTypeProvider, _options, subPath, out var contentType)) { } else { //执行静态文件操作 return TryServeStaticFile(context, contentType, subPath); } return _next(context); } private Task TryServeStaticFile(HttpContext context, string contentType, PathString subPath) { var fileContext = new StaticFileContext(context, _options, _logger, _fileProvider, contentType, subPath); //判断文件是否存在 if (!fileContext.LookupFileInfo()) { _logger.FileNotFound(fileContext.SubPath); } else { //静态文件处理 return fileContext.ServeStaticFile(context, _next); } return _next(context); } } 关于FileExtensionContentTypeProvider这里就不作讲解了,主要是承载文件扩展名和MimeType的映射关系代码不复杂,但是映射关系比较多,有兴趣的可以自行查看FileExtensionContentTypeProvider源码,通过上面我们可以看到,最终执行文件相关操作的是StaticFileContext类[点击查看StaticFileContext源码]
internal struct StaticFileContext { private const int StreamCopyBufferSize = 64 * 1024; private readonly HttpContext _context; private readonly StaticFileOptions _options; private readonly HttpRequest _request; private readonly HttpResponse _response; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly string _method; private readonly string _contentType; private IFileInfo _fileInfo; private EntityTagHeaderValue _etag; private RequestHeaders _requestHeaders; private ResponseHeaders _responseHeaders; private RangeItemHeaderValue _range; private long _length; private readonly PathString _subPath; private DateTimeOffset _lastModified; private PreconditionState _ifMatchState; private PreconditionState _ifNoneMatchState; private PreconditionState _ifModifiedSinceState; private PreconditionState _ifUnmodifiedSinceState; private RequestType _requestType; public StaticFileContext(HttpContext context, StaticFileOptions options, ILogger logger, IFileProvider fileProvider, string contentType, PathString subPath) { _context = context; _options = options; _request = context.Request; _response = context.Response; _logger = logger; _fileProvider = fileProvider; _method = _request.Method; _contentType = contentType; _fileInfo = null; _etag = null; _requestHeaders = null; _responseHeaders = null; _range = null; _length = 0; _subPath = subPath; _lastModified = new DateTimeOffset(); _ifMatchState = PreconditionState.Unspecified; _ifNoneMatchState = PreconditionState.Unspecified; _ifModifiedSinceState = PreconditionState.Unspecified; _ifUnmodifiedSinceState = PreconditionState.Unspecified; //再次判断请求HttpMethod if (HttpMethods.IsGet(_method)) { _requestType = RequestType.IsGet; } else if (HttpMethods.IsHead(_method)) { _requestType = RequestType.IsHead; } else { _requestType = RequestType.Unspecified; } } /// /// 判断文件是否存在 /// public bool LookupFileInfo() { //判断根据请求路径是否可以获取到文件信息 _fileInfo = _fileProvider.GetFileInfo(_subPath.Value); if (_fileInfo.Exists) { //获取文件长度 _length = _fileInfo.Length; //最后修改日期 DateTimeOffset last = _fileInfo.LastModified; _lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime(); //ETag标识 long etagHash = _lastModified.ToFileTime() ^ _length; _etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"'); } return _fileInfo.Exists; } /// /// 处理文件输出 /// public async Task ServeStaticFile(HttpContext context, RequestDelega
相关内容
- 详解ASP.NET Core3.0 配置的Options模式_实用技巧_
- ASP.NET Core 奇淫技巧之伪属性注入的实现_实用技巧_
- ASP.NET Core奇淫技巧之动态WebApi的实现_实用技巧_
- ASP.NET Core Authentication认证实现方法_实用技巧_
- ASP.NET Core 奇技淫巧之接口代理转发的实现_实用技巧_
- 基于Fiddler实现修改接口返回数据进行测试_实用技巧_
- .Net Core使用MongoDB的详细教程_实用技巧_
- .NET Core中反解ObjectId_实用技巧_
- .NET CORE HttpClient的使用方法_实用技巧_
- Element NavMenu导航菜单的使用方法_实用技巧_
点击排行
本栏推荐
