第五单元 特殊视图

发布时间 2023-12-06 21:03:56作者: 誉尚学教育

1. _Layout.cshtml 布局页

布局视图和我们在Asp.Net MVC一样,布局视图_Layout.cshtml使得所有视图保持一致的外观变得更加容易,因为我们只有一个要修改的布局视图文件,更改后将立即反映在整个应用程序的所有视图中。

  在 ASP.NET Core MVC 中,有一些视图文件,如布局的视图,ViewStart.cshtml 和ViewImports.cshtml 等其他.cshtml 文件的文件名以下划线开头,这些文件名中的前下划线表示这些文件不是直接面向浏览器。

  我们可以在单个应用程序中包含多个布局视图文件。比如一个布局视图文件服务为管理员用户,另外一个不同的布局视图文件服务于普通用户。

  我们一般将布局视图建在Views/Shared文件夹下,以_Layout.cshtml命名。


  • @RenderBody()

    是注入视图特定内容的位置。例如,如果使用此布局视图呈现 index.chtml 视图,则会在我们 调用@RenderBody()方法 的位置注入 index.cshtml 视图内容

  • IsSectionDefined("Scripts")

    判断子视图中是否定义了Scripts Section 模块, 假设有子视图中index.cshtml 定义如下:

    @{
        ViewData["Title"] = "Home Page";
    }
    
    <form method="get" action="/home/query">
        <label for="username">姓名:</label><input name="username" id="username"/>
        <label for="studentNo">学号:</label><input name="studentno" id="studentNo"/>
        <input type="submit" value="查询"/> 
        <a href="/home/download">下载文件</a>
    </form>
    
    <!--Scripts section-->
    @section Scripts{
        <script>
            $('form').submit(function (){
                $.get('/home/query',$(this).serialize(),function (data){
                    console.log(data);
                });
    
                return false;
            });
        </script>
    }

     

    此时子视图中已经定义了 @section Scripts ,那么_Layout.cshtml 中的 IsSectionDefined("Scripts") 结果为true

  • @RenderSection("Scripts", false);

    name: Scripts 表示 section 的名称

    required: false 表示在子视图中,Scripts section 并不是必要的

    它的功能与RenderBody() 类似,同样是将子视图中的 @section Scripts中的内容注入到_Layout.cshtml 中的 @RenderSection("Scripts", false) 位置

 

2. _ViewStart.cshtml 视图

一盘位于 ~/Views/_ViewStart.cshtml 位置 ,表示对Views 文件夹下的所有视图文件起作用。可以将所有子视图中公共的部分放在其中。

作用:所有的视图请求都会先执行_ViewStart.cshtml。

@{
    Layout = "_Layout"; // 如果子视图不指定布局视图,则默认为"_Layout.cshtml"
}

@if (ViewData["Layout"]!=null && ViewData["Layout"].Equals("Admin"))
{
    Layout = "_AdminLayout";  
}
else
{
    Layout = "_Layout";
}

 

_AdminLayout.cshtml 布局页代码:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>title</title>
</head>
<body>
<div>
    <h1>_AdminLayout.cshtml 视图布局页</h1>
    @RenderBody()
</div>
</body>
</html>

 

子视图代码:

public ViewResult Index()
{
    ViewData["Layout"] = "Admin";


    return View();
}
<div>
    <h1>大家好,欢迎来到任我行码农场,Asp.net Mvc</h1>
    
    @if (true)
    {
        <h1>hello</h1>
    }
    else
    {
        <p>world</p>
    }
    
    <h1>我的姓名:@Context.Session.GetString("user")</h1>
</div>

 

运行结果:

 

 

3. _ViewImport.cshtml 命名导入视图

如果我们在很多页面都使用同一个命名空间,同一个model的话,我们可以在Views/_ViewImports.cshtml文件中添加共用的命名空间,model。

@using StudentManagement.Models;
@using StudentManagement.ViewModels;

@*还支持以下指令*@
@*
    @addTagHelper
    @removeTagHelper
    @tagHelperPrefix
    @model
    @inherits
    @inject
