創(chuàng)建ASP.NET Core MVC應(yīng)用程序(4)-添加CRUD動作方法和視圖
創(chuàng)建CRUD動作方法及視圖
參照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ù)中采用依賴注入來注入一個數(shù)據(jù)庫上下文到該控制器碍岔。數(shù)據(jù)庫上下文將被應(yīng)用到控制器中的每一個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);
}
一個http://localhost:5000/User
這樣的請求到達(dá)User控制器后,將會從User
表返回所有的數(shù)據(jù)谜慌,將將這些數(shù)據(jù)傳遞到Index
視圖:
④ 在Views/User文件夾中添加與上述Action方法名稱相對應(yīng)的Index.cshtml文件然想、Create.cshtml文件、Details.cshtml文件欣范、Edit.cshtml文件、Delete.cshtml文件令哟。
Create.cshtml運行效果:
Details.cshtml運行效果:
Edit.cshtml運行效果:
Delete.cshtml運行效果:
強類型模型和@model關(guān)鍵字
MVC提供了傳遞強類型對象給視圖的能力恼琼,這樣為你的代碼提供了更好的編譯時檢查,并在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
會:
- Controller設(shè)置為
user
(第一個URL段) - Action設(shè)置為
details
(第二個URL段) - id設(shè)置為1(第三個URL段)
你也可以通過查詢字符串來傳遞id
:
http://localhost:5000/user/details?id=1
如果指定的User被找到,則User
Model實例將被傳遞到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>
你會發(fā)現(xiàn)在頂部有一個@model
語句狠半,你可以指定視圖所期望的對象類型噩死。
@model MyFirstApp.Models.User
@model
指令允許你通過使用強類型的Model
對象來訪問從控制器傳遞到視圖的User對象。例如神年,在Details.cshtml視圖中已维,通過使用強類型的Model
對象傳遞User的每一個字段到DisplayNameFor
和DisplayFor
HTML Helper。
再來查看Index.cshtml文件和User控制器中的Index
方法已日。注意在調(diào)用View方法時垛耳,是如何創(chuàng)建一個List
對象的。下面的代碼將從Index
Action方法傳遞整個User
到視圖中飘千。
User控制器中的Index
方法:
public async Task<IActionResult> Index()
{
return View(await _context.Users.ToListAsync());
}
Index.cshtml文件最頂部:
@model IEnumerable<MyFirstApp.Models.User>
@model
指令允許你訪問通過強類型的Model
從控制器傳遞到視圖的User列表堂鲜。例如,在Index.cshtml視圖中护奈,在強類型的Model
對象上通過foreach
語句遍歷了整個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>
}
添加倉儲類
首先缔莲,新建一個Repositories文件夾。在該文件夾下定義一個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);
}
}
接著再添加一個UserRepository
來實現(xiàn)IUserRepository
接口痴奏。將之前定義的UserContext.cs邏輯移到該類中磺箕,在UserRepository.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個數(shù)據(jù)庫上下文(UserContext)到該倉儲類。數(shù)據(jù)庫上下文將被應(yīng)用到倉儲類中的每一個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ù)中依賴注入UserRepository
再修改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");
}
}
注冊倉儲
通過定義Repository接口,從MVC Controller中解耦該repository類棉姐。通過注入一個UserRepository來代替直接在Controller里面實例化一個UserRepository類屠列。
為了注入一個Repository到Controller,我們必須通過DI容器來注冊它伞矩,打開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指定字段的顯示名,DataType
Attribute指定數(shù)據(jù)類型乃坤。
最終的顯示效果如下:
打開Views/User/Index.cshtml,你會發(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動作方法和路由ID動態(tài)生成HTMLhref
屬性值狱杰。
查看Startup.cs中的Configure
方法:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core會將http://localhost:5000/User/Edit/4
轉(zhuǎn)換成發(fā)送給User
控制器的Edit
方法(帶有值為4的Id
參數(shù))的請求。
查看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是用來防止偽造請求的,會與Views/User/Edit.cshtml視圖文件生成的反偽造標(biāo)記(Token)進行配對朗和。Views/User/Edit.cshtml視圖文件通過Form Tag Helper來生成反偽造標(biāo)記(Token)错沽。
<form asp-action="Edit">
Form Tag Helper生成一個隱藏的防偽標(biāo)記必須和User
控制器中的Eidt
方法的[ValidateAntiForgeryToken]
產(chǎn)生的防偽標(biāo)記相匹配。
查看Edit.cshtml,會發(fā)現(xiàn)基架系統(tǒng)(Scaffolding System)會為User
類的每一個屬性生成用來呈現(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>
基架代碼使用了多個Tag Helper方法來簡化HTML標(biāo)記千埃。
- Label Tag Helper用來顯示字段的名字。
-
Input Tag Helper用來呈現(xiàn)HTML
<input>
元素镀层。 - Validation Tag Helper用來顯示關(guān)聯(lián)屬性的驗證信息镰禾。
最終在瀏覽器中為<form>
元素所生成的HTML如下:
HTML<form>
中的action
Attribute設(shè)置成POST到/User/Edit/id
URL(所有<input>
元素都在該<form>
元素中)。當(dāng)點擊Save
按鈕時唱逢,表單數(shù)據(jù)會被發(fā)送(POST)到服務(wù)器吴侦。在</form>
元素的上面顯示了Form Tag Helper所生成的隱藏的XSRF反偽造標(biāo)記。
處理POST請求
查看[HttpPost]
版本的Edit方法:
[ValidateAntiForgeryToken]
驗證Form Tag Helper中的反偽造標(biāo)記生成器所生成的隱藏的XSRF反偽造標(biāo)記坞古。
模型綁定(Model Binding)機制接受POST過來的表單數(shù)據(jù)并創(chuàng)建一個User
對象并作為user
參數(shù)备韧。ModelState.IsValid
方法驗證從表單提交過來的數(shù)據(jù)可以用來修改一個User
對象。如果數(shù)據(jù)有效痪枫,就可以進行保存织堂。被更新的數(shù)據(jù)通過調(diào)用數(shù)據(jù)庫的上下文(Database Context)的SaveChangesAsync
方法來保存到數(shù)據(jù)庫中叠艳。數(shù)據(jù)保存之后,代碼將用戶重定向到UserController
類的Index
方法易阳。該頁面會顯示剛剛被改動后的最新的用戶集合附较。
在表單被POST到服務(wù)器之前,客戶端驗證會檢查所有字段上的驗證規(guī)則潦俺,如果有任何驗證錯誤拒课,則會顯示該錯誤信息,并且表單不會被發(fā)送到服務(wù)器事示。如果禁用了JS早像,將不會有客戶端驗證,但服務(wù)器會檢測POST過來的數(shù)據(jù)是無效的肖爵,表單會重新顯示錯誤信息卢鹦。