最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談ASP.NET Core靜態(tài)文件處理源碼探究

 更新時間:2020年08月14日 11:37:28   作者:yi念之間  
這篇文章主要介紹了淺談ASP.NET Core靜態(tài)文件處理源碼探究,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

    靜態(tài)文件(如 HTML、CSS、圖像和 JavaScript)等是Web程序的重要組成部分。傳統(tǒng)的ASP.NET項目一般都是部署在IIS上,IIS是一個功能非常強大的服務器平臺,可以直接處理接收到的靜態(tài)文件處理而不需要經(jīng)過應用程序池處理,所以很多情況下對于靜態(tài)文件的處理程序本身是無感知的。ASP.NET Core則不同,作為Server的Kestrel服務是宿主到程序上的,由宿主運行程序啟動Server然后可以監(jiān)聽請求,所以通過程序我們直接可以處理靜態(tài)文件相關。靜態(tài)文件默認存儲到項目的wwwroot目錄中,當然我們也可以自定義任意目錄去處理靜態(tài)文件??傊贏SP.NET Core我們可以處理靜態(tài)文件相關的請求。

StaticFile三劍客

    通常我們在說道靜態(tài)文件相關的時候會涉及到三個話題分別是啟用靜態(tài)文件、默認靜態(tài)頁面、靜態(tài)文件目錄瀏覽,在ASP.NET Core分別是通過UseStaticFiles、UseDefaultFiles、UseDirectoryBrowser三個中間件去處理。只有配置了相關中間件才能去操作對應的處理,相信大家對這種操作已經(jīng)很熟了。靜態(tài)文件操作相關的源碼都位于GitHub aspnetcore倉庫中的https://github.com/dotnet/aspnetcore/tree/v3.1.6/src/Middleware/StaticFiles/src目錄。接下來我們分別探究這三個中間件的相關代碼,來揭開靜態(tài)文件處理的神秘面紗。

UseStaticFiles

UseStaticFiles中間件使我們處理靜態(tài)文件時最常使用的中間件,因為只有開啟了這個中間件我們才能使用靜態(tài)文件,比如在使用MVC開發(fā)的時候需要私用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是自定義使用靜態(tài)文件時的配置信息類,接下來我們大致看一下具體包含哪些配置項[點擊查看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; }
  }
}

我們自定義靜態(tài)文件訪問時,最常用到的就是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))
    {
    }
    //根據(jù)請求文件名稱判斷是否可以匹配到對應的MimeType,如果匹配到則返回contentType
    else if (!LookupContentType(_contentTypeProvider, _options, subPath, out var contentType))
    {
    }
    else
    {  
      //執(zhí)行靜態(tài)文件操作
      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
    {  
      //靜態(tài)文件處理
      return fileContext.ServeStaticFile(context, _next);
    }
    return _next(context);
  }
}

關于FileExtensionContentTypeProvider這里就不作講解了,主要是承載文件擴展名和MimeType的映射關系代碼不復雜,但是映射關系比較多,有興趣的可以自行查看FileExtensionContentTypeProvider源碼,通過上面我們可以看到,最終執(zhí)行文件相關操作的是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()
  {
    //判斷根據(jù)請求路徑是否可以獲取到文件信息
    _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,主要是獲取和輸出靜態(tài)文件輸出緩存相關的內(nèi)容
    //2.我們之前提到的OnPrepareResponse也是在這里執(zhí)行的
    ComprehendRequestHeaders();
    //根據(jù)ComprehendRequestHeaders方法獲取到的文件狀態(tài)進行判斷
    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數(shù)據(jù)
    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中間件的實現(xiàn)

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判斷根據(jù)匹配出來的路徑能否查找到真實的物理路徑
    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;
      }
      //返回展示目錄的內(nèi)容
      return _formatter.GenerateContentAsync(context, contents);
    }
    return _next(context);
  }
  
  /// <summary>
  /// 根據(jù)請求路徑匹配到物理路徑信息是否存在,存在則返回路徑信息
  /// </summary>
  private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents)
  {
    contents = _fileProvider.GetDirectoryContents(subpath.Value);
    return contents.Exists;
  }
}

這個操作相對簡單了許多,主要就是判斷請求路徑能否和預設置的路徑匹配的到,如果匹配到則獲取可以操作當前目錄內(nèi)容IDirectoryContents然后通過IDirectoryFormatter輸出如何展示目錄內(nèi)容,關于IDirectoryFormatter的默認實現(xiàn)類HtmlDirectoryFormatter這里就不展示里面的代碼了,邏輯非常的加單就是拼接成table的html代碼然后輸出,有興趣的同學可自行查看源碼[點擊查看HtmlDirectoryFormatter源碼],如果自定義的話規(guī)則也非常簡單,主要看你想輸出啥

public class TreeDirectoryFormatter: IDirectoryFormatter
{
  public Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents)
  {
    //遍歷contents實現(xiàn)你想展示的方式
  }
}

然后在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
});

老規(guī)矩,我們查看下注冊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的大致實現(xiàn)[點擊查看源碼]

public class DefaultFilesOptions : SharedOptionsBase
{
  public DefaultFilesOptions()
    : this(new SharedOptions())
  {
  }

  public DefaultFilesOptions(SharedOptions sharedOptions)
    : base(sharedOptions)
  {
    //系統(tǒng)提供的默認頁面的名稱
    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))
    {
      //根據(jù)匹配路徑獲取物理路徑對應的信息
      var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);
      if (dirContents.Exists)
      {
        //循環(huán)配置的默認文件名稱
        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);
  }
}

