前言

静态文件(如 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<StaticFileMiddleware>();}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<StaticFileMiddleware>(Options.Create(options));}
}

一般我们最常用到的是无参的方式和传递自定义StaticFileOptions的方式比较多,StaticFileOptions是自定义使用静态文件时的配置信息类,接下来我们大致看一下具体包含哪些配置项[点击查看StaticFileOptions源码]

public class StaticFileOptions : SharedOptionsBase
{public StaticFileOptions() : this(new SharedOptions()){}public StaticFileOptions(SharedOptions sharedOptions) : base(sharedOptions){OnPrepareResponse = _ => { };}/// <summary>/// 文件类型提供程序,也就是我们常用的文件名对应MimeType的对应关系/// </summary>public IContentTypeProvider ContentTypeProvider { get; set; }/// <summary>/// 设置该路径下默认文件输出类型/// </summary>public string DefaultContentType { get; set; }public bool ServeUnknownFileTypes { get; set; }/// <summary>/// 文件压缩方式/// </summary>public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress;/// <summary>/// 准备输出之前可以做一些自定义操作/// </summary>public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
}public abstract class SharedOptionsBase
{protected SharedOptionsBase(SharedOptions sharedOptions){SharedOptions = sharedOptions;}protected SharedOptions SharedOptions { get; private set; }/// <summary>/// 请求路径/// </summary>public PathString RequestPath{get { return SharedOptions.RequestPath; }set { SharedOptions.RequestPath = value; }}/// <summary>/// 文件提供程序,在.NET Core中如果需要访问文件相关操作可使用FileProvider文件提供程序获取文件相关信息/// </summary>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<StaticFileOptions> 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<StaticFileMiddleware>();}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,如果匹配到则返回contentTypeelse 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;//再次判断请求HttpMethodif (HttpMethods.IsGet(_method)){_requestType = RequestType.IsGet;}else if (HttpMethods.IsHead(_method)){_requestType = RequestType.IsHead;}else{_requestType = RequestType.Unspecified;}}/// <summary>/// 判断文件是否存在/// </summary>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;}/// <summary>/// 处理文件输出/// </summary>public async Task ServeStaticFile(HttpContext context, RequestDelegate next){//1.准备输出相关Header,主要是获取和输出静态文件输出缓存相关的内容//2.我们之前提到的OnPrepareResponse也是在这里执行的ComprehendRequestHeaders();//根据ComprehendRequestHeaders方法获取到的文件状态进行判断switch (GetPreconditionState()){case PreconditionState.Unspecified://处理文件输出case PreconditionState.ShouldProcess://判断是否是Head请求if (IsHeadMethod){await SendStatusAsync(Constants.Status200Ok);return;}try{//判断是否包含range请求,即文件分段下载的情况if (IsRangeRequest){await SendRangeAsync();return;}//正常文件输出处理await SendAsync();_logger.FileServed(SubPath, PhysicalPath);return;}catch (FileNotFoundException){context.Response.Clear();}await next(context);return;case PreconditionState.NotModified:await SendStatusAsync(Constants.Status304NotModified);return;case PreconditionState.PreconditionFailed:await SendStatusAsync(Constants.Status412PreconditionFailed);return;default:var exception = new NotImplementedException(GetPreconditionState().ToString());throw exception;}}/// <summary>/// 通用文件文件返回处理/// </summary>public async Task SendAsync(){SetCompressionMode();ApplyResponseHeaders(Constants.Status200Ok);string physicalPath = _fileInfo.PhysicalPath;var sendFile = _context.Features.Get<IHttpResponseBodyFeature>();//判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等if (sendFile != null && !string.IsNullOrEmpty(physicalPath)){await sendFile.SendFileAsync(physicalPath, 0, _length, CancellationToken.None);return;}try{//不存在任何特殊处理的操作作,直接读取文件返回using (var readStream = _fileInfo.CreateReadStream()){await StreamCopyOperation.CopyToAsync(readStream, _response.Body, _length, StreamCopyBufferSize, _context.RequestAborted);}}catch (OperationCanceledException ex){_context.Abort();}}/// <summary>/// 分段请求下载操作处理/// </summary>internal async Task SendRangeAsync(){if (_range == null){ResponseHeaders.ContentRange = new ContentRangeHeaderValue(_length);ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);_logger.RangeNotSatisfiable(SubPath);return;}//计算range相关header数据ResponseHeaders.ContentRange = ComputeContentRange(_range, out var start, out var length);_response.ContentLength = length;//设置输出压缩相关headerSetCompressionMode();ApplyResponseHeaders(Constants.Status206PartialContent);string physicalPath = _fileInfo.PhysicalPath;var sendFile = _context.Features.Get<IHttpResponseBodyFeature>();//判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等if (sendFile != null && !string.IsNullOrEmpty(physicalPath)){_logger.SendingFileRange(_response.Headers[HeaderNames.ContentRange], physicalPath);await sendFile.SendFileAsync(physicalPath, start, length, CancellationToken.None);return;}try{using (var readStream = _fileInfo.CreateReadStream()){readStream.Seek(start, SeekOrigin.Begin); _logger.CopyingFileRange(_response.Headers[HeaderNames.ContentRange], SubPath);//设置文件输出起始位置和读取长度await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted);}}catch (OperationCanceledException ex){_context.Abort();}}
}

