創(chuàng)建ASP.NET Core MVC應(yīng)用程序(4)-添加CRUD動(dòng)作方法和視圖
創(chuàng)建CRUD動(dòng)作方法及視圖
參照VS自帶的基架(Scaffold)系統(tǒng)-MVC Controller with views, using Entity Framework我們來創(chuàng)建CRUD方法。
① 將上一篇的Models/UserContext.cs文件中的用來指定使用的數(shù)據(jù)庫邏輯的OnConfiguring方法?刪除,將邏輯移到Startup.cs文件中的ConfigureServices方法中。
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("MyConnection");
services.AddDbContext<UserContext>(options =>
options.UseMySQL(connectionString));
// Add framework services.
services.AddMvc();
}
② 在UserController.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個(gè)數(shù)據(jù)庫上下文到該控制器。數(shù)據(jù)庫上下文將被應(yīng)用到控制器中的每一個(gè)CRUD方法。
private readonly UserContext _context;
public UserController(UserContext context)
{
_context = context;
}
③ 在UserController.cs中?添加基本的CRUD方法:
// GET: /<controller>/
public async Task<IActionResult> Index()
{
return View(await _context.Users.ToListAsync());
}
// GET: User/Details/1
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// GET: User/Create
public IActionResult Create()
{
return View();
}
// POST: User/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Name,Email,Bio")]User user)
{
if (ModelState.IsValid)
{
_context.Add(user);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(user);
}
//GET: User/Edit/1
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Edit/1
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
{
if (id != user.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(user);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(user.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(user);
}
//// GET: User/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Delete/1
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
private bool UserExists(int id)
{
return _context.Users.Any(e => e.ID == id);
}
一個(gè)http://localhost:5000/User 這樣的請(qǐng)求到達(dá)User控制器后,將會(huì)從User表返回所有的數(shù)據(jù),將將這些數(shù)據(jù)傳遞到Index視圖:

④ 在Views/User文件夾中添加與上述Action方法名稱相對(duì)應(yīng)的Index.cshtml文件、Create.cshtml文件、Details.cshtml文件、Edit.cshtml文件、Delete.cshtml文件。
Create.cshtml運(yùn)行效果:

Details.cshtml運(yùn)行效果:

Edit.cshtml運(yùn)行效果:

Delete.cshtml運(yùn)行效果:

強(qiáng)類型模型和@model關(guān)鍵字
MVC提供了傳遞強(qiáng)類型對(duì)象給視圖的能力,這樣為你的代碼提供了更好的編譯時(shí)檢查,并在VS中提供了更豐富的智能感知功能。
查看UserController/Details方法:
// GET: User/Details/1
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
id參數(shù)通常作為路由數(shù)據(jù)來傳遞,比如 http://localhost:5000/user/details/1 會(huì):
- Controller設(shè)置為
user(第一個(gè)URL段) - Action設(shè)置為
details(第二個(gè)URL段) - id設(shè)置為1(第三個(gè)URL段)
你也可以通過查詢字符串來傳遞id:
http://localhost:5000/user/details?id=1
如果指定的User被找到,則User Model實(shí)例將被傳遞到Details視圖:
return View(user);
查看Views/User/Details.cshtml文件:
@model IEnumerable<MyFirstApp.Models.User>
@{
ViewData["Title"] = "Index - User List";
}
<h2>Index - User List</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Bio)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Bio)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
你會(huì)發(fā)現(xiàn)在頂部有一個(gè)@model語句,你可以指定視圖所期望的對(duì)象類型。
@model MyFirstApp.Models.User
@model指令允許你通過使用強(qiáng)類型的Model對(duì)象來訪問從控制器傳遞到視圖的User對(duì)象。例如,在Details.cshtml視圖中,通過使用強(qiáng)類型的Model對(duì)象傳遞User的每一個(gè)字段到DisplayNameFor和DisplayFor HTML Helper。
再來查看Index.cshtml文件和User控制器中的Index方法。注意在調(diào)用View方法時(shí),是如何創(chuàng)建一個(gè)List對(duì)象的。下面的代碼將從Index Action方法傳遞整個(gè)User到視圖中。
User控制器中的Index方法:
public async Task<IActionResult> Index()
{
return View(await _context.Users.ToListAsync());
}
Index.cshtml文件最頂部:
@model IEnumerable<MyFirstApp.Models.User>
@model指令允許你訪問通過強(qiáng)類型的Model從控制器傳遞到視圖的User列表。例如,在Index.cshtml視圖中,在強(qiáng)類型的Model對(duì)象上通過foreach語句遍歷了整個(gè)User列表:
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Bio)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
添加倉儲(chǔ)類
首先,新建一個(gè)Repositories文件夾。在該文件夾下定義一個(gè)IUserRepository接口。
namespace MyFirstApp.Repositories
{
public interface IUserRepository
{
Task<IEnumerable<User>> GetAll();
Task<User> Get(int id);
void Add(User user);
void Update(User user);
void Delete(int id);
bool UserExists(int id);
}
}
接著再添加一個(gè)UserRepository來實(shí)現(xiàn)IUserRepository接口。將之前定義的UserContext.cs邏輯移到該類中,在UserRepository.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個(gè)數(shù)據(jù)庫上下文(UserContext)到該倉儲(chǔ)類。數(shù)據(jù)庫上下文將被應(yīng)用到倉儲(chǔ)類中的每一個(gè)CRUD方法。
public class UserRepository : IUserRepository
{
private readonly UserContext _context;
public UserRepository(UserContext context)
{
_context = context;
}
public async Task<IEnumerable<User>> GetAll()
{
return await _context.Users.ToListAsync();
}
public async Task<User> Get(int id)
{
return await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
}
public async void Add(User user)
{
//_context.Users.Add(user);
_context.Add(user);
await _context.SaveChangesAsync();
}
public async void Update(User user)
{
//_context.Users.Update(user);
_context.Update(user);
await _context.SaveChangesAsync();
}
public async void Delete(int id)
{
var user = _context.Users.SingleOrDefault(u => u.ID == id);
_context.Users.Remove(user);
await _context.SaveChangesAsync();
}
public bool UserExists(int id)
{
return _context.Users.Any(e => e.ID == id);
}
}
在Controller構(gòu)造函數(shù)中依賴注入U(xiǎn)serRepository
再修改Controllers/UserController.cs文件,將private readonly的UserContext變量刪除:
private readonly UserContext _context;
添加IUserRepository變量:
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
將所有方法中的_context操作刪除,替換成_userRepository。例如,將Index方法中的_context.Users.ToListAsync()刪除:
return View(await _context.Users.ToListAsync());
替換成
return View(await _context.Users.ToListAsync());
最終的UserController.cs如下:
public class UserController : Controller
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
// GET: /<controller>/
public async Task<IActionResult> Index()
{
return View(await _userRepository.GetAll());
}
// GET: User/Details/1
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _userRepository.Get(id.Value);
if (user == null)
{
return NotFound();
}
return View(user);
}
// GET: User/Create
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("ID,Name,Email,Bio")]User user)
{
if (ModelState.IsValid)
{
_userRepository.Add(user);
return RedirectToAction("Index");
}
return View(user);
}
//GET: User/Edit/1
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _userRepository.Get(id.Value);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Edit/1
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
{
if (id != user.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_userRepository.Update(user);
}
catch (DbUpdateConcurrencyException)
{
if (!_userRepository.UserExists(user.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(user);
}
//// GET: User/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _userRepository.Get(id.Value);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: User/Delete/1
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
_userRepository.Delete(id);
return RedirectToAction("Index");
}
}
注冊(cè)倉儲(chǔ)
通過定義Repository接口,從MVC Controller中解耦該repository類。通過注入一個(gè)UserRepository來代替直接在Controller里面實(shí)例化一個(gè)UserRepository類。
為了注入一個(gè)Repository到Controller,我們必須通過DI容器來注冊(cè)它,打開Startup.cs?文件,在ConfigureServices方法添加如下代碼:
// Add our repository type
services.AddScoped<IUserRepository, UserRepository>();
DataAnnotations & Tag Helpers
我們?yōu)?em>Models/User.cs文件添加Display和DataType注解,首先要添加必要的命名空間using System.ComponentModel.DataAnnotations;:

