使用MVC相关技术
- Controller
- Tag Helper
- Settings
- View Component
- Razor Page
建立Controller
添加实体类
项目右键,新建Models文件夹,在Models下添加Department类。
public class Department { public int Id { get; set; } public string Name { get; set; } public string Location { get; set; } public int EmployeeCount { get; set; } }
|
在Models下添加Employee类。
public class Employee { public int Id { get; set; } public int DepartmentId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public Gender Gender { get; set; } public bool Fired { get; set; } }
public enum Gender { 女 = 0, 男 = 1 }
|
在Models下添加CompanySummary类。
public class CompanySummary { public int EmployeeCount { get; set; } public int AverageDepartmentEmployeeCount { get; set; } }
|
添加服务
在Serivces下,添加IDepartmentService接口。
public interface IDepartmentService { Task<IEnumerable<Department>> GetAll(); Task<Department> GetById(int id); Task<CompanySummary> GetCompanySummary(); Task Add(Department department); }
|
在Services下,添加DepartmentService实现类。
public class DepartmentService : IDepartmentService { private readonly List<Department> _departments = new List<Department>();
public DepartmentService() { _departments.Add(new Department { Id = 1, Name = "HR", EmployeeCount = 16, Location = "Beijing" }); _departments.Add(new Department { Id = 2, Name = "G&D", EmployeeCount = 52, Location = "Shanghai" }); _departments.Add(new Department { Id = 3, Name = "Sales", EmployeeCount = 200, Location = "China" }); }
public Task<IEnumerable<Department>> GetAll() { return Task.Run(function: () => _departments.AsEnumerable()); }
public Task<Department> GetById(int id) { return Task.Run(function: () => _departments.FirstOrDefault(x => x.Id == id)); }
public Task<CompanySummary> GetCompanySummary() { return Task.Run(function: () => { return new CompanySummary { EmployeeCount = _departments.Sum(x => x.EmployeeCount), AverageDepartmentEmployeeCount = (int)_departments.Average(x => x.EmployeeCount) }; }); }
public Task Add(Department department) { department.Id = _departments.Max(x => x.Id) + 1; _departments.Add(department); return Task.CompletedTask; } }
|
在Services下,添加IEmployeeService接口。
public interface IEmployeeService { Task Add(Employee employee); Task<IEnumerable<Employee>> GetByDepartmentId(int departmentId); Task<Employee> Fire(int id); }
|
在Services下,添加EmployeeService实现类。
public class EmployeeService : IEmployeeService { private readonly List<Employee> _employees = new List<Employee>();
public EmployeeService() { _employees.Add(new Employee { Id = 1, DepartmentId = 1, FirstName = "Nick", LastName = "Carter", Gender = Gender.男 }); _employees.Add(new Employee { Id = 2, DepartmentId = 1, FirstName = "Michael", LastName = "Jackson", Gender = Gender.男 }); _employees.Add(new Employee { Id = 3, DepartmentId = 1, FirstName = "Mariah", LastName = "Carey", Gender = Gender.女 }); _employees.Add(new Employee { Id = 4, DepartmentId = 2, FirstName = "Axl", LastName = "Rose", Gender = Gender.男 }); _employees.Add(new Employee { Id = 5, DepartmentId = 2, FirstName = "Kate", LastName = "Winslet", Gender = Gender.女 }); _employees.Add(new Employee { Id = 6, DepartmentId = 3, FirstName = "Rob", LastName = "Thomas", Gender = Gender.男 }); _employees.Add(new Employee { Id = 7, DepartmentId = 3, FirstName = "Avril", LastName = "Lavigne", Gender = Gender.女 }); _employees.Add(new Employee { Id = 8, DepartmentId = 3, FirstName = "Katy", LastName = "Perry", Gender = Gender.女 }); _employees.Add(new Employee { Id = 9, DepartmentId = 3, FirstName = "Michelle", LastName = "Monaghan", Gender = Gender.女 }); }
public Task Add(Employee employee) { employee.Id = _employees.Max(x => x.Id) + 1; _employees.Add(employee); return Task.CompletedTask; }
public Task<IEnumerable<Employee>> GetByDepartmentId(int departmentId) { return Task.Run(function: () => _employees.Where(x => x.DepartmentId == departmentId)); }
public Task<Employee> Fire(int id) { return Task.Run(function: () => { var employee = _employees.FirstOrDefault(e => e.Id == id); if (employee != null) { employee.Fired = true; return employee; } return null; }); } }
|
注册服务
在Startup类中的ConfigureServices方法中,注册服务。
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews();
services.AddSingleton<IDepartmentService, DepartmentService>(); services.AddSingleton<IEmployeeService, EmployeeService>(); }
|
添加Controller
在Controllers下添加DepartmentController。
public class DepartmentController : Controller { private readonly IDepartmentService _departmentService;
public DepartmentController(IDepartmentService departmentService) { _departmentService = departmentService; } public async Task<IActionResult> Index() { ViewBag.Title = "Department Index"; var departments = await _departmentService.GetAll(); return View(departments); }
public IActionResult Add() { ViewBag.Title = "Add Department"; return View(new Department()); }
[HttpPost] public async Task<IActionResult> Add(Department model) { if (ModelState.IsValid) { await _departmentService.Add(model); }
return RedirectToAction(nameof(Index)); } }
|
注意:
- 对于POST请求,必须在方法前写HttpPost特性,不写默认为HttpGet。
- 使用nameof的优点是,当符号重命名时,所有引用之处都会随之重新命名。
在Controllers下添加EmployeeController。
public class EmployeeController : Controller { private readonly IDepartmentService _departmentService; private readonly IEmployeeService _employeeService;
public EmployeeController(IDepartmentService departmentService, IEmployeeService employeeService) { _departmentService = departmentService; _employeeService = employeeService; }
public async Task<IActionResult> Index(int departmentId) { var department = await _departmentService.GetById(departmentId);
ViewBag.Title = $"Employees of {department.Name}"; ViewBag.DepartmentId = departmentId;
var employees = await _employeeService.GetByDepartmentId(departmentId);
return View(employees); }
public IActionResult Add(int departmentId) { ViewBag.Title = "Add Employee"; return View(new Employee { DepartmentId = departmentId }); }
[HttpPost] public async Task<IActionResult> Add(Employee model) { if (ModelState.IsValid) { await _employeeService.Add(model); }
return RedirectToAction(nameof(Index), routeValues: new { departmentId = model.DepartmentId }); }
public async Task<IActionResult> Fire(int employeeId) { var employee = await _employeeService.Fire(employeeId);
return RedirectToAction(nameof(Index), routeValues: new { departmentId = employee.DepartmentId }); } }
|
注意:routeValues的作用是,RedirectToAction方法重定向到Index需要一个参数,通过匿名类型传递参数。
建立视图
项目右键,新建文件夹Views,在Views下新建文件夹Shared,用于存放所有页面的模板。
Shared右键,新建Razor 布局,命名为默认的_layout.cshtml。
_layout.cshtml的代码如下,其中asp-append-version属性就是使用了Tag Helper:
<!DOCTYPE html>
<html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div class="container"> <div class="row"> <div class="col-md-2"> <img asp-append-version="true" alt="Logo" src="~/images/ae86.jpg" style="height: 60px;" /> </div> <div class="col-md-10"> <span class="h2">@ViewBag.Title</span> </div> </div> <div class="row"> <div class="col-md-12"> @RenderBody() </div> </div> </div> </body> </html>
|
然后,设置_layout作为其他所有页面的模板。
Views右键,新建Razor 视图开始,命名为默认的_ViewStart.cshtml。
_ViewStart.cshtml中设置了,所有的页面的母页面是_Layout。
使用Tag Helper
Views右键,新建Razor 视图导入,命名为_ViewImports.cshtml。
输入以下Razor代码,可以为全局的View添加Tag Helper或自定义的Tag Helper。
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
|
使用Tag Helper区分当前环境
可以使用environment标签可以区分当前环境是开发环境还是其他环境,从而加载不同的静态资源。
<!DOCTYPE html>
<html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title>
<environment include="Development"> <link rel="stylesheet" asp-href-include="css/*" asp-href-exclude="css/all.min.css" /> </environment>
<environment exclude="Development"> <link rel="stylesheet" asp-href-include="all.min.css" /> </environment> </head> <body> <div class="container"> <div class="row"> <div class="col-md-2"> <img asp-append-version="true" alt="Logo" src="~/images/ae86.jpg" style="height: 60px;" /> </div> <div class="col-md-10"> <span class="h2">@ViewBag.Title</span> </div> </div> <div class="row"> <div class="col-md-12"> @RenderBody() </div> </div> </div> </body> </html>
|
注意:img标签中的asp-append-version属性的作用是,在图片地址后添加Hash值,这样可以防止图片缓存。
建立View
Views右键,新建Department文件夹,在Department下新建Razor 视图,命名为Index.cshtml。
这时候启动项目Three,可以看到页面的样式正常。并且复制图片地址后,也能看到Hash值。
由于开发环境,在Tag Helper的environment标签作用下,all.min.css也没有加载出来。
建立列表
在Views文件夹下的Department下的Index.cshtml。
由于DepartmentController中的Index方法,返回给View的参数是IEnumerable<Department>,因此使用@model IEnumerable<Department>。
@using Three.Models; @model IEnumerable<Department>
<div class="row"> <div class="col-md-10 offset-md-2"> <table class="table"> <tr> <th>Name</th> <th>Location</th> <th>EmployeeCount</th> <th>操作</th> </tr> @Html.DisplayForModel() </table> </div> </div> <div class="row"> <div class="col-md-4 offset-md-2"> <a asp-action="Add">Add</a> </div> </div>
|
注意:
- @Html.DisplayForModel(),这里使用了Html Helper,作用是展示数据。
- asp-action指定了Action为Add,但是它所在的a标签不需要指定Controller,因为默认就是在DepartmentController。
建立展示模板
Department模板
Department文件夹下,新建DisplayTemplates文件夹,在DisplayTemplates下新建Razor 视图,命名为Department.cshtml。编辑如下代码。
@model Three.Models.Department
<tr> <td>@Model.Name</td> <td>@Model.Location</td> <td>@Model.EmployeeCount</td>
<td> <a asp-controller="Employee" asp-action="Index" asp-route-departmentId="@Model.Id"> Employees </a> </td> </tr>
|
注意:该展示模板是一行数据。asp-controller的指定了Controller,asp-action指定了Action,asp-route-departmentId的作用是给指定的Action传递参数,departmentId和Action的参数名保持一致。
运行项目,可以发现列表正常显示了。
Employee模板
同理,又在Views文件夹下,新建Employee文件夹,然后新建Index.cshtml,在Employee文件夹下新建DisplayTemplate文件夹,再新建Employee.cshtml。
编辑Views/Employee/Index.cshtml代码如下。
@using Three.Models @model IEnumerable<Employee>
<div class="row"> <div class="col-md-10 offset-md-2"> <table class="table"> <tr> <th>FirstName</th> <th>LastName</th> <th>Gender</th> <th>Is Fired</th> <th>操作</th> </tr> @Html.DisplayForModel() </table> </div> </div> <div class="row"> <div class="col-md-4 offset-md-2"> <a asp-action="Add">Add</a> </div> </div>
|
编辑Views/Employee/DisplayTemplates/Employee.cshtml代码如下。
@model Three.Models.Employee
<tr> <td>@Model.FirstName</td> <td>@Model.LastName</td> <td>@Model.Gender</td> <td>@(Model.Fired ? "是" : "")</td>
<td> @if (!Model.Fired) { <a asp-action="Fire" asp-route-employeeId="@Model.Id"> Fire </a> } </td> </tr>
|
运行项目,可以进入Employee页面,并解雇Employee了。
新建添加页面
部门添加页面
在Views/Department下新建Add.cshtml,代码如下。
@using Three.Models @model Department
<form asp-action="Add"> <div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="Name"></label> </div> <div class="col-md-6"> <input class="form-control" asp-for="Name" /> </div> </div> <div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="Location"></label> </div> <div class="col-md-6"> <input class="form-control" asp-for="Location" /> </div> </div> <div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="EmployeeCount"></label> </div> <div class="col-md-6"> <input class="form-control" asp-for="EmployeeCount" /> </div> </div> <div class="row"> <div class="col-md-2 offset-md-2"> <button type="submit" class="btn btn-primary">Add</button> </div> </div> </form>
|
注意:asp-for的作用是指定属性,也可以在Model类中使用Display特性,来改变Label显示的内容。
[Display(Name = "部门名称")] public string Name { get; set; }
|
完成部门添加页面。
员工添加页面
由于EmployeeController中的Add方法,需要一个参数departmentId。因此在Views/Employee/Index的Add按钮中,通过ViewBag添加参数。
在Views/Employee下新建Add.cshtml,代码如下。
@using Three.Models @model Employee
<form asp-action="Add"> <input type="hidden" asp-for="DepartmentId" />
<div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="FirstName"></label> </div> <div class="col-md-6"> <input class="form-control" asp-for="FirstName" /> </div> </div> <div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="LastName"></label> </div> <div class="col-md-6"> <input class="form-control" asp-for="LastName" /> </div> </div> <div class="row form-group"> <div class="col-md-2 offset-md-2"> <label asp-for="Gender"></label> </div> <div class="col-md-6"> <select class="form-control" asp-for="Gender" asp-items="Html.GetEnumSelectList<Gender>()"> </select> </div> </div> <div class="row"> <button type="submit" class="btn btn-primary">Add</button> </div> </form>
|
注意:Gender下拉框使用asp-items,然后通过Html Helper获取枚举值显示在列表中。
完成员工添加页面。
配置信息
ASP.NET Core的配置信息
- Key-Value键值对。
- 可以读取自,内存里、JSON、XML、INI等文件,或者系统环境变量。
- 配置信息与配置系统是解耦的。
- 可以依赖注入
ASP.NET Core的配置信息源
按先后顺序从以下位置依次读取,如果存在重复的配置,则以最后读取的位置为准。
- appsettings.json
- appsettings.{Environment}.json
- Secret Manager
- 环境变量
- 命令行参数
appsettings.json文件
创建ASP.NET Core项目之后,都会有一个appsettings.json文件。
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
|
- Logging:日志的配置。
- AllowedHosts:允许在哪个Host上运行应用。
注意:在开发环境中,appsettings.Development.json会覆盖appsettings.json中的配置。
使用配置
添加自定义配置
在appsettings.json中添加自定义配置BoldDepartmentEmployeeCountThreshold,可以使用对象与属性的形式。
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Three": { "BoldDepartmentEmployeeCountThreshold": 30 }, "AllowedHosts": "*", "me": 25 }
|
构造函数注入使用配置
在Startup类中,使用构造函数注入,通过索引的方式读取配置。
public class Startup { private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration) { _configuration = configuration; var three = _configuration["Three:BoldDepartmentEmployeeCountThreshold"]; var me = _configuration["me"]; } }
|
把配置映射到类中
由于C#是强类型,不推荐使用对象冒号属性的方式读取配置。因此把对象配置映射到类中。
项目右键,新建ThreeOptions类,配置的属性和类的属性一一对应。
public class ThreeOptions { public int BoldDepartmentEmployeeCountThreshold { get; set; } }
|
在Startup类中的ConfigureServices方法映射配置文件中Three的配置。
public void ConfigureServices(IServiceCollection services) { services.Configure<ThreeOptions>(_configuration.GetSection(key: "Three")); }
|
在Controller中使用配置
在DepartmentController中,使用构造函数注入,然后就可以在整个Controller中使用配置。
private readonly IDepartmentService _departmentService; private readonly IOptions<ThreeOptions> _threeOptions;
public DepartmentController(IDepartmentService departmentService, IOptions<ThreeOptions> threeOptions) { _departmentService = departmentService; _threeOptions = threeOptions; }
public async Task<IActionResult> Index() { int count = _threeOption.Value.BoldDepartmentEmployeeCountThreshold; }
|
在View中使用配置
在Department.cshtml中,修改如下代码。通过判断部门人数大于配置人数,则部门名称显示为粗体。
@using Microsoft.Extensions.Options @using Three @model Three.Models.Department @inject IOptions<ThreeOptions> options
<tr> @if (Model.EmployeeCount > options.Value.BoldDepartmentEmployeeCountThreshold) { <td><strong>@Model.Name</strong></td> } else { <td>@Model.Name</td> } <td>@Model.Location</td> <td>@Model.EmployeeCount</td>
<td> <a asp-controller="Employee" asp-action="Index" asp-route-departmentId="@Model.Id"> Employees </a> </td> </tr>
|
使用其他配置文件
在Program类的CreateHostBuilder方法中,修改代码,增加ConfigureAppConfiguration。不使用原有的appsettings.json,使用自己添加的json文件nick.json。
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, configBuilder) => { configBuilder.Sources.Clear(); configBuilder.AddJsonFile("nick.json"); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
|
View Component
需求:需要做一个控件,在每个页面显示两个字段,公司的总人数和部门平均人数。可以使用View Component实现。
- 为什么Partial View不行?因为没法添加业务逻辑。
- 如果在Controller里面写呢?那就无法到处复用。
- Child Action呢?开销太大,它也需要走完整个Controller的生命周期。
View Component:就像Partial View,但是带有一个迷你的Controller,可以写业务逻辑。可以使用Razor语法来渲染VIew Conponent。
新建ViewComponent
ASP.NET Core MVC默认在项目根目录下的ViewComponents文件夹寻找Component。
项目右键,新建ViewConponents文件夹,在里面新建CompanySummaryViewConponent类。
public class CompanySummaryViewComponent : ViewComponent { private readonly IDepartmentService _departmentService;
public CompanySummaryViewComponent(IDepartmentService departmentService) { _departmentService = departmentService; } public async Task<IViewComponentResult> InvokeAsync() { var summary = await _departmentService.GetCompanySummary(); return View(summary); } }
|
新建View
在Views/Shared下新建Components文件夹,在里面新建CompanySummary文件夹,在里面新建Default.cshtml。
由于在CompanySummaryViewComponent中没有指定返回的view的名称,所以默认会找到Views/Shared/Components/CompanySummary下的Default.cshtml。
@model Three.Models.CompanySummary
<div class="small"> <div class="row h5">Company Summary</div> <div class="row"> <div class="col-md-8 h6">员工总数:</div> <div class="col-md-4 h6">@Model.EmployeeCount</div> </div> <div class="row"> <div class="col-md-8 h6">部门平均:</div> <div class="col-md-4 h6">@Model.AverageDepartmentEmployeeCount</div> </div> </div>
|
使用ViewComponent
编辑Views/Department/Index.cshtml,在Add按钮上面添加代码,通过await Component.InvokeAsync调用,参数name为对应ViewComponent的名称。
@using Three.Models; @model IEnumerable<Department>
<div class="row"> <div class="col-md-10 offset-md-2"> <table class="table"> <tr> <th>Name</th> <th>Location</th> <th>EmployeeCount</th> <th>操作</th> </tr> @Html.DisplayForModel() </table> </div> </div> <div class="row"> <div class="col-md-2"> @await Component.InvokeAsync(name: "CompanySummary") </div>
<div class="col-md-4"> <a asp-action="Add">Add</a> </div> </div>
|
添加参数
添加动态标题,编辑CompanySummaryViewComponent.cs,修改InvokeAsync方法。
public async Task<IViewComponentResult> InvokeAsync(string title) { ViewBag.Title = title; var summary = await _departmentService.GetCompanySummary(); return View(summary); }
|
编辑Default.cshtml,替换标题。
@model Three.Models.CompanySummary
<div class="small"> <div class="row h5">@ViewBag.Title</div> <div class="row"> <div class="col-md-8 h6">员工总数:</div> <div class="col-md-4 h6">@Model.EmployeeCount</div> </div> <div class="row"> <div class="col-md-8 h6">部门平均:</div> <div class="col-md-4 h6">@Model.AverageDepartmentEmployeeCount</div> </div> </div>
|
编辑Views/Department/Index.cshtml,在调用的地方修改代码。
@await Component.InvokeAsync(name: "CompanySummary", arguments: new { title = "部门列表页的汇总" })
|
使用Tag Helper方式调用
使用标签vc:加ViewComponent的名称(用-分隔),参数通过属性传递。
<vc:company-summary title="部门列表页的汇总2"></vc:company-summary>
|
修改_ViewImports.cshtml,把当前程序集Three注册到Tag Helper中。
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" @addTagHelper "*, Three"
|
运行项目,效果如下。