*@

 

需要注意的是,ViewStart和ViewImports是支持分层的,除了将它放在 Views 文件夹中之外,我们还可以在 Views 文件夹的“Home”子文件夹中放置另一个_ViewImports,在文件 Home 的文件夹中的\_ViewImports将覆盖在 Shared 文件夹中的\_ViewImports文件指定的设置。

 

4. 标签助手

标签助手是服务端代码能够参与在 Razor 文件中创建和呈现HTML元素。例如,内置的 ImageTagHelper 可以将版本号追加到图像名称。无论何时更改图像,服务器都会为图像生成新的唯一版本,因此可以保证客户端获取当前图像(而不是过时的缓存图像)。内置的标签助手多用于常见任务,例如创建表单,链接和加载资源等。标签助手是在 C# 中定义的,它们基于元素名称,属性名称或父标签来定位HTML元素。例如,当应用 LabelTagHelper 特性时,内置的 LabelTagHelper 可以减少 Razor 视图中 HTML和 C# 之间的显示转换

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

上面的代码使用通配符语法(“ * ”)来指定程序集中的所有标签助手将可用于Views目录或子目录中的每个视图文件。@addTagHelper 之后的第一个参数指定要装载的标签助手,第二个参数 “Microsoft.AspNetCore.Mvc.TagHelpers” 指定包含标签助手的程序集。

1. 表单标签助手

Microsoft.AspNetCore.Mvc.TagHelpers 是内置的 ASP.NET Core 标签助手的程序集。

 

@model MvcDemo1.Student

@{
    ViewBag.Title = "title";
    Layout = "_Layout";
}

<h2>添加学生信息</h2>

<form asp-controller="Student" asp-action="Submit" method="post">
    <table class="table table-bordered" style="width: 500px;margin: auto">
        <tr>
            <td colspan="3">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            </td>
        </tr>
        <tr>
            <td>
                <label asp-for="Account"></label>
            </td>
            <td>
                <input asp-for="Account" class="form-control"/>
            </td>
            <td>
                <span asp-validation-for="Account" class="text-danger"></span>
            </td>
        </tr>
        <tr>
            <td>
                <label asp-for="Password"></label>
            </td>
            <td>
                <input asp-for="Password" class="form-control"/>
            </td>
            <td>
                <span asp-validation-for="Password" class="text-danger"></span>
            </td>
        </tr>
        <tr>
            <td>
                <label asp-for="ConfirmPassword"></label>
            </td>
            <td>
                <input asp-for="ConfirmPassword" class="form-control"/>
            </td>
            <td>
                <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
            </td>
        </tr>
        <tr>
            <td>
                <label asp-for="Hobby"></label>
            </td>
            <td colspan="2">
                <input type="checkbox" name="Hobby" value="编程"/> 编程
                <input type="checkbox" name="Hobby" value="学习"/> 学习
                <input type="checkbox" name="Hobby" value="爬山"/> 爬山
            </td>
           
        </tr>
        <tr>
            <td><label asp-for="Gender"></label></td>
            <td colspan="2">
                <input type="radio" name="Gender" value=""/><input type="radio" name="Gender" value=""/></td>
        </tr>
        <tr>
            <td>
                <label asp-for="Province"></label>
            </td>
            <td colspan="2">
                <select asp-for="Province" class="form-control" asp-items="ViewBag.ProvinceList"></select>
            </td>
        </tr>
        <tr>
            <td>
                <label asp-for="Birthday"></label>
            </td>
            <td >
                <input asp-for="Birthday" class="form-control" />
            </td>
            <td><span asp-validation-for="Birthday"></span></td>
        </tr>
        <tr>
            <td>
                <label asp-for="Description" ></label>
            </td>
            <td colspan="2">
                 <textarea asp-for="Description" class="form-control"></textarea>
            </td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" class="btn btn-primary" value="提交"/>
            </td>
        </tr>
    </table>
</form>

@section Scripts
{
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
}
 

public class Student
{
    [Display(Name = "账号")]
    [Required(ErrorMessage = "请输入账号")]
    public String? Account { get; set; }

