浅谈ASP.NET Core静态文件处理源码探究
前言
静态文件(如HTML、CSS、图像和JavaScript)等是Web程序的重要组成部分。传统的ASP.NET项目一般都是部署在IIS上,IIS是一个功能非常强大的服务器平台,可以直接处理接收到的静态文件处理而不需要经过应用程序池处理,所以很多情况下对于静态文件的处理程序本身是无感知的。ASP.NETCore则不同,作为Server的Kestrel服务是宿主到程序上的,由宿主运行程序启动Server然后可以监听请求,所以通过程序我们直接可以处理静态文件相关。静态文件默认存储到项目的wwwroot目录中,当然我们也可以自定义任意目录去处理静态文件。总之,在ASP.NETCore我们可以处理静态文件相关的请求。
StaticFile三剑客
通常我们在说道静态文件相关的时候会涉及到三个话题分别是启用静态文件、默认静态页面、静态文件目录浏览,在ASP.NETCore分别是通过UseStaticFiles、UseDefaultFiles、UseDirectoryBrowser三个中间件去处理。只有配置了相关中间件才能去操作对应的处理,相信大家对这种操作已经很熟了。静态文件操作相关的源码都位于GitHubaspnetcore仓库中的https://github.com/dotnet/aspnetcore/tree/v3.1.6/src/Middleware/StaticFiles/src目录。接下来我们分别探究这三个中间件的相关代码,来揭开静态文件处理的神秘面纱。
UseStaticFiles
UseStaticFiles中间件使我们处理静态文件时最常使用的中间件,因为只有开启了这个中间件我们才能使用静态文件,比如在使用MVC开发的时候需要私用jscsshtml等文件都需要用到它,使用的方式也比较简单
//使用默认路径,即wwwroot app.UseStaticFiles(); //或自定义读取路径 varfileProvider=newPhysicalFileProvider($"{env.ContentRootPath}/staticfiles"); app.UseStaticFiles(newStaticFileOptions{ RequestPath="/staticfiles", FileProvider=fileProvider });
我们直接找到中间件的注册类StaticFileExtensions[点击查看StaticFileExtensions源码]
publicstaticclassStaticFileExtensions { publicstaticIApplicationBuilderUseStaticFiles(thisIApplicationBuilderapp) { returnapp.UseMiddleware(); } publicstaticIApplicationBuilderUseStaticFiles(thisIApplicationBuilderapp,stringrequestPath) { returnapp.UseStaticFiles(newStaticFileOptions { RequestPath=newPathString(requestPath) }); } publicstaticIApplicationBuilderUseStaticFiles(thisIApplicationBuilderapp,StaticFileOptionsoptions) { returnapp.UseMiddleware (Options.Create(options)); } }
一般我们最常用到的是无参的方式和传递自定义StaticFileOptions的方式比较多,StaticFileOptions是自定义使用静态文件时的配置信息类,接下来我们大致看一下具体包含哪些配置项[点击查看StaticFileOptions源码]
publicclassStaticFileOptions:SharedOptionsBase { publicStaticFileOptions():this(newSharedOptions()) { } publicStaticFileOptions(SharedOptionssharedOptions):base(sharedOptions) { OnPrepareResponse=_=>{}; } //////文件类型提供程序,也就是我们常用的文件名对应MimeType的对应关系 /// publicIContentTypeProviderContentTypeProvider{get;set;} //////设置该路径下默认文件输出类型 /// publicstringDefaultContentType{get;set;} publicboolServeUnknownFileTypes{get;set;} //////文件压缩方式 /// publicHttpsCompressionModeHttpsCompression{get;set;}=HttpsCompressionMode.Compress; //////准备输出之前可以做一些自定义操作 /// publicActionOnPrepareResponse{get;set;} } publicabstractclassSharedOptionsBase { protectedSharedOptionsBase(SharedOptionssharedOptions) { SharedOptions=sharedOptions; } protectedSharedOptionsSharedOptions{get;privateset;} /// ///请求路径 /// publicPathStringRequestPath { get{returnSharedOptions.RequestPath;} set{SharedOptions.RequestPath=value;} } //////文件提供程序,在.NETCore中如果需要访问文件相关操作可使用FileProvider文件提供程序获取文件相关信息 /// publicIFileProviderFileProvider { get{returnSharedOptions.FileProvider;} set{SharedOptions.FileProvider=value;} } }
我们自定义静态文件访问时,最常用到的就是RequestPath和FileProvider,一个设置请求路径信息,一个设置读取文件信息。如果需要自定义MimeType映射关系可通过ContentTypeProvider自定义设置映射关系
varprovider=newFileExtensionContentTypeProvider(); provider.Mappings[".myapp"]="application/x-msdownload"; provider.Mappings[".htm3"]="text/html"; app.UseStaticFiles(newStaticFileOptions { ContentTypeProvider=provider, //可以在输出之前设置输出相关 OnPrepareResponse=ctx=> { ctx.Context.Response.Headers.Append("Cache-Control",$"public,max-age=3600"); } });
接下来我们步入正题直接查看StaticFileMiddleware中间件的代码[点击查看StaticFileMiddleware源码]
publicclassStaticFileMiddleware { privatereadonlyStaticFileOptions_options; privatereadonlyPathString_matchUrl; privatereadonlyRequestDelegate_next; privatereadonlyILogger_logger; privatereadonlyIFileProvider_fileProvider; privatereadonlyIContentTypeProvider_contentTypeProvider; publicStaticFileMiddleware(RequestDelegatenext,IWebHostEnvironmenthostingEnv,IOptionsoptions,ILoggerFactoryloggerFactory) { _next=next; _options=options.Value; //设置文件类型提供程序 _contentTypeProvider=options.Value.ContentTypeProvider??newFileExtensionContentTypeProvider(); //文件提供程序 _fileProvider=_options.FileProvider??Helpers.ResolveFileProvider(hostingEnv); //匹配路径 _matchUrl=_options.RequestPath; _logger=loggerFactory.CreateLogger (); } publicTaskInvoke(HttpContextcontext) { //判断是够获取到终结点信息,这也就是为什么我们使用UseStaticFiles要在UseRouting之前 if(!ValidateNoEndpoint(context)) { } //判断HttpMethod,只能是Get和Head操作 elseif(!ValidateMethod(context)) { } //判断请求路径是否存在 elseif(!ValidatePath(context,_matchUrl,outvarsubPath)) { } //根据请求文件名称判断是否可以匹配到对应的MimeType,如果匹配到则返回contentType elseif(!LookupContentType(_contentTypeProvider,_options,subPath,outvarcontentType)) { } else { //执行静态文件操作 returnTryServeStaticFile(context,contentType,subPath); } return_next(context); } privateTaskTryServeStaticFile(HttpContextcontext,stringcontentType,PathStringsubPath) { varfileContext=newStaticFileContext(context,_options,_logger,_fileProvider,contentType,subPath); //判断文件是否存在 if(!fileContext.LookupFileInfo()) { _logger.FileNotFound(fileContext.SubPath); } else { //静态文件处理 returnfileContext.ServeStaticFile(context,_next); } return_next(context); } }
关于FileExtensionContentTypeProvider这里就不作讲解了,主要是承载文件扩展名和MimeType的映射关系代码不复杂,但是映射关系比较多,有兴趣的可以自行查看FileExtensionContentTypeProvider源码,通过上面我们可以看到,最终执行文件相关操作的是StaticFileContext类[点击查看StaticFileContext源码]
internalstructStaticFileContext { privateconstintStreamCopyBufferSize=64*1024; privatereadonlyHttpContext_context; privatereadonlyStaticFileOptions_options; privatereadonlyHttpRequest_request; privatereadonlyHttpResponse_response; privatereadonlyILogger_logger; privatereadonlyIFileProvider_fileProvider; privatereadonlystring_method; privatereadonlystring_contentType; privateIFileInfo_fileInfo; privateEntityTagHeaderValue_etag; privateRequestHeaders_requestHeaders; privateResponseHeaders_responseHeaders; privateRangeItemHeaderValue_range; privatelong_length; privatereadonlyPathString_subPath; privateDateTimeOffset_lastModified; privatePreconditionState_ifMatchState; privatePreconditionState_ifNoneMatchState; privatePreconditionState_ifModifiedSinceState; privatePreconditionState_ifUnmodifiedSinceState; privateRequestType_requestType; publicStaticFileContext(HttpContextcontext,StaticFileOptionsoptions,ILoggerlogger,IFileProviderfileProvider,stringcontentType,PathStringsubPath) { _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=newDateTimeOffset(); _ifMatchState=PreconditionState.Unspecified; _ifNoneMatchState=PreconditionState.Unspecified; _ifModifiedSinceState=PreconditionState.Unspecified; _ifUnmodifiedSinceState=PreconditionState.Unspecified; //再次判断请求HttpMethod if(HttpMethods.IsGet(_method)) { _requestType=RequestType.IsGet; } elseif(HttpMethods.IsHead(_method)) { _requestType=RequestType.IsHead; } else { _requestType=RequestType.Unspecified; } } //////判断文件是否存在 /// publicboolLookupFileInfo() { //判断根据请求路径是否可以获取到文件信息 _fileInfo=_fileProvider.GetFileInfo(_subPath.Value); if(_fileInfo.Exists) { //获取文件长度 _length=_fileInfo.Length; //最后修改日期 DateTimeOffsetlast=_fileInfo.LastModified; _lastModified=newDateTimeOffset(last.Year,last.Month,last.Day,last.Hour,last.Minute,last.Second,last.Offset).ToUniversalTime(); //ETag标识 longetagHash=_lastModified.ToFileTime()^_length; _etag=newEntityTagHeaderValue('\"'+Convert.ToString(etagHash,16)+'\"'); } return_fileInfo.Exists; } //////处理文件输出 /// publicasyncTaskServeStaticFile(HttpContextcontext,RequestDelegatenext) { //1.准备输出相关Header,主要是获取和输出静态文件输出缓存相关的内容 //2.我们之前提到的OnPrepareResponse也是在这里执行的 ComprehendRequestHeaders(); //根据ComprehendRequestHeaders方法获取到的文件状态进行判断 switch(GetPreconditionState()) { casePreconditionState.Unspecified: //处理文件输出 casePreconditionState.ShouldProcess: //判断是否是Head请求 if(IsHeadMethod) { awaitSendStatusAsync(Constants.Status200Ok); return; } try { //判断是否包含range请求,即文件分段下载的情况 if(IsRangeRequest) { awaitSendRangeAsync(); return; } //正常文件输出处理 awaitSendAsync(); _logger.FileServed(SubPath,PhysicalPath); return; } catch(FileNotFoundException) { context.Response.Clear(); } awaitnext(context); return; casePreconditionState.NotModified: awaitSendStatusAsync(Constants.Status304NotModified); return; casePreconditionState.PreconditionFailed: awaitSendStatusAsync(Constants.Status412PreconditionFailed); return; default: varexception=newNotImplementedException(GetPreconditionState().ToString()); throwexception; } } //////通用文件文件返回处理 /// publicasyncTaskSendAsync() { SetCompressionMode(); ApplyResponseHeaders(Constants.Status200Ok); stringphysicalPath=_fileInfo.PhysicalPath; varsendFile=_context.Features.Get(); //判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等 if(sendFile!=null&&!string.IsNullOrEmpty(physicalPath)) { awaitsendFile.SendFileAsync(physicalPath,0,_length,CancellationToken.None); return; } try { //不存在任何特殊处理的操作作,直接读取文件返回 using(varreadStream=_fileInfo.CreateReadStream()) { awaitStreamCopyOperation.CopyToAsync(readStream,_response.Body,_length,StreamCopyBufferSize,_context.RequestAborted); } } catch(OperationCanceledExceptionex) { _context.Abort(); } } /// ///分段请求下载操作处理 /// internalasyncTaskSendRangeAsync() { if(_range==null) { ResponseHeaders.ContentRange=newContentRangeHeaderValue(_length); ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable); _logger.RangeNotSatisfiable(SubPath); return; } //计算range相关header数据 ResponseHeaders.ContentRange=ComputeContentRange(_range,outvarstart,outvarlength); _response.ContentLength=length; //设置输出压缩相关header SetCompressionMode(); ApplyResponseHeaders(Constants.Status206PartialContent); stringphysicalPath=_fileInfo.PhysicalPath; varsendFile=_context.Features.Get(); //判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等 if(sendFile!=null&&!string.IsNullOrEmpty(physicalPath)) { _logger.SendingFileRange(_response.Headers[HeaderNames.ContentRange],physicalPath); awaitsendFile.SendFileAsync(physicalPath,start,length,CancellationToken.None); return; } try { using(varreadStream=_fileInfo.CreateReadStream()) { readStream.Seek(start,SeekOrigin.Begin); _logger.CopyingFileRange(_response.Headers[HeaderNames.ContentRange],SubPath); //设置文件输出起始位置和读取长度 awaitStreamCopyOperation.CopyToAsync(readStream,_response.Body,length,_context.RequestAborted); } } catch(OperationCanceledExceptionex) { _context.Abort(); } } }
关的读取设置和处理,其此次是针对正常返回和分段返回的情况,在返回之前判断是否有对输出做特殊处理的情况,比如输出压缩或者自定义的其他输出操作的IHttpResponseBodyFeature,分段返回和正常返回相比主要是多了一部分关于Http头Content-Range相关的设置,对于读取本身其实只是读取的起始位置和读取长度的差别。
UseDirectoryBrowser
目录浏览允许在指定目录中列出目录里的文件及子目录。出于安全方面考虑默认情况下是关闭的可以通过UseDirectoryBrowser中间件开启指定目录浏览功能。通常情况下我们会这样使用
//启用默认目录浏览,即wwwroot app.UseDirectoryBrowser(); //或自定义指定目录浏览 varfileProvider=newPhysicalFileProvider($"{env.ContentRootPath}/MyImages"); app.UseDirectoryBrowser(newDirectoryBrowserOptions { RequestPath="/MyImages", FileProvider=fileProvider });
开启之后当我们访问https://
/MyImages地址的时候将会展示如下效果,通过一个表格展示目录里的文件信息等
到中间件注册类[点击查看DirectoryBrowserExtensions源码]
publicstaticclassDirectoryBrowserExtensions { publicstaticIApplicationBuilderUseDirectoryBrowser(thisIApplicationBuilderapp) { returnapp.UseMiddleware(); } publicstaticIApplicationBuilderUseDirectoryBrowser(thisIApplicationBuilderapp,stringrequestPath) { returnapp.UseDirectoryBrowser(newDirectoryBrowserOptions { RequestPath=newPathString(requestPath) }); } publicstaticIApplicationBuilderUseDirectoryBrowser(thisIApplicationBuilderapp,DirectoryBrowserOptionsoptions) { returnapp.UseMiddleware (Options.Create(options)); } }
这个中间件启用的重载方法和UseStaticFiles类似最终都是在传递DirectoryBrowserOptions,接下来我们就看DirectoryBrowserOptions传递了哪些信息[点击查看DirectoryBrowserOptions源码]
publicclassDirectoryBrowserOptions:SharedOptionsBase { publicDirectoryBrowserOptions() :this(newSharedOptions()) { } publicDirectoryBrowserOptions(SharedOptionssharedOptions) :base(sharedOptions) { } //////目录格式化提供,默认是提供表格的形式展示,课自定义 /// publicIDirectoryFormatterFormatter{get;set;} }
无独有偶这个类和StaticFileOptions一样也是集成自SharedOptionsBase类,唯一多了IDirectoryFormatter操作,通过它我们可以自定义展示到页面的输出形式,接下来我们就重点看下DirectoryBrowserMiddleware中间件的实现
publicclassDirectoryBrowserMiddleware { privatereadonlyDirectoryBrowserOptions_options; privatereadonlyPathString_matchUrl; privatereadonlyRequestDelegate_next; privatereadonlyIDirectoryFormatter_formatter; privatereadonlyIFileProvider_fileProvider; publicDirectoryBrowserMiddleware(RequestDelegatenext,IWebHostEnvironmenthostingEnv,IOptionsoptions) :this(next,hostingEnv,HtmlEncoder.Default,options) { } publicDirectoryBrowserMiddleware(RequestDelegatenext,IWebHostEnvironmenthostingEnv,HtmlEncoderencoder,IOptions options) { _next=next; _options=options.Value; //默认是提供默认目录的访问程序 _fileProvider=_options.FileProvider??Helpers.ResolveFileProvider(hostingEnv); //默认传递的是HtmlDirectoryFormatter类型,也就是我们看到的输出表格的页面 _formatter=options.Value.Formatter??newHtmlDirectoryFormatter(encoder); _matchUrl=_options.RequestPath; } publicTaskInvoke(HttpContextcontext) { //1.IsGetOrHeadMethod判断是否为Get或Head请求 //2.TryMatchPath判断请求的路径和设置的路径是否可以匹配的上 //3.TryGetDirectoryInfo判断根据匹配出来的路径能否查找到真实的物理路径 if(context.GetEndpoint()==null&& Helpers.IsGetOrHeadMethod(context.Request.Method) &&Helpers.TryMatchPath(context,_matchUrl,forDirectory:true,subpath:outvarsubpath) &&TryGetDirectoryInfo(subpath,outvarcontents)) { //判断请求路径是否是/为结尾 if(!Helpers.PathEndsInSlash(context.Request.Path)) { //如果不是以斜线结尾则重定向(个人感觉直接在服务端重定向就可以了,为啥还要返回浏览器在请求一次) context.Response.StatusCode=StatusCodes.Status301MovedPermanently; varrequest=context.Request; varredirect=UriHelper.BuildAbsolute(request.Scheme,request.Host,request.PathBase,request.Path+"/",request.QueryString); context.Response.Headers[HeaderNames.Location]=redirect; returnTask.CompletedTask; } //返回展示目录的内容 return_formatter.GenerateContentAsync(context,contents); } return_next(context); } /// ///根据请求路径匹配到物理路径信息是否存在,存在则返回路径信息 /// privateboolTryGetDirectoryInfo(PathStringsubpath,outIDirectoryContentscontents) { contents=_fileProvider.GetDirectoryContents(subpath.Value); returncontents.Exists; } }
这个操作相对简单了许多,主要就是判断请求路径能否和预设置的路径匹配的到,如果匹配到则获取可以操作当前目录内容IDirectoryContents然后通过IDirectoryFormatter输出如何展示目录内容,关于IDirectoryFormatter的默认实现类HtmlDirectoryFormatter这里就不展示里面的代码了,逻辑非常的加单就是拼接成table的html代码然后输出,有兴趣的同学可自行查看源码[点击查看HtmlDirectoryFormatter源码],如果自定义的话规则也非常简单,主要看你想输出啥
publicclassTreeDirectoryFormatter:IDirectoryFormatter { publicTaskGenerateContentAsync(HttpContextcontext,IEnumerablecontents) { //遍历contents实现你想展示的方式 } }
然后在UseDirectoryBrowser的时候给Formatter赋值即可
app.UseDirectoryBrowser(newDirectoryBrowserOptions { Formatter=newTreeDirectoryFormatter() });
UseDefaultFiles
很多时候出于安全考虑或者其他原因我们想在访问某个目录的时候返回一个默认的页面或展示,这个事实我们就需要使用UseDefaultFiles中间件,当我们配置了这个中间件,如果命中了配置路径,那么会直接返回默认的页面信息,简单使用方式如下
//wwwroot目录访问展示默认文件 app.UseDefaultFiles(); //或自定义目录默认展示文件 varfileProvider=newPhysicalFileProvider($"{env.ContentRootPath}/staticfiles"); app.UseDefaultFiles(newDefaultFilesOptions { RequestPath="/staticfiles", FileProvider=fileProvider });
老规矩,我们查看下注册UseDefaultFiles的源码[点击查看DefaultFilesExtensions源码]
publicstaticclassDefaultFilesExtensions { publicstaticIApplicationBuilderUseDefaultFiles(thisIApplicationBuilderapp) { returnapp.UseMiddleware(); } publicstaticIApplicationBuilderUseDefaultFiles(thisIApplicationBuilderapp,stringrequestPath) { returnapp.UseDefaultFiles(newDefaultFilesOptions { RequestPath=newPathString(requestPath) }); } publicstaticIApplicationBuilderUseDefaultFiles(thisIApplicationBuilderapp,DefaultFilesOptionsoptions) { returnapp.UseMiddleware (Options.Create(options)); } }
使用方式和UseStaticFiles、UseDirectoryBrowser是一样,最终都是调用传递DefaultFilesOptions的方法,我们查看一下DefaultFilesOptions的大致实现[点击查看源码]
publicclassDefaultFilesOptions:SharedOptionsBase { publicDefaultFilesOptions() :this(newSharedOptions()) { } publicDefaultFilesOptions(SharedOptionssharedOptions) :base(sharedOptions) { //系统提供的默认页面的名称 DefaultFileNames=newList{ "default.htm", "default.html", "index.htm", "index.html", }; } /// ///通过这个属性可以配置默认文件名称 /// publicIListDefaultFileNames{get;set;} }
和之前的方法如出一辙,都是继承自SharedOptionsBase,通过DefaultFileNames我们可以配置默认文件的名称,默认是default.html/htm和index.html/htm。我们直接查看中间件DefaultFilesMiddleware的源码[点击查看源码]
publicclassDefaultFilesMiddleware { privatereadonlyDefaultFilesOptions_options; privatereadonlyPathString_matchUrl; privatereadonlyRequestDelegate_next; privatereadonlyIFileProvider_fileProvider; publicDefaultFilesMiddleware(RequestDelegatenext,IWebHostEnvironmenthostingEnv,IOptionsoptions) { _next=next; _options=options.Value; _fileProvider=_options.FileProvider??Helpers.ResolveFileProvider(hostingEnv); _matchUrl=_options.RequestPath; } publicTaskInvoke(HttpContextcontext) { //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:outvarsubpath)) { //根据匹配路径获取物理路径对应的信息 vardirContents=_fileProvider.GetDirectoryContents(subpath.Value); if(dirContents.Exists) { //循环配置的默认文件名称 for(intmatchIndex=0;matchIndex<_options.DefaultFileNames.Count;matchIndex++) { stringdefaultFile=_options.DefaultFileNames[matchIndex]; //匹配配置的启用默认文件的路径+遍历到的默认文件名称的路径是否存在 varfile=_fileProvider.GetFileInfo(subpath.Value+defaultFile); if(file.Exists) { //判断请求路径是否已"/"结尾,如果不是则从定向(这个点个人感觉可以改进) if(!Helpers.PathEndsInSlash(context.Request.Path)) { context.Response.StatusCode=StatusCodes.Status301MovedPermanently; varrequest=context.Request; varredirect=UriHelper.BuildAbsolute(request.Scheme,request.Host,request.PathBase,request.Path+"/",request.QueryString); context.Response.Headers[HeaderNames.Location]=redirect; returnTask.CompletedTask; } //如果匹配的上,则将配置的启用默认文件的路径+遍历到的默认文件名称的路径组合成新的Path交给_next(context) //比如将组成类似这种路径/staticfiles/index.html向下传递 context.Request.Path=newPathString(context.Request.Path.Value+defaultFile); break; } } } } return_next(context); } }
这个中间件的实现思路也非常简单主要的工作就是,匹配配置的启用默认文件的路径+遍历到的默认文件名称的路径是否存在,如果匹配的上,则将配置的启用默认文件的路径+遍历到的默认文件名称的路径组合成新的Path(比如/staticfiles/index.html)交给后续的中间件去处理。这里值得注意的是UseDefaultFiles必须要配合UseStaticFiles一起使用,而且注册位置要出现在UseStaticFiles之上。这也是为什么UseDefaultFiles只需要匹配到默认文件所在的路径并重新赋值给context.Request.Path既可的原因。
当然我们也可以自定义默认文件的名称,因为只要能匹配的到具体的文件既可
vardefaultFilesOptions=newDefaultFilesOptions { RequestPath="/staticfiles", FileProvider=fileProvider }; //我们可以清除掉系统默认的默认文件名称 defaultFilesOptions.DefaultFileNames.Clear(); defaultFilesOptions.DefaultFileNames.Add("mydefault.html"); app.UseDefaultFiles(defaultFilesOptions);
总结
通过上面的介绍我们已经大致了解了静态文件处理的大致实现思路,相对于传统的Asp.Net程序我们可以更方便的处理静态文件信息,但是思路是一致的,IIS会优先处理静态文件,如果静态文件处理不了的情况才会交给程序去处理。ASP.NETCore也不例外,通过我们查看中间件源码里的context.GetEndpoint()==null判断可以知道,ASP.NETCore更希望我们优先去处理静态文件,而不是任意出现在其他位置去处理。关于ASP.NETCore处理静态文件的讲解就到这里,欢迎评论区探讨交流。
到此这篇关于浅谈ASP.NETCore静态文件处理源码探究的文章就介绍到这了,更多相关ASP.NETCore静态文件处理内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。