由于代码较多删除了处主流程处理以外的其他代码,从这里我们可以看出,首先是针对输出缓存相关的读取设置和处理,其此次是针对正常返回和分段返回的情况,在返回之前判断是否有对输出做特殊处理的情况,比如输出压缩或者自定义的其他输出操作的IHttpResponseBodyFeature,分段返回和正常返回相比主要是多了一部分关于Http头Content-Range相关的设置,对于读取本身其实只是读取的起始位置和读取长度的差别。

UseDirectoryBrowser

目录浏览允许在指定目录中列出目录里的文件及子目录。出于安全方面考虑默认情况下是关闭的可以通过UseDirectoryBrowser中间件开启指定目录浏览功能。通常情况下我们会这样使用

//启用默认目录浏览,即wwwroot
app.UseDirectoryBrowser();
//或自定义指定目录浏览
var fileProvider = new PhysicalFileProvider($"{env.ContentRootPath}/MyImages");
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{RequestPath = "/MyImages",FileProvider = fileProvider
});

开启之后当我们访问https:///MyImages地址的时候将会展示如下效果,通过一个表格展示目录里的文件信息等
找到中间件注册类[点击查看DirectoryBrowserExtensions源码]

public static class DirectoryBrowserExtensions
{public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app){return app.UseMiddleware<DirectoryBrowserMiddleware>();}public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, string requestPath){return app.UseDirectoryBrowser(new DirectoryBrowserOptions{RequestPath = new PathString(requestPath)});}public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, DirectoryBrowserOptions options){return app.UseMiddleware<DirectoryBrowserMiddleware>(Options.Create(options));}
}

这个中间件启用的重载方法和UseStaticFiles类似最终都是在传递DirectoryBrowserOptions,接下来我们就看DirectoryBrowserOptions传递了哪些信息[点击查看DirectoryBrowserOptions源码]

public class DirectoryBrowserOptions : SharedOptionsBase
{public DirectoryBrowserOptions(): this(new SharedOptions()){}public DirectoryBrowserOptions(SharedOptions sharedOptions): base(sharedOptions){}/// <summary>/// 目录格式化提供,默认是提供表格的形式展示,课自定义/// </summary>public IDirectoryFormatter Formatter { get; set; }
}

无独有偶这个类和StaticFileOptions一样也是集成自SharedOptionsBase类,唯一多了IDirectoryFormatter操作,通过它我们可以自定义展示到页面的输出形式,接下来我们就重点看下DirectoryBrowserMiddleware中间件的实现