    [Display(Name = "密码")]
    [DataType(DataType.Password)]
    [Required(ErrorMessage = "请输入密码")]
    public String? Password { get; set; }

    [Display(Name = "确认密码")]
    [DataType(DataType.Password)]
    [Compare("Password",ErrorMessage = "两次输入的密码不一致")]
    public String? ConfirmPassword { get; set; }

    [Display(Name = "爱好")]
    public String[]? Hobby { get; set; }

    [Display(Name = "性别")]
    public String? Gender { get; set; }

    [Display(Name = "祖籍")]
    [Required(ErrorMessage = "请选择祖籍")]
    [Range(1,int.MaxValue,ErrorMessage = "请选择祖籍")]
    public int Province { get; set; }

    [Display(Name = "生日")]
    [DataType(DataType.Date)]
    [Required(ErrorMessage = "请选择你的生日")]
    public DateTime Birthday { get; set; }

    [Display(Name = "简介")]
    public string? Description { get; set; }
}

public record Province(int Id, String Name);

public IActionResult Create()
{
    List<Province> provinces = new()
    {
        new(0, "请选择"),
        new(1, "江西"),
        new(2, "北京"),
        new(3, "台湾")
    };
    // 创建一个下拉框数据源
    ViewBag.ProvinceList = new SelectList(
        provinces, // 下拉框的数据源
        "Id",      // 选中值设置为Id 属性
        "Name"      // 显示项设置为Name属性
    );

    return View();
}

[HttpPost]
public IActionResult Submit(Student student)
{
    if (ModelState.IsValid)
    {
        var entity = Students.FirstOrDefault(p => p.Account!.Equals(student.Account));
        if (entity != null)
        {
            // 服务端用于逻辑处理产品的错误 key 需要为空
            ModelState.AddModelError("", "账号已经存在");
            return View("Create");
        }

        Students.Add(student);
        return Ok("添加成功");
    }

    return BadRequest("请求失败");
}

 

  • asp-validation-summary

    只能用于div 标签中,来至于 ValidationSummaryTagHelper,其中的ValidationSummary中包含了三个枚举值:

    • All : 显示所有的错误信息

    • ModelOnly : 只显示Model级别的错误信息(不包括所有的属性错误)

    • None: 不显示错误信息,一般用不上

  • asp-validation-for

    用于显示属性的错误信息,只能用于span标签中

  • SelectList

    专用于绑定下拉框数据源,也可以直接绑定一个枚举

    enum OrderState{
        WaitPay,
        WaitSend,
        WaitReceive,
        Finish,
        Cancel
    }
    <select asp-for="OrderState" asp-items="@Html.GetEnumSelectList<OrderState>()"></select>
     

     

2. LinkTagHelper

应用在link元素上,支持备用的样式文件。它具有以下属性:

  • href——指定样式资源的链接地址。

  • asp-href-include——指定所有需要被加载的样式文件路径,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。

  • asp-href-exclude——指定那些不需要被加载的样式文件路径,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。

  • asp-fallback-href——指定备用资源链接地址。

  • asp-fallback-href-include——指定所有需要被加载的备用样式文件路径,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。

  • asp-fallback-href-exclude——指定那些不需要被加载的备用样式文件路径,当有多个时,以逗号来分隔每一个;这里的路径是相对于应用程序中wwwroot的相对路径。

  • asp-fallback-test-class——用来检测加载失败的样式名。

  • asp-fallback-test-property——用来检测资源加载失败所用的测试属性。

  • asp-fallback-test-value——用来检测资源加载失败所用的测试值。

  • asp-file-version——bool值,用来指定是否需要将文件版本信息加入到url地址中。

