zoukankan      html  css  js  c++  java
  • 理解ASP.NET Core

    注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录

    提供静态文件

    静态文件默认存放在 Web根目录(Web Root) 中,路径为 项目根目录(Content Root) 下的wwwroot文件夹,也就是{Content Root}/wwwroot

    如果你调用了Host.CreateDefaultBuilder方法,那么在该方法中,会通过UseContentRoot方法,将程序当前工作目录(Directory.GetCurrentDirectory())设置为项目根目录。具体可以查看主机一节。

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    

    当然,你也可以通过UseWebRoot扩展方法将默认的路径{Content Root}/wwwroot修改为自定义目录(不过,你改它干啥捏?)

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                // 配置静态资源的根目录为 mywwwroot, 默认为 wwwroot
                webBuilder.UseWebRoot("mywwwroot");
    
                webBuilder.UseStartup<Startup>();
            });
    

    为了方便,后面均使用 wwwroot 来表示Web根目录

    首先,我们先在 wwwroot 文件夹下创建一个名为 config.json 的文件,内容随便填写

    注意,确保 wwwroot 下的文件的属性为“如果较新则复制”或“始终复制”。

    接着,我们通过UseStaticFiles扩展方法,来注册静态文件中间件StaticFileMiddleware

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseStaticFiles();
    }
    

    现在,尝试一下通过 http://localhost:5000/config.json 来获取 wwwroot/config.json 的文件内容吧

    如果你的项目中启用SwaggerUI,那么你会发现,即使你没有手动通过调用UseStaticFiles()添加中间件,你也可以访问 wwwroot 文件下的文件,这是因为 SwaggerUIMiddleware 中使用了 StaticFileMiddleware

    提供Web根目录之外的文件

    上面我们已经能够提供 wwwroot 文件夹内的静态文件了,那如果我们的文件不在 wwwroot 文件夹内,那如何提供呢?

    很简单,我们可以针对StaticFileMiddleware中间件进行一些额外的配置,了解一下配置项:

    public abstract class SharedOptionsBase
    {
        // 用于自定义静态文件的相对请求路径
        public PathString RequestPath { get; set; }
    
        // 文件提供程序
        public IFileProvider FileProvider { get; set; }
    
        // 是否补全路径末尾斜杠“/”,并重定向
        public bool RedirectToAppendTrailingSlash { get; set; }
    }
    
    public class StaticFileOptions : SharedOptionsBase
    {
        // ContentType提供程序
        public IContentTypeProvider ContentTypeProvider { get; set; }
        
        // 如果 ContentTypeProvider 无法识别文件类型,是否仍作为默认文件类型提供
        public bool ServeUnknownFileTypes { get; set; }
        
        // 当 ServeUnknownFileTypes = true 时,若出现无法识别的文件类型,则将该属性的值作为此文件的类型
        // 当 ServeUnknownFileTypes = true 时,必须赋值该属性,才会生效
        public string DefaultContentType { get; set; }
        
        // 当注册了HTTP响应压缩中间件时,是否对文件进行压缩
        public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress;
        
        // 在HTTP响应的 Status Code 和 Headers 设置完毕之后,Body 写入之前进行调用
        // 用于添加或更改 Headers
        public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
    }
    

    假设我们现在有这样一个文件目录结构:

    • wwwroot
      • config.json
    • files
      • file.json

    然后,除了用于提供 wwwroot 静态文件的中间件外,我们还要注册一个用于提供 files 静态文件的中间件:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 提供 wwwroot 静态文件
        app.UseStaticFiles();
    
        // 提供 files 静态文件
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")),
            // 指定文件的访问路径,允许与 FileProvider 中的文件夹不同名
            // 如果不指定,则可通过 http://localhost:5000/file.json 获取,
            // 如果指定,则需要通过 http://localhost:5000/files/file.json 获取
            RequestPath = "/files",
            OnPrepareResponse = ctx =>
            {
                // 配置前端缓存 600s(为了后续示例的良好运行,建议先不要配置该Header)
                ctx.Context.Response.Headers.Add(HeaderNames.CacheControl, "public,max-age=600");
            }
        });
    }
    

    建议将公开访问的文件放置到 wwwroot 目录下,而将需要授权访问的文件放置到其他目录下(在调用UseAuthorization之后调用UseStaticFiles并指定文件目录)

    提供目录浏览

    上面,我们可以通过Url访问某一个文件的内容,而通过UseDirectoryBrowser,注册DirectoryBrowserMiddleware中间件,可以让我们在浏览器中以目录的形式来访问文件列表。

    另外,DirectoryBrowserMiddleware中间件的可配置项除了SharedOptionsBase中的之外,还有一个Formatter,用于自定义目录视图。

    public class DirectoryBrowserOptions : SharedOptionsBase
    {
        public IDirectoryFormatter Formatter { get; set; }
    }
    

    示例如下:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDirectoryBrowser();
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 通过 http://localhost:5000,即可访问 wwwroot 目录
        app.UseDirectoryBrowser();
        
        // 通过 http://localhost:5000/files,即可访问 files 目录
        app.UseDirectoryBrowser(new DirectoryBrowserOptions
        {
            // 如果指定了没有在 UseStaticFiles 中提供的文件目录,虽然可以浏览文件列表,但是无法访问文件内容
            FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")),
            // 这里一定要和 StaticFileOptions 中的 RequestPath 一致,否则会无法访问文件
            RequestPath = "/files"
        });
    }
    

    提供默认页

    通过UseDefaultFiles,注册DefaultFilesMiddleware中间件,允许在访问静态文件、但未提供文件名的情况下(即传入的是一个目录的路径),提供默认页的展示。

    注意:UseDefaultFiles必须在UseStaticFiles之前进行调用。因为DefaultFilesMiddleware仅仅负责重写Url,实际上默认页文件,仍然是通过StaticFilesMiddleware来提供的。

    默认情况下,该中间件会按照顺序搜索文件目录下的HTML页面文件:

    • default.htm
    • default.html
    • index.htm
    • index.html

    另外,DefaultFilesMiddleware中间件的可配置项除了SharedOptionsBase中的之外,还有一个DefaultFileNames,是个列表,用于自定义默认页的文件名,里面的默认值就是上面提到的4个文件名。

    public class DefaultFilesOptions : SharedOptionsBase
    {
        public IList<string> DefaultFileNames { get; set; }
    }
    

    示例如下:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 会去 wwwroot 寻找 default.htm 、default.html 、index.htm 或 index.html 文件作为默认页
        app.UseDefaultFiles();
    
        // 设置 files 目录的默认页
        var defaultFilesOptions = new DefaultFilesOptions();
        defaultFilesOptions.DefaultFileNames.Clear();
        // 指定默认页名称
        defaultFilesOptions.DefaultFileNames.Add("index1.html");
        // 指定请求路径
        defaultFilesOptions.RequestPath = "/files";
        // 指定默认页所在的目录
        defaultFilesOptions.FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files"));
    
        app.UseDefaultFiles(defaultFilesOptions);
    }
    

    UseFileServer

    UseFileServer集成了UseStaticFilesUseDefaultFilesUseDirectoryBrowser的功能,用起来方便一些,也是我们项目中使用的首选扩展方法。

    先看一下FileServerOptions

    public class FileServerOptions : SharedOptionsBase
    {
        public FileServerOptions()
            : base(new SharedOptions())
        {
            StaticFileOptions = new StaticFileOptions(SharedOptions);
            DirectoryBrowserOptions = new DirectoryBrowserOptions(SharedOptions);
            DefaultFilesOptions = new DefaultFilesOptions(SharedOptions);
            EnableDefaultFiles = true;
        }
    
        public StaticFileOptions StaticFileOptions { get; private set; }
    
        public DirectoryBrowserOptions DirectoryBrowserOptions { get; private set; }
    
        public DefaultFilesOptions DefaultFilesOptions { get; private set; }
    
        // 默认禁用目录浏览
        public bool EnableDirectoryBrowsing { get; set; }
    
        // 默认启用默认页(在构造函数中初始化的)
        public bool EnableDefaultFiles { get; set; }
    }
    

    可以看到,FileServerOptions包含了StaticFileOptionsDirectoryBrowserOptionsDefaultFilesOptions三个选项,可以针对StaticFileMiddlewareDirectoryBrowserMiddlewareDefaultFilesMiddleware进行自定义配置。另外,其默认启用了静态文件和默认页,禁用了目录浏览。

    下面举个例子熟悉一下:

    假设文件目录:

    • files
      • images
        • 1.jpg
      • file.json
      • myindex.html
    public void ConfigureServices(IServiceCollection services)
    {
        // 如果将 EnableDirectoryBrowsing 设为 true,记得注册服务
        services.AddDirectoryBrowser();
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {   
        // 启用 StaticFileMiddleware
        // 启用 DefaultFilesMiddleware
        // 禁用 DirectoryBrowserMiddleware
        // 默认指向 wwwroot
        app.UseFileServer();
        
        // 针对 files 文件夹配置
        var fileServerOptions = new FileServerOptions
        {
            FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")),
            RequestPath = "/files",
            EnableDirectoryBrowsing = true
        };
        fileServerOptions.StaticFileOptions.OnPrepareResponse = ctx =>
        {
            // 配置缓存600s
            ctx.Context.Response.Headers.Add(HeaderNames.CacheControl, "public,max-age=600");
        };
        fileServerOptions.DefaultFilesOptions.DefaultFileNames.Clear();
        fileServerOptions.DefaultFilesOptions.DefaultFileNames.Add("myindex.html");
        app.UseFileServer(fileServerOptions);
    }
    

    当访问 http://localhost:5000/files 时,由于在DefaultFilesOptions.DefaultFileNames中添加了文件名myindex.html,所以可以找到默认页,此时会显示默认页的内容。

    假如我们没有在DefaultFilesOptions.DefaultFileNames中添加文件名myindex.html,那么便找不到默认页,但由于启用了DirectoryBrowsing,所以此时会展示文件列表。

    核心配置项

    FileProvider

    上面我们已经见过PhysicalFileProvider了,它仅仅是众多文件提供程序中的一种。所有的文件提供程序均实现了IFileProvider接口:

    public interface IFileProvider
    {
        // 获取给定路径的目录信息,可枚举该目录中的所有文件
        IDirectoryContents GetDirectoryContents(string subpath);
    
        // 获取给定路径的文件信息
        IFileInfo GetFileInfo(string subpath);
    
        // 创建指定 filter 的 ChangeToken
        IChangeToken Watch(string filter);
    }
    
    public interface IDirectoryContents : IEnumerable<IFileInfo>, IEnumerable
    {
        bool Exists { get; }
    }
    
    public interface IFileInfo
    {
        bool Exists { get; }
    
        bool IsDirectory { get; } 
    
        DateTimeOffset LastModified { get; }
    
        // 字节(bytes)长度
        // 如果是目录或文件不存在,则是 -1
        long Length { get; }
    
        // 目录或文件名,纯文件名,不包括路径
        string Name { get; }
    
        // 文件路径,包含文件名
        // 如果文件无法直接访问,则返回 null
        string PhysicalPath { get; }
    
        // 创建该文件只读流
        Stream CreateReadStream();
    }
    

    常用的文件提供程序有以下三种:

    • PhysicalFileProvider
    • ManifestEmbeddedFileProvider
    • CompositeFileProvider

    glob模式

    在介绍这三种文件提供程序之前,先说一下glob模式,即通配符模式。两个通配符分别是***

    • *:匹配当前目录层级(不包含子目录)下的任何内容、任何文件名或任何文件扩展名,可以通过/.进行分隔。
    • **:匹配目录多层级(包含子目录)的任何内容,用于递归匹配多层级目录的多个文件。

    PhysicalFileProvider

    PhysicalFileProvider用于提供物理文件系统的访问。该提供程序需要将文件路径范围限定在一个目录及其子目录中,不能访问目录外部的内容。

    当实例化该文件提供程序时,需要提供一个绝对的目录路径,作为文件目录的root。

    PhysicalFileProvider目录或文件路径不支持glob(通配符)模式。

    ManifestEmbeddedFileProvider

    ManifestEmbeddedFileProvider用于提供嵌入在程序集中的文件的访问。

    可能你对这个嵌入文件比较陌生,没关系,请按照下面的步骤来:

    • 安装Nuget包:Install-Package Microsoft.Extensions.FileProviders.Embedded
    • 编辑.csproj文件:
      • 添加<GenerateEmbeddedFilesManifest>,并设置为true
      • 使用<EmbeddedResource>添加要嵌入的文件

    以下是 .csproj 文件的示例:

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
        <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="5.0.11" />
      </ItemGroup>
    
      <ItemGroup>
        <EmbeddedResource Include="files**" />
      </ItemGroup>
    </Project>
    

    现在我们通过ManifestEmbeddedFileProvider来提供嵌入到程序集的 files 目录下文件的访问:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
         var fileServerOptions = new FileServerOptions();
        fileServerOptions.StaticFileOptions.FileProvider = new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "/files");
        fileServerOptions.StaticFileOptions.RequestPath = "/files";
    
        app.UseFileServer(fileServerOptions);
    }
    

    现在,你可以通过 http://localhost:5000/files/file.json 来访问文件了。

    CompositeFileProvider

    CompositeFileProvider用于将多种文件提供程序进行集成。

    如:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        var fileServerOptions = new FileServerOptions();
        var fileProvider = new CompositeFileProvider(
            env.WebRootFileProvider,
            new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "/files")
        );
        fileServerOptions.StaticFileOptions.FileProvider = fileProvider;
        fileServerOptions.StaticFileOptions.RequestPath = "/composite";
    
        app.UseFileServer(fileServerOptions);
    }
    

    现在,你可以通过 http://localhost:5000/composite/file.json 来访问文件了。

    ContentTypeProvider

    Http请求头中的Content-Type大家一定很熟悉,ContentTypeProvider就是用来提供文件扩展名和MIME类型映射关系的。

    若我们没有显示指定ContentTypeProvider,则框架默认使用FileExtensionContentTypeProvider,其实现了接口IContentTypeProvider

    public interface IContentTypeProvider
    {
        // 尝试根据文件路径,获取对应的 MIME 类型
        bool TryGetContentType(string subpath, out string contentType);
    }
    
    public class FileExtensionContentTypeProvider : IContentTypeProvider
    {
        public FileExtensionContentTypeProvider()
            : this(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                // ...此处省略一万字
            }
        {
        }
    
        public FileExtensionContentTypeProvider(IDictionary<string, string> mapping)
        {
            Mappings = mapping;
        }
    
        public IDictionary<string, string> Mappings { get; private set; }
    
        public bool TryGetContentType(string subpath, out string contentType)
        {
            string extension = GetExtension(subpath);
            if (extension == null)
            {
                contentType = null;
                return false;
            }
            return Mappings.TryGetValue(extension, out contentType);
        }
    
        private static string GetExtension(string path)
        {
            // 没有使用 Path.GetExtension() 的原因是:当路径中存在无效字符时,其会抛出异常,而这里不应抛出异常。
    
            if (string.IsNullOrWhiteSpace(path))
            {
                return null;
            }
    
            int index = path.LastIndexOf('.');
            if (index < 0)
            {
                return null;
            }
    
            return path.Substring(index);
        }
    }
    

    FileExtensionContentTypeProvider的无参构造函数中,默认添加了380种已知的文件扩展名和MIME类型的映射,存放在Mappings属性中。你也可以添加自定义的映射,或移除不想要的映射。

    核心中间件

    StaticFileMiddleware

    通过UseStaticFiles扩展方法,可以方便的注册StaticFileMiddleware中间件:

    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));
        }
    }
    

    紧接着查看StaticFileMiddlewareInvoke方法:

    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,则默认使用 FileExtensionContentTypeProvider
            _contentTypeProvider = _options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
            // 若未指定 FileProvider,则默认使用 hostingEnv.WebRootFileProvider
            _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
            _matchUrl = _options.RequestPath;
            _logger = loggerFactory.CreateLogger<StaticFileMiddleware>();
        }
    
        public Task Invoke(HttpContext context)
        {
            // 若已匹配到 Endpoint,则跳过
            if (!ValidateNoEndpoint(context))
            {
                _logger.EndpointMatched();
            }
            // 若HTTP请求方法不是 Get,也不是 Head,则跳过
            else if (!ValidateMethod(context))
            {
                _logger.RequestMethodNotSupported(context.Request.Method);
            }
            // 如果请求路径不匹配,则跳过
            else if (!ValidatePath(context, _matchUrl, out var subPath))
            {
                _logger.PathMismatch(subPath);
            }
            // 如果 ContentType 不受支持,则跳过
            else if (!LookupContentType(_contentTypeProvider, _options, subPath, out var contentType))
            {
                _logger.FileTypeNotSupported(subPath);
            }
            else
            {
                // 尝试提供静态文件
                return TryServeStaticFile(context, contentType, subPath);
            }
    
            return _next(context);
        }
    
        private static bool ValidateNoEndpoint(HttpContext context) => context.GetEndpoint() == null;
    
        private static bool ValidateMethod(HttpContext context) => Helpers.IsGetOrHeadMethod(context.Request.Method);
    
        internal static bool ValidatePath(HttpContext context, PathString matchUrl, out PathString subPath) => Helpers.TryMatchPath(context, matchUrl, forDirectory: false, out subPath);
    
        internal static bool LookupContentType(IContentTypeProvider contentTypeProvider, StaticFileOptions options, PathString subPath, out string contentType)
        {
            // 查看 Provider 中是否支持该 ContentType
            if (contentTypeProvider.TryGetContentType(subPath.Value, out contentType))
            {
                return true;
            }
    
            // 如果提供未知文件类型,则将其设置为默认 ContentType
            if (options.ServeUnknownFileTypes)
            {
                contentType = options.DefaultContentType;
                return true;
            }
    
            return false;
        }
    
        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);
        }
    }
    

    DirectoryBrowserMiddleware

    通过UseDirectoryBrowser扩展方法,可以方便的注册DirectoryBrowserMiddleware中间件:

    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));
        }
    }
    

    紧接着查看DirectoryBrowserMiddlewareInvoke方法:

    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,则默认使用 hostingEnv.WebRootFileProvider
            _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
            _formatter = _options.Formatter ?? new HtmlDirectoryFormatter(encoder);
            _matchUrl = _options.RequestPath;
        }
    
        public Task Invoke(HttpContext context)
        {
            // 若已匹配到 Endpoint,则跳过
            // 若HTTP请求方法不是 Get,也不是 Head,则跳过
            // 如果请求路径不匹配,则跳过
            // 若文件目录不存在,则跳过
            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 (_options.RedirectToAppendTrailingSlash && !Helpers.PathEndsInSlash(context.Request.Path))
                {
                    Helpers.RedirectToPathWithSlash(context);
                    return Task.CompletedTask;
                }
    
                // 生成文件浏览视图
                return _formatter.GenerateContentAsync(context, contents);
            }
    
            return _next(context);
        }
    
        private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents)
        {
            contents = _fileProvider.GetDirectoryContents(subpath.Value);
            return contents.Exists;
        }
    }
    

    DefaultFilesMiddleware

    通过UseDefaultFiles扩展方法,可以方便的注册DefaultFilesMiddleware中间件:

    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));
        }
    }
    

    紧接着查看DefaultFilesMiddlewareInvoke方法:

    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,则默认使用 hostingEnv.WebRootFileProvider
            _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
            _matchUrl = _options.RequestPath;
        }
        
        public Task Invoke(HttpContext context)
        {
            // 若已匹配到 Endpoint,则跳过
            // 若HTTP请求方法不是 Get,也不是 Head,则跳过
            // 如果请求路径不匹配,则跳过
            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 (_options.RedirectToAppendTrailingSlash && !Helpers.PathEndsInSlash(context.Request.Path))
                            {
                                Helpers.RedirectToPathWithSlash(context);
                                return Task.CompletedTask;
                            }
                            
                            // 重写为默认页的Url,后续通过 StaticFileMiddleware 提供该页面
                            context.Request.Path = new PathString(Helpers.GetPathValueWithSlash(context.Request.Path) + defaultFile);
                            break;
                        }
                    }
                }
            }
    
            return _next(context);
        }
    }
    

    FileServer

    FileServer并不是某个具体的中间件,它的实现还是依赖了StaticFileMiddlewareDirectoryBrowserMiddlewareDefaultFilesMiddleware这3个中间件。不过,我们可以看一下UseFileServer里的逻辑:

    public static class FileServerExtensions
    {
        public static IApplicationBuilder UseFileServer(this IApplicationBuilder app)
        {
            return app.UseFileServer(new FileServerOptions());
        }
    
        public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, bool enableDirectoryBrowsing)
        {
            return app.UseFileServer(new FileServerOptions
            {
                EnableDirectoryBrowsing = enableDirectoryBrowsing
            });
        }
    
        public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, string requestPath)
        {
            return app.UseFileServer(new FileServerOptions
            {
                RequestPath = new PathString(requestPath)
            });
        }
    
        public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, FileServerOptions options)
        {
            // 启用默认页
            if (options.EnableDefaultFiles)
            {
                app.UseDefaultFiles(options.DefaultFilesOptions);
            }
    
            // 启用目录浏览
            if (options.EnableDirectoryBrowsing)
            {
                app.UseDirectoryBrowser(options.DirectoryBrowserOptions);
            }
    
            return app.UseStaticFiles(options.StaticFileOptions);
        }
    }
    

    FileProvider in IWebHostingEnvironment

    在接口IHostingEnvironment中,包含ContentRootFileProviderWebRootFileProvider两个文件提供程序。下面我们就看一下他们是如何被初始化的。

    internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
    {
        private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context)
        {
            if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal))
            {
                var options = new WebHostOptions(context.Configuration, Assembly.GetEntryAssembly()?.GetName().Name);
                var webHostBuilderContext = new WebHostBuilderContext
                {
                    Configuration = context.Configuration,
                    HostingEnvironment = new HostingEnvironment(),
                };
                
                // 重点在这里,看这个 Initialize 方法
                webHostBuilderContext.HostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options);
                context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext;
                context.Properties[typeof(WebHostOptions)] = options;
                return webHostBuilderContext;
            }
    
            var webHostContext = (WebHostBuilderContext)contextVal;
            webHostContext.Configuration = context.Configuration;
            return webHostContext;
        }
    }
    
    internal static class HostingEnvironmentExtensions
    {
        internal static void Initialize(this IWebHostEnvironment hostingEnvironment, string contentRootPath, WebHostOptions options)
        {
            hostingEnvironment.ApplicationName = options.ApplicationName;
            hostingEnvironment.ContentRootPath = contentRootPath;
            // 初始化 ContentRootFileProvider
            hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath);
    
            var webRoot = options.WebRoot;
            if (webRoot == null)
            {
                // 如果 /wwwroot 目录存在,则设置为Web根目录
                var wwwroot = Path.Combine(hostingEnvironment.ContentRootPath, "wwwroot");
                if (Directory.Exists(wwwroot))
                {
                    hostingEnvironment.WebRootPath = wwwroot;
                }
            }
            else
            {
                hostingEnvironment.WebRootPath = Path.Combine(hostingEnvironment.ContentRootPath, webRoot);
            }
    
            if (!string.IsNullOrEmpty(hostingEnvironment.WebRootPath))
            {
                hostingEnvironment.WebRootPath = Path.GetFullPath(hostingEnvironment.WebRootPath);
                if (!Directory.Exists(hostingEnvironment.WebRootPath))
                {
                    Directory.CreateDirectory(hostingEnvironment.WebRootPath);
                }
                
                // 初始化 WebRootFileProvider
                hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(hostingEnvironment.WebRootPath);
            }
            else
            {
                hostingEnvironment.WebRootFileProvider = new NullFileProvider();
            }
    
            hostingEnvironment.EnvironmentName =
                options.Environment ??
                hostingEnvironment.EnvironmentName;
        }
    }
    

    注意

    • 使用UseDirectoryBrowserUseStaticFiles提供文件浏览和访问时,URL 受大小写和基础文件系统字符的限制。例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。
    • 如果使用 IIS 托管应用,那么 IIS 自带的静态文件处理器是不工作的,均是使用 ASP.NET Core Module 进行处理的,包括静态文件处理。

    小结

    • 使用UseFileServer扩展方法提供文件浏览和访问,其集成了UseStaticFilesUseDirectoryBrowserUseDefaultFiles三个中间件的功能。
      • UseStaticFiles:注册StaticFilesMiddleware,提供文件访问
      • UseDirectoryBrowser:注册DirectoryBrowserMiddleware,提供文件目录浏览
      • UseDefaultFiles:注册DefaultFilesMiddleware,当Url未指定访问的文件名时,提供默认页。
    • 文件提供程序均实现了接口IFileProvider,常用的文件提供程序有以下三种:
      • PhysicalFileProvider:提供物理文件系统的访问
      • ManifestEmbeddedFileProvider:提供嵌入在程序集中的文件的访问
      • CompositeFileProvider:用于将多种文件提供程序进行集成。
    • 可通过IWebHostingEnvironment获取ContentRootFileProvider(默认目录为项目根目录)和WebRootFileProvider(默认目录为Web根目录)。
  • 相关阅读:
    linux内核中GNU C和标准C的区别
    linux内核中GNU C和标准C的区别
    Getting start with dbus in systemd (02)
    Getting start with dbus in systemd (01)
    Getting start with dbus in systemd (03)
    物理内存相关的三个数据结构
    数据类型对应字节数(32位,64位 int 占字节数)
    Linux kernel 内存
    共模电感的原理以及使用情况
    [原创]DC-DC输出端加电压会烧毁
  • 原文地址:https://www.cnblogs.com/xiaoxiaotank/p/15496538.html
Copyright © 2011-2022 走看看