.Net Core中間件之靜態(tài)文件(StaticFiles)示例詳解
一、介紹
靜態(tài)文件(static files),諸如 HTML、CSS、圖片和 JavaScript 之類的資源會(huì)被 ASP.NET Core 應(yīng)用直接提供給客戶端。
在介紹靜態(tài)文件中間件之前,先介紹 ContentRoot和WebRoot概念。
ContentRoot:指web的項(xiàng)目的文件夾,包括bin和webroot文件夾。
WebRoot:一般指ContentRoot路徑下的wwwroot文件夾。
介紹這個(gè)兩個(gè)概念是因?yàn)殪o態(tài)資源文件一般存放在WebRoot路徑下,也就是wwwroot。下面為這兩個(gè)路徑的配置,如下所示:
public static void Main(string[] args)
{var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWebRoot(Directory.GetCurrentDirectory() + @"\wwwroot\")
.UseEnvironment(EnvironmentName.Development)
.Build();
host.Run();
}
上面的代碼將ContentRoot路徑和WebRoot路徑都配置了,其實(shí)只需配置ContentRoot路徑,WebRoot默認(rèn)為ContentRoot路徑下的wwwroot文件夾路徑。
在了解靜態(tài)文件中間件前,還需要了解HTTP中關(guān)于靜態(tài)文件緩存的機(jī)制。跟靜態(tài)文件相關(guān)的HTTP頭部主要有Etag和If-None-Match。
下面為訪問靜態(tài)文件服務(wù)器端和客戶端的流程:
1、客戶端第一次向客戶端請(qǐng)求一個(gè)靜態(tài)文件。
2、服務(wù)器收到客戶端訪問靜態(tài)文件的請(qǐng)求,服務(wù)器端會(huì)根據(jù)靜態(tài)文件最后的修改時(shí)間和文件內(nèi)容的長(zhǎng)度生成一個(gè)Hash值,并將這個(gè)值放到請(qǐng)求頭ETag中。
3、客戶端第二次發(fā)起同一個(gè)請(qǐng)求時(shí),因?yàn)橹罢?qǐng)求過此文件,所以本地會(huì)有緩存。在請(qǐng)求時(shí)會(huì)在請(qǐng)求頭中加上If-Nono-Match,其值為服務(wù)器返回的ETag的值。
4、服務(wù)器端比對(duì)發(fā)送的來(lái)的If-None-Match的值和本地計(jì)算的ETag的值是否相同。如果相同,返回304狀態(tài)碼,客戶端繼續(xù)使用本地緩存。如果不相同,返回200狀態(tài)碼,客戶端重新解析服務(wù)器返回的數(shù)據(jù),不使用本地緩存。
具體看下面例子。
二、簡(jiǎn)單使用
2.1 最簡(jiǎn)單的使用
最簡(jiǎn)單的使用就是在Configure中加入下面一句話,然后將靜態(tài)文件放到webRoot的路徑下,我沒有修改webRoot指定的路徑,所以就是wwwroot文件夾。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseMvc();
}
在wwwroot文件夾下放一個(gè)名稱為1.txt的測(cè)試文本,然后通過地址訪問。

這種有一個(gè)缺點(diǎn),暴露這個(gè)文件的路徑在wwwroot下。
2.2 指定請(qǐng)求地址
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(@"C:\Users\Administrator\Desktop"),
RequestPath = new PathString("/Static")
});
//app.UseStaticFiles("/Static");
}
這種指定了靜態(tài)文件存放的路徑為:C:\Users\Administrator\Desktop,不是使用默認(rèn)的wwwroot路徑,就隱藏了文件的真實(shí)路徑,并且需要在地址中加上static才能訪問。
當(dāng)然也可以不指明靜態(tài)文件的路徑,只寫請(qǐng)求路徑,如上面代碼中的注釋的例子。這樣靜態(tài)文件就必須存儲(chǔ)到WebRoot對(duì)應(yīng)的目錄下了。如果WebRoot的目錄對(duì)應(yīng)的是wwwroot,靜態(tài)文件就放到wwwroot文件夾中。

下面通過例子看一下靜態(tài)文件的緩存,如果你想做這個(gè)例子,別忘記先清空緩存。

(第一次請(qǐng)求)

(第二次請(qǐng)求 文件相對(duì)第一次請(qǐng)求沒有修改的情況)