例如,在下面例子中,当从网络上(http://ajax.aspnetcdn.com/ajax/bootstrap-touch-carousel/0.8.0/css/bootstrap-touch-carousel.css)加载样式文件失败时,加载本地相应的样式文件(~/lib/bootstrap-touch-carousel/css/bootstrap-touch-carousel.css)。通过检测样式类carousel-caption中display属性是否是none来判断网络上样式文件是否加载成功。

 <link rel="stylesheet" 
          href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.1/css/bootstrap.min.css"
          asp-fallback-href-include="~/lib/bootstrap/dist/css/bootstrap.css"
          asp-fallback-test-class="alert-warning"
          asp-fallback-test-property="color"
          asp-fallback-test-value="#664d03"
          />

 

 

3. EnvironmentTagHelper

应用在environment元素上,根据不同names的设置呈现不同的内容。它支持以下属性:

  • names——指定环境名,当有多个时候以逗号分隔。这里判断的依据是,读取IHostingEnvironment的EnvironmentName的值,与environment元素中的names匹配,当匹配上的时候就呈现出里面的内容,否则移除该environment元素。

在很多情况下,我们想在开发环境使用一套配置信息,在生产环境又是另外一套,这时候就需要使用条件判断语句了,不过在新版的MVC中,使用EnvironmentTagHelper提供的Environment元素标签就可以了,示例如下:

<environment names="Development">
    <link rel="stylesheet" href="~/lib2/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</environment>

<environment names="Staging,Production">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
          asp-fallback-href="~/lib2/bootstrap/dist/css/bootstrap.min.css"
          asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />

</environment>

 

在上述代码中,我们定于,如果是Development环境就使用本地的js文件,否则(Staging或Production环境)就使用网络上的css文件。asp-append-version="true"为静态文件提供唯一版本号。

 

4. ScriptTagHelper

应用在script元素上,和LinkTagHelper一样,它也具有fallback功能, 只不过这里判断的不是class样式,而是用来判断默认的js文件是否加载成功。

它支持以下属性:

src指定要加载的js源地址。

asp-src-include指定要加载的js文件,当有多个文件时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。

asp-src-exclude指定不需要加载的js文件,当有多个文件时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。

asp-fallback-src指定备用的js源地址。

asp-fallback-src-include指定需要加载的备用js文件,当有多个文件格式时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。

asp-fallback-src-exclude指定不需要加载的备用js文件,当有多个文件时以逗号分隔。这里文件路径是相对于程序webroot的相对路径。

asp-file-version——bool值,用来指定是否需要将文件版本信息加入到url地址中。

<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.2.min.js"
    asp-fallback-src="~/lib/jquery/jquery.min.js">
</script>

 

此外,还可以根据需要自定义TagHelper。

 

5. 局部视图

<partial name="局部视图名" for="局部视图数据源"/>

 

 

 

5. 自定义标签助手

  1. 所有的标签助手都继承于 TagHelper 类,类名都以TagHelper命名结尾,例如:TableListTagHelper

  2. 重写Process 方法

    • using Microsoft.AspNetCore.Razor.TagHelpers;
      
      namespace MvcDemo.Util;
      
      [HtmlTargetElement("email")]
      public class EmailTagHelper:TagHelper
      {
          // 想要生成的目标html <a href="mailto:1310734881@qq.com">1310734881@qq.com</a>
          
          /// <summary>
          /// 邮箱地址属性
          /// </summary>
          public string? EmailTo { get; set; }
          public override void Process(TagHelperContext context, TagHelperOutput output)
          {
              output.TagName = "a"; // 将email 标签替换成a标签
              output.Attributes.SetAttribute("href",$"mailto:{EmailTo}"); // 设置a 标签属性
              output.Content.SetContent(EmailTo); // 设置a 标签中间内容
          }
      }

       

      TagHelperContext: 包含了当前执行的TagName 标签元素相关的信息

    • TagHelperOutput:包含了即将生成的html相关信息。

    • [HtmlTargetElement("email")] 表示目标标签名叫"Email"

    • [HtmlTargetElement(Attributes = "email")] 表示此标签只能作为一个名叫email的属性标签

  3. 在_ViewImport.cshtml 中将 TableListTagHelper 类的程序集通过@addTagHelper 指令将自定义的标签助手导入进来。当前我的程序集名称是:MvcDemo, 我的tagHelper类是在MvcDemo.Utils 命名空间里。

    @addTagHelper *,MvcDemo // 导入自定义的标签助手的程序集

     

  4. 在Razor视图中使用

    • 假设你的自定义标签助手名叫:EmailTagHelper,那么视图中写成如下:

      <email></email>

       

      本例中,EmailTagHelper中有个EmailTo 属性,那么我们应该写成:

      <email email-to="xxx@qq.com"></email>

       

      注意属性名单词与单词间用“-”隔开,单词全部小写。

    • 假设你的定义标签助手名叫:TableListTagHelper,那么视图中写成如下:

      <table-list></table-list>

       

       

6. TagHelper 前缀

随着现在前端框架越来越丰富多彩,如果自定义tagHelper 太多,或许你的tagHelper 会与某些前端控件冲突了。

此时我们可以利用TagHelper 前缀来帮忙解决这个问题(只针对当前页面有效)

@tagHelperPrefix "renwoxing:" // 只针对当前页面有效

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>


<renwoxing:email email-to="131@qq.com"></renwoxing:email>
<a> 没有用到自定义标签,不需要加前缀</a>

<renwoxing:a asp-action="Privacy">Privacy</renwoxing:a>

 

我们通过会在前缀尾部加一个冒号,以免与其他标签连在一起了。

一旦加了标签前缀,则所有用到了标签助手的地方,都需要加上前缀。

 

5. ViewComponent 视图组件

视图组件与分部视图类似,但它们的功能更加强大。 视图组件不使用模型绑定,具体取决于调用视图组件时传递的数据。 本文是使用控制器和视图编写的,但视图组件使用 Razor Pages

视图组件:

  • 呈现一个区块而不是整个响应。

  • 包括控制器和视图间发现的相同关注点分离和可测试性优势。

  • 可以有参数和业务逻辑。

  • 通常从布局页调用。

视图组件适用于任何对于部分视图来说过于复杂的可重用呈现逻辑,例如:

  • 动态导航菜单

  • 标记云,在其中查询数据库

  • 登录面板

  • 购物车

  • 最近发布的文章

  • 博客上的边栏内容

  • 将在每个页面上呈现的登录面板,并显示注销或登录的链接,具体取决于用户的登录状态

视图组件由两个部分组成:

  • 类,通常派生自 ViewComponent

  • 它返回的结果通常是视图。

 

视图组件类

若要防止具有不区分大小写 ViewComponent 的后缀的类被视为视图组件,请使用 [[NonViewComponent]该属性修饰类:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

 

视图组件方法

视图组件在以下项中定义其逻辑:

参数直接来自视图组件的调用,而不是来自模型绑定。 视图组件从不直接处理请求。 通常,视图组件通过调用 View 方法来初始化模型并将其传递到视图。 总之,视图组件方法:

  • 定义返回 TaskInvokeAsync 方法,或是返回 IViewComponentResult 的同步 Invoke 方法。

  • 通常通过调用 ViewComponent.View 方法初始化模型并将其传递给视图。

  • 参数来自调用方法,而不是 HTTP。 没有模型绑定。

  • 无法直接作为 HTTP 终结点访问。 它们通常在视图中调用。 视图组件从不处理请求。

  • 在签名上重载,而不是当前 HTTP 请求的任何详细信息。

// Student 是视图组件名称
public class StudentViewComponent:ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        List<Student> list = new()
        {
            new() {Id = 1, Name = "张三"},
            new() {Id = 2, Name = "李四"}
        };
        // 如果不写Index 参数,则默认视图名称为Default.cshtml
        return View("Index",list); // 返回 一个ViewComponentResult
    }
}

 

 