再將屬性在視圖上顯示成中文:

Display Attribute指定字段的顯示名,DataTypeAttribute指定數(shù)據(jù)類型。
最終的顯示效果如下:

打開Views/User/Index.cshtml,你會(huì)發(fā)現(xiàn)Edit,Details,Delete鏈接是由MVC Core Anchor Tag Helper生成的。
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
Tag Helpers允許服務(wù)器代碼在Razor文件中參與創(chuàng)建和渲染HTML元素。在上述代碼中,AnchorTagHelper從Controller Action動(dòng)作方法和路由ID動(dòng)態(tài)生成HTMLhref屬性值。

查看Startup.cs中的Configure方法:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core會(huì)將http://localhost:5000/User/Edit/4 轉(zhuǎn)換成發(fā)送給User控制器的Edit方法(帶有值為4的Id參數(shù))的請(qǐng)求。
查看UserController.cs中的[HttpPost]版本的Edit方法:
// POST: User/Edit/1
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
{
if (id != user.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
// _context.Update(user);
// await _context.SaveChangesAsync();
_userRepository.Update(user);
}
catch (DbUpdateConcurrencyException)
{
if (!_userRepository.UserExists(user.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(user);
}
[Bind] Attribute是一種防止over-posting(過度提交)的方式。應(yīng)該只把你需要改變的屬性包含到[Bind] Attribute中。
[ValidateAntiForgeryToken] Attribute是用來防止偽造請(qǐng)求的,會(huì)與Views/User/Edit.cshtml視圖文件生成的反偽造標(biāo)記(Token)進(jìn)行配對(duì)。Views/User/Edit.cshtml視圖文件通過Form Tag Helper來生成反偽造標(biāo)記(Token)。
<form asp-action="Edit">
Form Tag Helper生成一個(gè)隱藏的防偽標(biāo)記必須和User控制器中的Eidt方法的[ValidateAntiForgeryToken]產(chǎn)生的防偽標(biāo)記相匹配。
查看Edit.cshtml,會(huì)發(fā)現(xiàn)基架系統(tǒng)(Scaffolding System)會(huì)為User類的每一個(gè)屬性生成用來呈現(xiàn)的<label>和<input>元素。
<form asp-action="Edit">
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger" />
</div>
</div>
</form>
基架代碼使用了多個(gè)Tag Helper方法來簡(jiǎn)化HTML標(biāo)記。
- Label Tag Helper用來顯示字段的名字。
-
Input Tag Helper用來呈現(xiàn)HTML
<input>元素。 - Validation Tag Helper用來顯示關(guān)聯(lián)屬性的驗(yàn)證信息。
最終在瀏覽器中為<form>元素所生成的HTML如下:

HTML<form>中的actionAttribute設(shè)置成POST到/User/Edit/idURL(所有<input>元素都在該<form>元素中)。當(dāng)點(diǎn)擊Save按鈕時(shí),表單數(shù)據(jù)會(huì)被發(fā)送(POST)到服務(wù)器。在</form>元素的上面顯示了Form Tag Helper所生成的隱藏的XSRF反偽造標(biāo)記。
處理POST請(qǐng)求
查看[HttpPost]版本的Edit方法:

[ValidateAntiForgeryToken]驗(yàn)證Form Tag Helper中的反偽造標(biāo)記生成器所生成的隱藏的XSRF反偽造標(biāo)記。
模型綁定(Model Binding)機(jī)制接受POST過來的表單數(shù)據(jù)并創(chuàng)建一個(gè)User對(duì)象并作為user參數(shù)。ModelState.IsValid方法驗(yàn)證從表單提交過來的數(shù)據(jù)可以用來修改一個(gè)User對(duì)象。如果數(shù)據(jù)有效,就可以進(jìn)行保存。被更新的數(shù)據(jù)通過調(diào)用數(shù)據(jù)庫的上下文(Database Context)的SaveChangesAsync方法來保存到數(shù)據(jù)庫中。數(shù)據(jù)保存之后,代碼將用戶重定向到UserController類的Index方法。該頁面會(huì)顯示剛剛被改動(dòng)后的最新的用戶集合。
在表單被POST到服務(wù)器之前,客戶端驗(yàn)證會(huì)檢查所有字段上的驗(yàn)證規(guī)則,如果有任何驗(yàn)證錯(cuò)誤,則會(huì)顯示該錯(cuò)誤信息,并且表單不會(huì)被發(fā)送到服務(wù)器。如果禁用了JS,將不會(huì)有客戶端驗(yàn)證,但服務(wù)器會(huì)檢測(cè)POST過來的數(shù)據(jù)是無效的,表單會(huì)重新顯示錯(cuò)誤信息。