【asp.net core 系列】5 布局页和静态资源

0. 前言

在之前的4篇的内容里,我们较为详细的介绍了路由以及控制器还有视图之间的关系。也就是说,系统如何从用户的HTTP请求解析到控制器里,然后在控制器里处理数据,并返回给视图,在视图中显示出来。这一篇我将为大家介绍基础的最后一部分,布局页和静态资源引入。

1. 布局页

在控制器和视图那一篇,我们了解到_ViewStart 里设置了一个Layout属性的值,这个值正是用来设置布局页的。所谓的布局页,就是视图的公用代码。在实际开发中,布局页通常存放我们为整个系统定义的页面框架,视图里写每个视图的页面。

回顾一下,默认的_ViewStart里的内容是:

@{
    Layout = "_Layout";
}

默认的布局页指定的是名为_Layout的布局页,在本系列第三篇中,我们得知这个视图应当在Shared文件夹下,那我们进去看一下这个视图有什么内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MvcWeb</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcWeb</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - MvcWeb - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>

这是默认的布局页内容,看着挺多的,但是除了一些html代码,里面还有一些关键的地方需要注意。

1.1 RenderSection

RenderSection 分部渲染,在页面中创建一个标记,表示这个页面块将在子视图(或者是路由的实际渲染视图)中添加内容。

来,我们看一下微软官方给的注释:

In layout pages, renders the content of the section named name.

意思就是在布局页中,渲染名称为name的分部内容。

新创建一个分布页,名称为_Layout1

<html>
    <head>
        <title>Render 测试</title>
    </head>
    <body>
        @RenderSection("SectionDemo")
    </body>
</html>

这个布局页里什么都没有,只有一个RenderSection。现在我们新建一个控制器:

using Microsoft.AspNetCore.Mvc;

namespace MvcWeb.Controllers
{
    public class RenderTestController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

创建对应的视图:

Views / RenderTest/Index.cshtml

先设置布局页为_Layout1

@{
    Layout = "_Layout1";
}

先试试启动应用,访问:

//localhost:5006/RenderTest/Index

正常情况下,你应该能看到这个页面:

image-20200603223436499

仔细看一下信息,意思是在 RenderTest/Index.cshtml 视图中没有找到 SectionDemo 的分部内容。

那么,如何在视图中设置分部内容呢?

@{
    Layout = "_Layout1";
}
@section SectionDemo{
    <h1>你好</h1>

}

使用 @section <Section 名称> 后面跟一对大括号,在大括号中间的内容就是这个section(分部)的内容。

重启应用,然后刷新页面,你能看到这样的页面:

image-20200603224339206

如果不做特殊要求的话,定义在布局页中的分部块,视图必须实现。当然,RenderSection还有一个参数,可以用来设置分部不是必须的:

public HtmlString RenderSection(string name, bool required);

1.2 RenderBody

先看下微软给的官方注释:

In a Razor layout page, renders the portion of a content page that is not within a named section.

简单讲,如果在布局页中设置了@RenderBody,那么在使用了这个布局页的视图里所有没被分部块包裹的代码都会渲染到布局页中声明了@RenderBody的地方。

修改_Layout1.cshtml:

<html>
    <head>
        <title>Render 测试</title>
    </head>
    <body>
        <h1>RenderBody 测试 -之前</h1>
        @RenderBody()
        <h1>RenderBody 测试 -之后</h1>
    </body>
</html>

修改RenderTest/Index.cshtml

@{
    Layout = "_Layout1";
}

RenderBody测试
<h1>我是视图的内容!</h1>

重启应用,刷新刚刚访问的页面:

image-20200603230213462

可以看出,RenderBody渲染的位置。

2. 静态资源引入

通常情况下,静态资源的引入与HTML引用js和css等资源是一致的,但是对于我们在编写系统时自己创建的脚本和样式表,asp.net core提供了不同的处理方式。那就是服务器端压缩功能。

asp.net core 3.0 的mvc 默认项目是不启动这个功能的,需要我们额外的开启支持。

2.1 开启支持

先引入 BuildBundleMinifier

cd MvcWeb # 切换目录到MvcWeb项目下
dotnet add package BuildBundleMinifier

创建 bundleconfig.json

[
    {
        "outputFileName": "wwwroot/css/site.min.css",
        "inputFiles": [
            "wwwroot/css/site.css"
        ]
    },
  	{
        "outputFileName": "wwwroot/js/site.min.js",
        "inputFiles": [
          "wwwroot/js/site.js"
        ],
        "minify": {
          "enabled": true,
          "renameLocals": true
        },
        "sourceMap": false
      }
]

每个节点允许设置项:

  • outputFileName 生成的捆绑压缩文件,通常路径携带wwwroot
  • inputFiles 数组,包含要压缩到此次输出文件的文件路径,会按照添加的顺序依次加入
  • minify 输出类型的缩小选项,可选。 默认是 enabled: true
  • sourceMap 表示是否为捆绑的文件生成源映射的标记
  • sourceMapRootPath 源映射文件的路径

2.2 使用

正常情况下在布局页中,把压缩后的文件路径引入即可。不过在开发中,通常按照以下方式引用:

<environment exclude="Development">
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
<environment include="Development">
    <link rel="stylesheet" href="~/css/site.css" />
</environment>

注: asp-append-version 表示在引用路径追加一个版本号,这是针对html静态资源缓存的问题的一个解决方案,这一步是由程序决定的。

environment表示环境,现在大家知道这个写法就行,在接下来的篇幅会讲。

3. 静态资源目录

我们知道到目前为止,我们的静态资源都是在wwwroot目录下。那么我们是否可以修改或者添加别的目录作为静态资源目录呢?

在Startup.cs文件内的Configure方法下有这样一行代码:

app.UseStaticFiles();

这行代码的意思就是启用静态文件,程序自动从 wwwroot寻找资源。那么,我们就可以从这个方法入手,设置我们自己的静态资源:

public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options);

我们找到了这个方法的另一个重载版本,里面有一个参数类:

public class StaticFileOptions : SharedOptionsBase
{
    public StaticFileOptions();
    public StaticFileOptions(SharedOptions sharedOptions);
    public IContentTypeProvider ContentTypeProvider { get; set; }
    public string DefaultContentType { get; set; }
    public HttpsCompressionMode HttpsCompression { get; set; }
    public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
    public bool ServeUnknownFileTypes { get; set; }
}

并没有发现我们想要的,先别慌,它还有个父类。我们再去它的父类里看看:

public abstract class SharedOptionsBase
{
    protected SharedOptionsBase(SharedOptions sharedOptions);
    public IFileProvider FileProvider { get; set; }
    public PathString RequestPath { get; set; }
    protected SharedOptions SharedOptions { get; }
}

这下就比较明了了,需要我们提供一个文件提供器,那么我们来找一个合适的IFileProvider实现类吧:

public class PhysicalFileProvider : IFileProvider, IDisposable

这个类可以满足我们的要求,它位于命名空间:

namespace Microsoft.Extensions.FileProviders

那么,添加一组我们自己的配置吧:

using Microsoft.Extensions.FileProviders;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 省略其他代码,仅添加以下代码
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
    });
}

在项目的根目录创建名为OtherStatic的文件夹,然后在里面创建个文件夹,例如: files,并在这个文件夹里随便添加一个文件。

然后启动应用访问:

//localhost:5006/files/<你添加的文件(包括后缀名)>

然后能在浏览器中看到这个文件被正确响应。

当然,这里存在一个问题,如果在 OtherStatic中的文件在wwwroot也有相同目录结构的文件存在,这样访问就会出现问题。这时候,可以为我们新加的这个配置设置一个请求前缀:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
    RequestPath = "/other"
});

重启程序,然后访问:

//localhost:5006/other/files/<你添加的文件(包括后缀名)>

然后就能看到刚才响应的文件,重新访问之前的路径,发现浏览器提示404。

4. 总结

在这一篇,我们讲解了布局页的内容,静态资源的压缩绑定以及添加一个新的静态资源目录。通过这几篇内容,让我们对asp.net core mvc有了一个基本的认知。下一篇,我们将重新创建一个项目,并结合之前的内容,以实战为背景,带领大家完成一个功能完备的web系统。

更多内容烦请关注我的博客《高先生小屋》

file