视图搜索路径

运行时在以下路径中搜索视图:

  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}

  • /Views/Shared/Components/{View Component Name}/{View Name}

  • /Pages/Shared/Components/{View Component Name}/{View Name}

搜索路径适用于使用控制器 + 视图和 Razor Pages 的项目。

视图组件的默认视图名称是 Default,这意味着视图文件通常命名 Default.cshtml。 创建视图组件结果或调用 View 方法时,可以指定其他视图名称。

建议命名视图文件 Default.cshtml ,并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。 Student此示例Views/Shared/Components/Student/Default.cshtml中使用的视图组件用于视图组件视图。

调用视图组件

要使用视图组件,请在视图中调用以下内容:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})
@await Component.InvokeAsync("Student")
 

 

ViewComponent 传参

public class StudentViewComponent:ViewComponent
{
    public IViewComponentResult Invoke(String? studentName=null)
    {
        List<Student> list = new()
        {
            new() {Id = 1, Name = "张三"},
            new() {Id = 2, Name = "李四"}
        };
        if (!string.IsNullOrWhiteSpace(studentName))
        {
            list = list.Where(p => p.Name.Contains(studentName)).ToList();
        }

        return View("Index",list); // 返回 一个ViewComponentResult
    }
}
@await Component.InvokeAsync("Student",new { StudentName="张三"})
 

 