這個中間件的實現(xiàn)思路也非常簡單主要的工作就是,匹配配置的啟用默認文件的路徑+遍歷到的默認文件名稱的路徑是否存在,如果匹配的上,則將配置的啟用默認文件的路徑+遍歷到的默認文件名稱的路徑組合成新的Path(比如/staticfiles/index.html)交給后續(xù)的中間件去處理。這里值得注意的是UseDefaultFiles 必須要配合UseStaticFiles一起使用,而且注冊位置要出現(xiàn)在UseStaticFiles之上。這也是為什么UseDefaultFiles只需要匹配到默認文件所在的路徑并重新賦值給context.Request.Path既可的原因。
當然我們也可以自定義默認文件的名稱,因為只要能匹配的到具體的文件既可

var defaultFilesOptions = new DefaultFilesOptions
{
  RequestPath = "/staticfiles",
  FileProvider = fileProvider
};
//我們可以清除掉系統(tǒng)默認的默認文件名稱
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(defaultFilesOptions);

總結

    通過上面的介紹我們已經(jīng)大致了解了靜態(tài)文件處理的大致實現(xiàn)思路,相對于傳統(tǒng)的Asp.Net程序我們可以更方便的處理靜態(tài)文件信息,但是思路是一致的,IIS會優(yōu)先處理靜態(tài)文件,如果靜態(tài)文件處理不了的情況才會交給程序去處理。ASP.NET Core也不例外,通過我們查看中間件源碼里的context.GetEndpoint()==null判斷可以知道,ASP.NET Core更希望我們優(yōu)先去處理靜態(tài)文件,而不是任意出現(xiàn)在其他位置去處理。關于ASP.NET Core處理靜態(tài)文件的講解就到這里,歡迎評論區(qū)探討交流。

到此這篇關于淺談ASP.NET Core靜態(tài)文件處理源碼探究的文章就介紹到這了,更多相關ASP.NET Core靜態(tài)文件處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • .NetCore利用BlockingCollection實現(xiàn)簡易消息隊列

    .NetCore利用BlockingCollection實現(xiàn)簡易消息隊列

    這篇文章主要介紹了.NetCore利用BlockingCollection實現(xiàn)簡易消息隊列,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • asp.net驗證提示美化效果代碼(打包下載)

    asp.net驗證提示美化效果代碼(打包下載)

    前段時間做了個ymPrompt提示效果的驗證控件,ymPrompt是彈出提示的。彈出提示會增加無畏的鼠標點擊動作。
    2010-09-09
  • .NET實現(xiàn)微信公共平臺上傳下載多媒體文件

    .NET實現(xiàn)微信公共平臺上傳下載多媒體文件

    這篇文章主要介紹了.NET實現(xiàn)微信公共平臺上傳下載多媒體文件的相關資料,感興趣的小伙伴們可以參考一下
    2016-07-07
  • asp.net中Fine Uploader文件上傳組件使用介紹

    asp.net中Fine Uploader文件上傳組件使用介紹

    最近在處理后臺數(shù)據(jù)時需要實現(xiàn)文件上傳.考慮到對瀏覽器適配上采用Fine Uploader. Fine Uploader 采用ajax方式實現(xiàn)對文件上傳.同時在瀏覽器中直接支持文件拖拽[對瀏覽器版本有要求類似IE版本必須是9或是更高的IE10].
    2013-01-01
  • 為ASP.NET MVC及WebApi添加路由優(yōu)先級

    為ASP.NET MVC及WebApi添加路由優(yōu)先級

    這是一個對Asp.Net Mvc的一個很小的功能拓展,小項目可能不太需要這個功能,但有時候項目大了注冊的路由不生效時你應該要想到有可能是因為路由順序的原因,這時這個路由優(yōu)先級的功能有可能就會給你帶來便利。
    2015-10-10
  • ASP.Net 分頁控件源碼

    ASP.Net 分頁控件源碼

    由于.net自帶的分頁功能極其死板,自定義性不強不能滿足需求 俺花了一個多星期的時間 寫成的一個.net分頁控件
    2007-03-03
  • 在.NET Core中使用異步編程的方法步驟

    在.NET Core中使用異步編程的方法步驟

    這篇文章主要介紹了在.NET Core中使用異步編程的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-03-03
  • asp.net 相關文章實現(xiàn)方法

    asp.net 相關文章實現(xiàn)方法

    大家或許會覺得很驚訝:為什么靈感之源會討論SQL?或許應該這樣說吧:搞業(yè)務系統(tǒng),不跟SQL扯上關系似乎比較難。
    2009-05-05
  • URL中去除指定參數(shù)實現(xiàn)C#代碼

    URL中去除指定參數(shù)實現(xiàn)C#代碼

    URL中去除指定參數(shù)在項目開發(fā)中還是很常見的,本文將介紹下它在c#代碼中的實現(xiàn),感興趣的朋友可以參考下哈
    2013-04-04
  • ASP.NET2.0數(shù)據(jù)庫入門之SqlDataSource

    ASP.NET2.0數(shù)據(jù)庫入門之SqlDataSource

    ASP.NET2.0數(shù)據(jù)庫入門之SqlDataSource...
    2006-09-09

最新評論

江门市| 芷江| 舞阳县| 通渭县| 无锡市| 九寨沟县| 高安市| 出国| 雅江县| 正镶白旗| 布拖县| 理塘县| 临潭县| 张家口市| 惠安县| 渑池县| 榕江县| 小金县| 澄江县| 西青区| 芷江| 乐昌市| 临澧县| 新邵县| 庆安县| 吉林市| 平舆县| 正定县| 永州市| 依兰县| 雷州市| 成安县| 衡阳县| 阿尔山市| 三门峡市| 金秀| 黑水县| 通榆县| 武城县| 镶黄旗| 昌宁县|