(第三次請(qǐng)求 文件相對(duì)第一次請(qǐng)求有修改的情況)
三、源碼分析
源碼在https://github.com/aspnet/StaticFiles,這個(gè)項(xiàng)目還包含有其他中間件。既然是中間件最重要的就是參數(shù)為HttpContext的Invoke方法了,因?yàn)槊恳粋€(gè)請(qǐng)求都要經(jīng)過其處理,然后再交給下一個(gè)中間件處理。
下面為處理流程。
public async Task Invoke(HttpContext context)
{
var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider);
if (!fileContext.ValidateMethod())//靜態(tài)文件的請(qǐng)求方式只能是Get或者Head
{
_logger.LogRequestMethodNotSupported(context.Request.Method);
} //判斷請(qǐng)求的路徑和配置的請(qǐng)求路徑是否匹配。如請(qǐng)求路徑為http://localhost:5000/static/1.txt //配置為RequestPath = new PathString("/Static") //則匹配,并將文件路徑賦值給StaticFileContext中點(diǎn)的_subPath
else if (!fileContext.ValidatePath())
{
_logger.LogPathMismatch(fileContext.SubPath);
} //通過獲取要訪問文件的擴(kuò)展名,獲取此文件對(duì)應(yīng)的MIME類型, //如果找到文件對(duì)應(yīng)的MIME,返回True,并將MIME類型賦值給StaticFileContext中的_contextType //沒有找到返回False.
else if (!fileContext.LookupContentType())
{
_logger.LogFileTypeNotSupported(fileContext.SubPath);
} //判斷訪問的文件是否存在?! ?//如果存在返回True,并根據(jù)文件的最后修改時(shí)間和文件的長(zhǎng)度,生成Hash值,并將值賦值給_etag,也就是相應(yīng)頭中的Etag?! ?/如果不存在 返回False,進(jìn)入下一個(gè)中間件中處理
else if (!fileContext.LookupFileInfo())
{
_logger.LogFileNotFound(fileContext.SubPath);
}
else
{
fileContext.ComprehendRequestHeaders(); //根據(jù)StaticFileContext中的值,加上對(duì)應(yīng)的相應(yīng)頭,并發(fā)送響應(yīng)。具體調(diào)用方法在下面
switch (fileContext.GetPreconditionState())
{
case StaticFileContext.PreconditionState.Unspecified:
case StaticFileContext.PreconditionState.ShouldProcess:
if (fileContext.IsHeadMethod)
{
await fileContext.SendStatusAsync(Constants.Status200Ok);
return;
}
try
{
if (fileContext.IsRangeRequest)
{
await fileContext.SendRangeAsync();
return;
}
await fileContext.SendAsync();
_logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath);
return;
}
catch (FileNotFoundException)
{
context.Response.Clear();
}
break;
case StaticFileContext.PreconditionState.NotModified:
_logger.LogPathNotModified(fileContext.SubPath);
await fileContext.SendStatusAsync(Constants.Status304NotModified);
return;
case StaticFileContext.PreconditionState.PreconditionFailed:
_logger.LogPreconditionFailed(fileContext.SubPath);
await fileContext.SendStatusAsync(Constants.Status412PreconditionFailed);
return;
default:
var exception = new NotImplementedException(fileContext.GetPreconditionState().ToString());
Debug.Fail(exception.ToString());
throw exception;
}
} //進(jìn)入下一個(gè)中間件中處理
await _next(context);
}
添加響應(yīng)頭的方法:
public void ApplyResponseHeaders(int statusCode)
{
_response.StatusCode = statusCode;
if (statusCode < 400)
{
if (!string.IsNullOrEmpty(_contentType))
{
_response.ContentType = _contentType;
} //設(shè)置響應(yīng)頭中最后修改時(shí)間、ETag和accept-ranges
_responseHeaders.LastModified = _lastModified;
_responseHeaders.ETag = _etag;
_responseHeaders.Headers[HeaderNames.AcceptRanges] = "bytes";
}
if (statusCode == Constants.Status200Ok)
{
_response.ContentLength = _length;
}
_options.OnPrepareResponse(new StaticFileResponseContext()
{
Context = _context,
File = _fileInfo,
});
}
校驗(yàn)文件是否修改的方法:
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();
//通過修改時(shí)間和文件長(zhǎng)度,得到ETag的值
long etagHash = _lastModified.ToFileTime() ^ _length;
_etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
}
return _fileInfo.Exists;
}
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- ASP.NET Core 應(yīng)用程序中的靜態(tài)文件中間件的實(shí)現(xiàn)
- .net core異常中間件的使用
- ASP.NET Core中間件初始化的實(shí)現(xiàn)
- 詳解ASP.NET Core 中基于工廠的中間件激活的實(shí)現(xiàn)方法
- 在 asp.net core 的中間件中返回具體的頁(yè)面的實(shí)現(xiàn)方法
- ASP.NET Core自定義中間件如何讀取Request.Body與Response.Body的內(nèi)容詳解
- .net core webapi通過中間件獲取請(qǐng)求和響應(yīng)內(nèi)容的方法
- 利用.net core實(shí)現(xiàn)反向代理中間件的方法
- 如何給asp.net core寫個(gè)中間件記錄接口耗時(shí)
- ASP.NET Core中間件計(jì)算Http請(qǐng)求時(shí)間示例詳解
- ASP.NET Core應(yīng)用錯(cuò)誤處理之ExceptionHandlerMiddleware中間件呈現(xiàn)“定制化錯(cuò)誤頁(yè)面”
- .net core靜態(tài)中間件的使用
相關(guān)文章
使用.NET?6開發(fā)TodoList應(yīng)用之引入數(shù)據(jù)存儲(chǔ)的思路詳解
在這篇文章中,我們僅討論如何實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)基礎(chǔ)設(shè)施的引入,具體的實(shí)體定義和操作后面專門來(lái)說(shuō)。對(duì).NET?6開發(fā)TodoList引入數(shù)據(jù)存儲(chǔ)相關(guān)知識(shí)感興趣的朋友一起看看吧2021-12-12
ASP.NET GridView 實(shí)現(xiàn)課程表顯示(動(dòng)態(tài)合并單元格)實(shí)現(xiàn)步驟
GridView,ASP.NET中很常用的數(shù)據(jù)顯示控件,這里,我將用這個(gè)控件來(lái)實(shí)現(xiàn)課程表的顯示。首先說(shuō)說(shuō)課程表的顯示與普通記錄的顯示有何不同?感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02
asp.net實(shí)現(xiàn)從Txt文件讀取數(shù)據(jù)到數(shù)據(jù)視圖的方法
這篇文章主要介紹了asp.net實(shí)現(xiàn)從Txt文件讀取數(shù)據(jù)到數(shù)據(jù)視圖的方法,涉及asp.net針對(duì)文本文件的遍歷操作與DataView的寫入操作相關(guān)技巧,需要的朋友可以參考下2015-12-12
.NET Core 1.0創(chuàng)建Self-Contained控制臺(tái)應(yīng)用
這篇文章主要為大家詳細(xì)介紹了.NET Core 1.0創(chuàng)建Self-Contained控制臺(tái)應(yīng)用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
ASP.NET中配合JS實(shí)現(xiàn)頁(yè)面計(jì)時(shí)(定時(shí))自動(dòng)跳轉(zhuǎn)
這篇文章主要介紹了ASP.NET中配合JS實(shí)現(xiàn)頁(yè)面計(jì)時(shí)(定時(shí))自動(dòng)跳轉(zhuǎn),本文主要依靠JS實(shí)現(xiàn)需求,只是在ASP.NET中實(shí)現(xiàn)而已,需要的朋友可以參考下2015-06-06
ASP.NET搭配Ajax實(shí)現(xiàn)搜索提示功能
為了更好的用戶體驗(yàn),不論是桌面軟件還是網(wǎng)站,在搜索查詢的輸入中都會(huì)加入提示功能,就像百度搜索一樣!今天筆者就ASP.NET編程介紹一下如何利用Ajax來(lái)實(shí)現(xiàn)搜索信息提示功能。2015-09-09
.NET下為百度文本編輯器UEditor增加圖片刪除功能示例
今天下載了目前最新版1.2.5為版本看更新記錄,主要是對(duì)表格做個(gè)修改,我下載用上,上傳圖片的刪除功能給取消了,下面與大家分享下增加圖片刪除功能示例2013-05-05
ASP.NET MVC+EF在服務(wù)端分頁(yè)使用jqGrid以及jquery Datatables的注意事項(xiàng)
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC+EF在服務(wù)端分頁(yè)使用jqGrid以及jquery Datatables的注意事項(xiàng),感興趣的小伙伴們可以參考一下2016-06-06

