前言

    静态文件(如 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,如果匹配到则返回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;
}
} /// <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;
//设置输出压缩相关header
SetCompressionMode();
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. ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 静态文件 前几章节中,我们学习了 ASP.NET Core 的中间件 ...

  2. ASP.NET Core静态文件中间件[1]: 搭建文件服务器

    虽然ASP.NET Core是一款"动态"的Web服务端框架,但是由它接收并处理的大部分是针对静态文件的请求,最常见的是开发Web站点使用的3种静态文件(JavaScript脚本. ...

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

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

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

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

  5. ASP.NET Core 静态文件

    静态文件(HTML,CSS,图片和Javascript之类的资源)会被ASP.NET Core应用直接提供给客户端. 静态文件通常位于网站根目录(web root) <content-root& ...

  6. asp .net core 静态文件资源

    前言 对静态资源的简单的一个概况,在<重新整理.net core 计1400篇>系列后面会深入. 正文 我们在加入中间件是这样写的: app.UseStaticFiles(); 默认是给w ...

  7. NET Core静态文件的缓存方式

    NET Core静态文件的缓存方式 阅读目录 一.前言 二.StaticFileMiddleware 三.ASP.NET Core与CDN? 四.写在最后 回到目录 一.前言 我们在优化Web服务的时 ...

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

    NET Core 静态文件及JS包管理器(npm, Bower)的使用 文章目录 在 ASP.NET Core 中添加静态文件 使用npm管理JavaScript包 使用Bower管理JavaScri ...

  9. [转]NET Core静态文件的缓存方式

    本文转自:https://www.cnblogs.com/Leo_wl/p/6059349.html 阅读目录 NET Core静态文件的缓存方式 一.前言 二.StaticFileMiddlewar ...

随机推荐

  1. WEB应用的常见安全漏洞

      01. SQL 注入 SQL 注入就是通过给 web 应用接口传入一些特殊字符,达到欺骗服务器执行恶意的 SQL 命令.SQL 注入漏洞属于后端的范畴,但前端也可做体验上的优化.原因:当使用外部不 ...

  2. Selenium自动化测试与练习

    Selenium WebDriver 提供了web自动化各种语言(java python ruby等等) 调用接口库 提供 各种浏览器的驱动(web driver) 来驱动浏览器的 特点 测试程度可以 ...

  3. docker部署dubbo怎么实现外部主机访问服务?

    dubbo在分布式项目中太常见了,docker也是现在热门的项目,然而docker的网络配置也是非常麻烦的一件事情,这里给大家介绍一下dubbo实现跨服务器访问服务配置 docker-compose. ...

  4. android自定义控件onLayout方法

    onLayout设置子控件的位置,对应一些普通的控件例如Button.TextView等控件,不存在子控件,所以可以不用复写该方法. 向线性布局.相对布局等存在子控件,可以覆写该方法去控制子控件的位置 ...

  5. redis高级命令2

    主服务负责数据的写,从服务器负责客户端的高并发来读 创建主从复制 clone不能让上面的mac地址不能重复,IP地址也不能重复 122和123是从服务器,我们修改二者的配置文件 其中 192.168. ...

  6. 输入url后浏览器干了些什么(详解)

    输入url后浏览器干了些什么(详解) DNS(Domain Name System, 域名系统) 解析 DNS解析的过程就是寻找哪台机器上有你真正需要的资源过程.但你在浏览器张红输入一个地址时,例如: ...

  7. dart快速入门教程 (8)

    9.dart中的库 9.1.自定义库 自定义库我们在前面已经使用过了,把某些功能抽取到一个文件,使用的时候通过import引入即可 9.2.系统内置库 以math库为例: import "d ...

  8. Python中的@staticmethod和@classmethod的区别

    一直搞不明白,类方法和静态方法的区别,特意研究了一下,跟大家分享一下. 为了方便大家了解两者的差别,以下的示例代码将有助于发现其中的差别: class A(object): def foo(self, ...

  9. python之浅谈计算机基础

    目录 一.计算机基础之编程 什么是编程语言 什么是编程 为什么要编程 二.计算机组成原理 1. 计算机五大组成 CPU 存储器 输入设备 输出设备 2.计算机五大部分补充 CPU相关 应用程序启动流程 ...

  10. 个人对于flask中蓝图的理解

    什么是蓝图? 蓝图可以理解为,是一种对项目中的代码进行模块化管理的工具,相当于python中的包为什么要使用蓝图? 在一个py文件中具有多个功能代码,不利于维护和管理. 如果在其他的模块中去调用视图函 ...