public class DirectoryBrowserMiddleware
{private readonly DirectoryBrowserOptions _options;private readonly PathString _matchUrl;private readonly RequestDelegate _next;private readonly IDirectoryFormatter _formatter;private readonly IFileProvider _fileProvider;public DirectoryBrowserMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions<DirectoryBrowserOptions> options): this(next, hostingEnv, HtmlEncoder.Default, options){}public DirectoryBrowserMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, HtmlEncoder encoder, IOptions<DirectoryBrowserOptions> options){_next = next;_options = options.Value;//默认是提供默认目录的访问程序_fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);//默认传递的是HtmlDirectoryFormatter类型,也就是我们看到的输出表格的页面_formatter = options.Value.Formatter ?? new HtmlDirectoryFormatter(encoder);_matchUrl = _options.RequestPath;}public Task Invoke(HttpContext context){//1.IsGetOrHeadMethod判断是否为Get或Head请求//2.TryMatchPath判断请求的路径和设置的路径是否可以匹配的上//3.TryGetDirectoryInfo判断根据匹配出来的路径能否查找到真实的物理路径if (context.GetEndpoint() == null &&Helpers.IsGetOrHeadMethod(context.Request.Method)&& Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath)&& TryGetDirectoryInfo(subpath, out var contents)){//判断请求路径是否是/为结尾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;}//返回展示目录的内容return _formatter.GenerateContentAsync(context, contents);}return _next(context);}/// <summary>/// 根据请求路径匹配到物理路径信息是否存在,存在则返回路径信息/// </summary>private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents){contents = _fileProvider.GetDirectoryContents(subpath.Value);return contents.Exists;}
}

这个操作相对简单了许多,主要就是判断请求路径能否和预设置的路径匹配的到,如果匹配到则获取可以操作当前目录内容IDirectoryContents然后通过IDirectoryFormatter输出如何展示目录内容,关于IDirectoryFormatter的默认实现类HtmlDirectoryFormatter这里就不展示里面的代码了,逻辑非常的加单就是拼接成table的html代码然后输出,有兴趣的同学可自行查看源码[点击查看HtmlDirectoryFormatter源码],如果自定义的话规则也非常简单,主要看你想输出啥

public class TreeDirectoryFormatter: IDirectoryFormatter
{public Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents){//遍历contents实现你想展示的方式}
}

然后在UseDirectoryBrowser的时候给Formatter赋值即可

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{Formatter = new TreeDirectoryFormatter()
});

UseDefaultFiles

很多时候出于安全考虑或者其他原因我们想在访问某个目录的时候返回一个默认的页面或展示,这个事实我们就需要使用UseDefaultFiles中间件,当我们配置了这个中间件,如果命中了配置路径,那么会直接返回默认的页面信息,简单使用方式如下

//wwwroot目录访问展示默认文件
app.UseDefaultFiles();
//或自定义目录默认展示文件
var fileProvider = new PhysicalFileProvider($"{env.ContentRootPath}/staticfiles");
app.UseDefaultFiles(new DefaultFilesOptions
{RequestPath = "/staticfiles",FileProvider = fileProvider
});

老规矩,我们查看下注册UseDefaultFiles的源码[点击查看DefaultFilesExtensions源码]

public static class DefaultFilesExtensions
{public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app){return app.UseMiddleware<DefaultFilesMiddleware>();}public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, string requestPath){return app.UseDefaultFiles(new DefaultFilesOptions{RequestPath = new PathString(requestPath)});}public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, DefaultFilesOptions options){return app.UseMiddleware<DefaultFilesMiddleware>(Options.Create(options));}
}

使用方式和UseStaticFiles、UseDirectoryBrowser是一样,最终都是调用传递DefaultFilesOptions的方法,我们查看一下DefaultFilesOptions的大致实现[点击查看源码]

