ASP.NET Core 3.x 学习笔记(2)——一个简单的 MVC 项目学习
ASP.NET Core 3.x 学习笔记(2)——一个简单的 MVC 项目学习
本系列学习笔记均来源于B站UP主”软件工艺师“的学习视频,学习连接如下:
https://www.bilibili.com/video/BV1c441167KQ
MVC 相关技术
-
Controller
-
Tag Helper
-
Settings
-
View Component
-
Razor Page(不属于MVC,是微软发布的用于 Web 页面开发的一种标记语法)
模型-视图-控制器 (MVC) 体系结构模式将应用程序分成 3 个主要组件组:模型、视图和控制器。 此模式有助于实现关注点分离。 使用此模式,用户请求被路由到控制器,后者负责使用模型来执行用户操作和/或检索查询结果。 控制器选择要显示给用户的视图,并为其提供所需的任何模型数据。
下图显示 3 个主要组件及其相互引用关系:
视图和控制器均依赖于模型。 但是,模型既不依赖于视图,也不依赖于控制器。 这是分离的一个关键优势。 这种分离允许模型独立于可视化展示进行构建和测试。
模型责任:MVC 应用程序的模型 (M) 表示应用程序和任何应由其执行的业务逻辑或操作的状态。 业务逻辑应与保持应用程序状态的任何实现逻辑一起封装在模型中。 强类型视图通常使用 ViewModel 类型,旨在包含要在该视图上显示的数据。 控制器从模型创建并填充 ViewModel 实例。
视图责任:视图 (V) 负责通过用户界面展示内容。 它们使用 Razor 视图引擎在 HTML 标记中嵌入 .net 代码。
控制器责任:控制器 © 是处理用户交互、使用模型并最终选择要呈现的视图的组件。在 MVC 应用程序中,视图仅显示信息;控制器处理并响应用户输入和交互。 在 MVC 模式中,控制器是初始入口点,负责选择要使用的模型类型和要呈现的视图。
注意点:
- 控制器不应由于责任过多而变得过于复杂。 要阻止控制器逻辑变得过于复杂,请将业务逻辑推出控制器并推入域模型。
- 如果发现控制器操作经常执行相同类型的操作,可将这些常见操作移入筛选器。
本博客内容主要关于一个简单的部门职员管理的 MVC 项目,在本例代码中,M 为下文的 Models + Services,V 为下文的 Views,C 为下文的 Controllers。该项目非常简单入门,仅呈现了 MVC 的相关结构。代码如下:
Models
Department.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Models
{
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public int EmployeeCount { get; set; }
}
}
Employee.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Models
{
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
}
}
CompanySummary.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Models
{
public class CompanySummary
{
public int EmployeeCount { get; set; }
public int AverageDepartmentEmployeeCount { get; set; }
}
}
Services
IDepartmentService.cs
using ASPNETCore_Learning_Three.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Services
{
public interface IDepartmentService
{
Task<IEnumerable<Department>> GetAll();
Task<Department> GetById(int Id);
Task<CompanySummary> GetCompanySummary();
Task Add(Department department);
}
}
DepartmentService.cs
using ASPNETCore_Learning_Three.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Services
{
public class DepartmentService : IDepartmentService
{
private readonly List<Department> _departments = new List<Department>();
public DepartmentService()
{
_departments.Add(new Department
{
Id = 1,
Name = "IT",
EmployeeCount = 16,
Location = "xiangzhou"
});
_departments.Add(new Department
{
Id = 2,
Name = "HR",
EmployeeCount = 20,
Location = "xiangzhou"
});
_departments.Add(new Department
{
Id = 3,
Name = "LY",
EmployeeCount = 20,
Location = "jida"
});
}
public Task Add(Department department)
{
department.Id = _departments.Max(x => x.Id) + 1;
_departments.Add(department);
return Task.CompletedTask;
}
public Task<IEnumerable<Department>> GetAll()
{
return Task.Run(() => _departments.AsEnumerable());
}
public Task<Department> GetById(int Id)
{
return Task.Run(() => _departments.FirstOrDefault(x => x.Id == Id));
}
public Task<CompanySummary> GetCompanySummary()
{
return Task.Run(() =>
{
return new CompanySummary
{
EmployeeCount = _departments.Sum(x => x.EmployeeCount),
AverageDepartmentEmployeeCount = (int)_departments.Average(x => x.EmployeeCount)
};
});
}
}
}
IEmployeeService.cs
using ASPNETCore_Learning_Three.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Services
{
public interface IEmployeeService
{
Task Add(Employee employee);
Task<IEnumerable<Employee>> GetByDepartmentId(int DepartmentId);
Task<Employee> Fire(int id);
}
}
EmployeeService.cs
using ASPNETCore_Learning_Three.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Services
{
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 = "Michal",
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 = "Aril",
LastName = "Lavigne",
Gender = Gender.女
});
_employees.Add(new Employee
{
Id = 7,
DepartmentId = 3,
FirstName = "Machill",
LastName = "Jonh",
Gender = Gender.男
});
}
public Task Add(Employee employee)
{
employee.Id = _employees.Max(x => x.Id) + 1;
_employees.Add(employee);
return Task.CompletedTask;
}
public Task<Employee> Fire(int id)
{
return Task.Run(() =>
{
var employee = _employees.FirstOrDefault(x => x.Id == id);
if (employee != null)
{
employee.Fired = true;
return employee;
}
return null;
});
}
public Task<IEnumerable<Employee>> GetByDepartmentId(int DepartmentId)
{
return Task.Run(() => _employees.Where(x => x.DepartmentId == DepartmentId));
}
}
}
Controllers
DepartmentController.cs
using ASPNETCore_Learning_Three.Models;
using ASPNETCore_Learning_Three.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Controllers
{
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);
}
//[HttpGet] //默认为 HttpGet,可以省略
public IActionResult Add()
{
ViewBag.Title = "Add Index";
return View(new Department());
}
[HttpPost]
public async Task<IActionResult> Add(Department model)
{
if (ModelState.IsValid)
{
await _departmentService.Add(model);
}
return RedirectToAction(nameof(Index)); //这里可以直接使用字符串,但是用 nameof 比较利于重命名
}
}
}
EmployeeController.cs
using ASPNETCore_Learning_Three.Models;
using ASPNETCore_Learning_Three.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNETCore_Learning_Three.Controllers
{
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 = $"Employee 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), new { departmentId = model.DepartmentId });
}
public async Task<IActionResult> Fire(int employeeId)
{
var employee = await _employeeService.Fire(employeeId);
return RedirectToAction(nameof(Index), new { departmentId = employee.DepartmentId });
}
}
}
Views
这里的 View 使用 Razor Pages。View 的结构如下:
在 MVC 的开发中,有以下几个固定作用的页面。
Views/_ViewStart.cshtml 文件:视图开始页。Views/_ViewStart.cshtml 文件将 Views/Shared/_Layout.cshtml 文件引入到每个视图中 。 可以使用 Layout
属性设置不同的布局视图,或将它设置为 null
,这样将不会使用任何布局文件。
@{
//设置:针对所有页面,其母版页为 _Layout
Layout = "_Layout";
}
Views/_ViewImports.cshtml 文件:视图导入页。用于导入 Views 编写过程中需要使用的资源。如下导入 TagHelper:
@*全局取用TagHelper,不想全局取用的话可以将其添加的具体的View中*@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Views/Shared/_Layout.cshtml 文件:布局视图,即母版页。该视图防止于 Shared 文件夹下,用户设置页面布局。与上文 _ViewStart 视图联合使用。
其中,形似 asp-append-version 的语法为 TagHelper。
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@*设置开发环境,当环境为 Development,加载以下资源*@
<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="css/all.min.css" />
</environment>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-2">
@*此处类似于 asp-append-version 就是 TagHelper*@
@*asp-append-version 作用于 img 防止图片缓存*@
<img asp-append-version="true" alt="Logo" src="~/images/log.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-2">
@RenderBody()
</div>
</div>
</div>
</body>
</html>
Department
Views/Department/Index.cshtml:Department 的主页。本项目中,在 ASP.NET Core 3.x 学习笔记(一)中将其设置为项目启动的默认路由,即 DepartmentController 中的 Index路由。
@using ASPNETCore_Learning_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>Employee</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/Department/DisplayTemplates/Department.cshtml:Department 的数据展示视图。
- 视图与 Index.cshtml 中的 @Html.DisplayForModel() 关联,展示具体数据;
- asp-controller 关联到控制器 EmployeeController;
- asp-action 关联到控制器 DepartmentController 中的 Index 路由;
- asp-route-departmentId 绑定此路由需要传递的参数 departmentId 为 @Model.Id(即选中的 Department 的 Id)
在此视图中,操作 Employees 连接,即可跳转查看对应 Department 下的所有 Employee 信息。
@model ASPNETCore_Learning_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>
Views/Department/Add.cshtml:用于添加 Department 的表单视图,关联到 DepartmentController 下的 Add 路由。
@using ASPNETCore_Learning_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>
Employee
Views/Department/Index.cshtml:Employee 的主页,关联到控制器 EmployeeController 中的 Index 路由。
代码中的 Add 连接,因为要在到对应 Deparment 下添加 Employee,故需要传递参数 DepartmentId。
@using ASPNETCore_Learning_Three.Models
@model IEnumerable<Employee>
<div class="row">
<div class="col-md-10 offset-md-2">
<table class="table">
<tr>
<th>First Name</th>
<th>Last Name</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" asp-route-departmentID="@ViewBag.DepartmentId">Add</a>
</div>
</div>
Views/Department/DisplayTemplates/Employee.cshtml:Employee 的数据视图。
@model ASPNETCore_Learning_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>
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201121184550940.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NRV0hfU1NHUw==,size_16,color_FFFFFF,t_70#pic_center)
}
</td>
</tr>
Views/Department/Add.cshtml:Employee 的新增表单视图。
此处使用 input 接收了参数 DepartmentId,但该参数并不需要在页面显式,故隐藏。
@using ASPNETCore_Learning_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">
<div class="col-md-2 offset-md-2">
<button type="submit" class="btn btn-primary">Add</button>
</div>
</div>
</form>