标签助手调用:

标记帮助程序采用 Pascal 大小写格式的类和方法参数将转换为各自相应的短横线格式。 要调用视图组件的标记帮助程序使用 <vc:> 元素。 按如下方式指定视图组件:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

 

若要将视图组件用作标记帮助程序,请使用 @addTagHelper 指令注册包含视图组件的程序集。 如果视图组件位于名为 MyWebApp 的程序集中,请将以下指令添加到 _ViewImports.cshtml 文件中:

@addTagHelper *, MyWebApp

 

无参:

<vc:student></vc:student>

 

有参:

<vc:student student-name="张三"></vc:student>

 

 

6. 区域路由与视图

区域是一项 ASP.NET 功能,用于将相关功能整理到一个组中,单独作为:

  • 用于路由的命名空间。

  • 用于视图和 Razor Pages 的文件夹结构。

使用区域会通过为 controlleraction 或 Razor 页面 page 添加另一个路由参数 area,创建用于路由目的的层次结构。

区域提供了一种将 ASP.NET Core Web 应用划分为更小的功能组的方法,每个功能组都有自己的一组 Razor Pages、控制器、视图和模型。 区域实际上是应用内的结构。 在 ASP.NET Core Web 项目中,Pages、模型、控制器和视图等逻辑组件保存在不同的文件夹中。 ASP.NET Core 运行时使用命名约定来创建这些组件之间的关系。 对于大型应用,将应用分区为独立的高级功能区域可能更有利。 例如,具有多个业务单位(如结账、计费、搜索等)的电子商务应用。 每个单位都有自己的区域,用于包含视图、控制器、Razor Pages 和模型。

如果发生以下情况,请考虑在项目中使用区域:

  • 应用由可以进行逻辑分隔的多个高级功能组件组成。

  • 想对应用进行分区,以便可以独立处理每个功能区域。

如果使用的是 Razor Pages,请参阅本文档中的“页面区域Razor”。

带视图的控制器区域