public class DefaultFilesOptions : SharedOptionsBase
{public DefaultFilesOptions(): this(new SharedOptions()){}public DefaultFilesOptions(SharedOptions sharedOptions): base(sharedOptions){//系统提供的默认页面的名称DefaultFileNames = new List<string>{"default.htm","default.html","index.htm","index.html",};}/// <summary>/// 通过这个属性可以配置默认文件名称/// </summary>public IList<string> DefaultFileNames { get; set; }
}

和之前的方法如出一辙,都是继承自SharedOptionsBase,通过DefaultFileNames我们可以配置默认文件的名称,默认是default.html/htm和index.html/htm。我们直接查看中间件DefaultFilesMiddleware的源码[点击查看源码]

public class DefaultFilesMiddleware
{private readonly DefaultFilesOptions _options;private readonly PathString _matchUrl;private readonly RequestDelegate _next;private readonly IFileProvider _fileProvider;public DefaultFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions<DefaultFilesOptions> options){_next = next;_options = options.Value;_fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);_matchUrl = _options.RequestPath;}public Task Invoke(HttpContext context){//1.我们使用UseDefaultFiles中间件的时候要置于UseRouting之上,否则就会不生效//2.IsGetOrHeadMethod判断请求为Get或Head的情况下才生效//3.TryMatchPath判断请求的路径和设置的路径是否可以匹配的上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);if (file.Exists){//判断请求路径是否已"/"结尾,如果不是则从定向(这个点个人感觉可以改进)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;}//如果匹配的上,则将配置的启用默认文件的路径+遍历到的默认文件名称的路径组合成新的Path交给_next(context)//比如将组成类似这种路径/staticfiles/index.html向下传递context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);break;}}}}return _next(context);}
}

这个中间件的实现思路也非常简单主要的工作就是,匹配配置的启用默认文件的路径+遍历到的默认文件名称的路径是否存在,如果匹配的上,则将配置的启用默认文件的路径+遍历到的默认文件名称的路径组合成新的Path(比如/staticfiles/index.html)交给后续的中间件去处理。这里值得注意的是UseDefaultFiles 必须要配合UseStaticFiles一起使用,而且注册位置要出现在UseStaticFiles之上。这也是为什么UseDefaultFiles只需要匹配到默认文件所在的路径并重新赋值给context.Request.Path既可的原因。
当然我们也可以自定义默认文件的名称,因为只要能匹配的到具体的文件既可

var defaultFilesOptions = new DefaultFilesOptions
{RequestPath = "/staticfiles",FileProvider = fileProvider
};
//我们可以清除掉系统默认的默认文件名称
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(defaultFilesOptions);

总结

通过上面的介绍我们已经大致了解了静态文件处理的大致实现思路,相对于传统的Asp.Net程序我们可以更方便的处理静态文件信息,但是思路是一致的,IIS会优先处理静态文件,如果静态文件处理不了的情况才会交给程序去处理。ASP.NET Core也不例外,通过我们查看中间件源码里的context.GetEndpoint()==null判断可以知道,ASP.NET Core更希望我们优先去处理静态文件,而不是任意出现在其他位置去处理。关于ASP.NET Core处理静态文件的讲解就到这里,欢迎评论区探讨交流。

????欢迎扫码关注我的公众号????

ASP.NET Core静态文件处理源码探究相关推荐

  1. Go语言中间件框架 Negroni 的静态文件处理源码分析

    Negroni是一个非常棒的中间件,尤其是其中间件调用链优雅的设计,以及对GO HTTP 原生处理器的兼容.我以前写过两篇文章,对Negroni进行了专门的分析,没有看过的朋友可以在看下. Go语言经 ...

  2. 细说ASP.NET Core静态文件的缓存方式

    一.前言 我们在优化Web服务的时候,对于静态的资源文件,通常都是通过客户端缓存.服务器缓存.CDN缓存,这三种方式来缓解客户端对于Web服务器的连接请求压力的. 本文指在这三个方面,在ASP.NET ...

  3. ASP.NET Core 静态文件及JS包管理器(npm, Bower)的使用

    在 ASP.NET Core 中添加静态文件 虽然ASP.NET主要大都做着后端的事情,但前端的一些静态文件也是很重要的.在ASP.NET Core中要启用静态文件,需要Microsoft.AspNe ...

  4. linux源码文件名,Linux中文件名解析处理源码分析

    Linux中文件名解析处理源码分析 前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象.在此,对文件名 ...

  5. 各类最新Asp .Net Core 项目和示例源码

    1.网站地址:http://www.freeboygirl.com 2.网站Asp .Net Core 资料 http://www.freeboygirl.com/blog/tag/asp%20net ...

  6. 【老生谈算法】matlab实现灰度图处理源码——灰度图处理

    matlab灰度图的处理源码 1.文档下载: 本算法已经整理成文档如下,有需要的朋友可以点击进行下载 序号 文档(点击下载) 本项目文档 [老生谈算法]matlab灰度图的处理程序.doc 2.算法详 ...

  7. 什么是ASP.NET Core静态Web资产?

    What are ASP.NET Core Static Web Assets? HostBuilder.ConfigureWebHostDefaults()中发生了很多隐藏的魔术(最终称为Confi ...

  8. Asp.Net Core Authorize解析(源码解读)

    一.前言 IdentityServer4已经分享了一些应用实战的文章,从架构到授权中心的落地应用,也伴随着对IdentityServer4掌握了一些使用规则,但是很多原理性东西还是一知半解,故我这里持 ...

  9. Asp.Net Core在线生成二维码

    前言: 原先用zxing Code写过基于Winfrom的批量生成二维码工具,以及单个生成二维码工具:批量生成二维码Gihub源代码 今天尝试用QRCoder 加 Asp.Net Core 写了一个在 ...

最新文章

  1. 卡耐基梅隆大学计算机金融专业,卡耐基梅隆大学计算机金融硕士申请要求及专业优势...
  2. 08-图7 公路村村通
  3. 设置 webstorm 对 .vue 高亮
  4. wxWidgets:测试样品
  5. 安卓开发要学多久_华为发布鸿蒙2.0,和安卓相似度达80%「西瓜视频」
  6. android 进程间的通信,Android native进程间通信实例-binder结合共享内存
  7. WPF中的Pack URI
  8. 一文吃透Json解析工具类合集(FastJson、Jackson、Gson)
  9. python发布_python如何发布模块
  10. C#性能优化篇 - 基于索引器封装EPList
  11. HDU 5835 Danganronpa 贪心
  12. 计算机系统的3t性能目标是什么,计算机系统结构习题.doc
  13. MQTT.fx客户端MQTT接入阿里云物联网平台,登录、订阅、发布消息
  14. 字节跳动高工面试:mysql查询重复数据sql
  15. .Net转Java自学之路—基础巩固篇二十二(XML)
  16. 计算机主机英语怎么说,电脑的英文-电脑的主机这个词英语怎么说?电脑的主 – 手机爱问...
  17. Apache ECharts教程
  18. AcrelEMS-IDC综合能效管理系统在某大型数据中心的应用方案
  19. 台式计算机 如何组装,如何组装一台台式电脑?
  20. 华为设备配置SEP多环

热门文章

  1. Web前端笔试面试题汇总(转自github)
  2. FPGA FIFO深度计算
  3. 数据库SQL语句学习笔记(6)-使用函数处理数据
  4. WPF中TreeView.BringIntoView方法的替代方案
  5. java 连接kafka_设置多个kafka连接接收器
  6. Kotlin 基础 - 数据类型
  7. HTML5程序开发范例宝典 完整版 (韩旭等著) 中文pdf扫描版
  8. 【转】 ConstraintLayout 完全解析 快来优化你的布局吧
  9. 去创业公司不能有一夜暴富的侥幸,更不能指望掉馅饼
  10. Mysql身份认证漏洞及利用(CVE-2012-2122) 补充测试用例