使用区域、控制器和视图的典型 ASP.NET Core Web 应用包含以下内容:

  • 区域文件夹结构

  • 使用 [Area("AreaName")]属性将自己与区域关联的控制器:

    [Area("Products")]
    public class ManageController : Controller
    {

     

  • 添加到Program.cs以下区域的路线

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllersWithViews();
    
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "MyArea",
        pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    
    app.Run();

     

区域文件夹结构

请考虑具有两个逻辑组(产品和服务)的应用。 使用区域,文件夹结构类似于以下内容:

  • 项目名称

    • Areas

      • 产品

        • Controllers

          • HomeController.cs

          • ManageController.cs

        • 视图

          • Home

            • Index.cshtml

          • 管理

            • Index.cshtml

            • About.cshtml

      • 服务

        • Controllers

          • HomeController.cs

        • 视图

          • Home

            • Index.cshtml

虽然前面的布局是使用区域时的典型布局,但只需要视图文件即可使用此文件夹结构。 视图发现按以下顺序搜索匹配的区域视图文件:

text复制

/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml
/Pages/Shared/<Action-Name>.cshtml

 

 

将控制器与区域关联

区域控制器使用 [Area\] 属性进行指定:

C#复制

using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;

namespace MVCareas.Areas.Products.Controllers;

[Area("Products")]
public class ManageController : Controller
{
    public IActionResult Index()
    {
        ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
        return View();
    }

    public IActionResult About()
    {
        ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
        return View();
    }
}

 

添加区域路由

区域路由通常使用传统路由,而不使用特性路由。 传统路由依赖于顺序。 一般情况下,具有区域的路由应放在路由表中靠前的位置,因为它们比没有区域的路由更特定。

如果所有区域的 url 空间一致,则 {area:...} 可用作路由模板中的令牌:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "MyArea",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

 

在前面的代码中,exists 应用了路由必须与区域匹配的约束。 将 {area:...}MapControllerRoute 结合使用:

  • 是将路由添加到区域的最简单的机制。

  • 将所有控制器与 [Area("Area name")] 特性相匹配。

以下代码使用 MapAreaControllerRoute 创建两个命名区域路由:

C#复制

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute(
            name: "MyAreaProducts",
            areaName: "Products",
            pattern: "Products/{controller=Home}/{action=Index}/{id?}");

app.MapAreaControllerRoute(
    name: "MyAreaServices",
    areaName: "Services",
    pattern: "Services/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

 

有关详细信息,请参阅区域路由

MVC 区域的链接生成

示例下载中的以下代码显示指定区域的链接生成:

CSHTML复制

<li>Anchor Tag Helper links</li>
<ul>
    <li>
        <a asp-area="Products" asp-controller="Home" asp-action="About">
            Products/Home/About
        </a>
    </li>
    <li>
        <a asp-area="Services" asp-controller="Home" asp-action="About">
            Services About
        </a>
    </li>
    <li>
        <a asp-area="" asp-controller="Home" asp-action="About">
            /Home/About
        </a>
    </li>
</ul>
<li>Html.ActionLink generated links</li>
<ul>
    <li>
        @Html.ActionLink("Product/Manage/About", "About", "Manage",
                                                new { area = "Products" })
    </li>
</ul>
<li>Url.Action generated links</li>
<ul>
    <li>
        <a href='@Url.Action("About", "Manage", new { area = "Products" })'>
            Products/Manage/About
        </a>
    </li>
</ul>

 

示例下载包括一个分部视图,其中包含:

  • 上面代码中所示的链接。

  • 与上述链接类似的链接,只是未指定 area

布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域和控制器中的页面引用时才有效。

如果未指定区域或控制器,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值将生成带有未指定区域的标记的错误链接。

有关详细信息,请参阅路由到控制器操作

使用 _ViewStart.cshtml 文件的共享区域布局

若要共享整个应用的常用布局,请将 _ViewStart.cshtml 保留在应用程序根文件夹中。 有关详细信息,请参阅 ASP.NET Core 中的布局

 

应用程序根文件夹

应用程序根文件夹是包含Program.cs使用 ASP.NET Core模板创建的 Web 应用中的文件的文件夹。

_ViewImports.cshtml

适用于 MVC 的 /Views/ViewImports.cshtml 和适用于 Razor Pages 的 /Pages/ViewImports.cshtml 不会导入到区域中的视图。 使用以下任一方法将视图导入到所有视图中:

  • 将 _ViewImports.cshtml 添加到应用程序根文件夹。 应用程序根文件夹中的 _ViewImports.cshtml 将应用到应用中的所有视图。

  • 将 _ViewImports.cshtml 文件复制到区域下相应的视图文件夹中。 例如, Razor 使用单个用户帐户创建的 Pages 应用具有以下文件夹中

     

    的 _ViewImports.cshtml

     

    文件:

    • /Areas/Identity/Pages/_ViewImports.cshtml

    • /Pages/_ViewImports.cshtml

_ViewImports.cshtml 文件通常包含标记帮助程序导入 @using@inject 语句。 有关详细信息,请参阅导入共享指令

 

更改存储视图的默认区域文件夹

以下代码将默认的区域文件夹从 "Areas" 改为"MyAreas"

C#复制

using Microsoft.AspNetCore.Mvc.Razor;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<RazorViewEngineOptions>(options =>
{
    options.AreaViewLocationFormats.Clear();
    options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");
    options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
    options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "MyArea",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
 

 

包含 Razor Pages 的区域

包含 Razor Pages 的区域要求在应用根目录中有一个 Areas//Pages 文件夹。 以下文件夹结构用于示例应用

  • 项目名称

    • Areas

      • 产品

        • 页数

          • _ViewImports

          • 关于

          • 索引

      • 服务

        • 页数

          • 管理

            • 关于

            • 索引

Razor Pages 和区域的链接生成

示例下载中的以下代码显示指定区域(例如 asp-area="Products")的链接生成:

CSHTML复制

<li>Anchor Tag Helper links</li>
<ul>
    <li>
        <a asp-area="Products" asp-page="/About">
            Products/About
        </a>
    </li>
    <li>
        <a asp-area="Services" asp-page="/Manage/About">
            Services/Manage/About
        </a>
    </li>
    <li>
        <a asp-area="" asp-page="/About">
            /About
        </a>
    </li>
</ul>
<li>Url.Page generated links</li>
<ul>
    <li>
        <a href='@Url.Page("/Manage/About", new { area = "Services" })'>
            Services/Manage/About
        </a>
    </li>
    <li>
        <a href='@Url.Page("/About", new { area = "Products" })'>
            Products/About
        </a>
    </li>
</ul>

 

示例下载包含部分视图,该视图包含以前的链接和相同的链接(未指定区域)。 在布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域中的页引用时才有效。

如果未指定区域,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值会生成错误的链接。 例如,考虑从下面的代码生成的链接:

CSHTML复制

<li>
    <a asp-page="/Manage/About">
        Services/Manage/About
    </a>
</li>
<li>
    <a asp-page="/About">
        /About
    </a>
</li>

 

对于上述代码:

  • 只有当最后一个请求是针对 Services 区域的页时,从 ` 生成的链接才是正确的。 例如,/Services/Manage//Services/Manage/Index/Services/Manage/About`。

  • 只有当最后一个请求是针对 /Home 中的页时,从 `` 生成的链接才是正确的。

  • 代码摘自示例下载

使用 _ViewImports 文件导入命名空间和标记帮助程序

可以向每个区域 Pages 文件夹添加一个 _ViewImports.cshtml 文件,以便将命名空间和标记帮助程序导入到该文件夹中的每个 Razor 页面中。

请考虑使用示例代码的“服务”区域,它不包含 _ViewImports.cshtml 文件。 以下标记显示 /Services/Manage/About Razor 页面:

CSHTML复制

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model RPareas.Areas.Services.Pages.Manage.AboutModel
@{
    ViewData["Title"] = "Srv Mng About";
}

<div>
  ViewData["routeInfo"]:  @ViewData["routeInfo"]
</div>

<a asp-area="Products" asp-page="/Index">
    Products/Index
</a>

 

在前面的标记中:

  • 必须使用完全限定的类名来指定模型 (@model RPareas.Areas.Services.Pages.Manage.AboutModel)。

  • 标记帮助程序@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 启动

在示例下载中,“产品”区域包含下列 _ViewImports.cshtml 文件:

CSHTML复制

@namespace RPareas.Areas.Products.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

 

以下标记显示 /Products/About Razor 页面:

CSHTML复制

@page
@model AboutModel
@{
    ViewData["Title"] = "Prod About";
}

 

在前面的文件中,命名空间和 @addTagHelper 指令由 Areas/Products/Pages/_ViewImports.cshtml 文件导入文件。

有关详细信息,请参阅管理标记帮助程序范围导入共享指令

Razor Pages 区域的共享布局

要共享整个应用的常用布局,请将 _ViewStart.cshtml 移动到应用程序根文件夹。

 

视频配套链接:【Asp.Net Core Mvc 进阶】.Net 6 系列 详细讲解,Mvc进阶讲解(已完结)_哔哩哔哩_bilibili