创建ASP.NET Core MVC应用程序(4)-添加CRUD动作方法和视图

创建CRUD动作方法及视图

参照VS自带的基架(Scaffold)系统-MVC Controller with views, using Entity Framework我们来创建CRUD方法。

① 将上一篇的Models/UserContext.cs文件中的用来指定使用的数据库逻辑的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 构造函数中采用依赖注入来注入一个数据库上下文到该控制器。数据库上下文将被应用到控制器中的每一个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 这样的请求到达User控制器后,将会从User表返回所有的数据,将将这些数据传递到Index视图:

④ 在Views/User文件夹中添加与上述Action方法名称相对应的Index.cshtml文件、Create.cshtml文件、Details.cshtml文件、Edit.cshtml文件、Delete.cshtml文件。

Create.cshtml运行效果:

Details.cshtml运行效果:

Edit.cshtml运行效果:

Delete.cshtml运行效果:

强类型模型和@model关键字

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参数通常作为路由数据来传递,比如 http://localhost:5000/user/details/1 会:

  • Controller设置为user(第一个URL段)
  • Action设置为details(第二个URL段)
  • id设置为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>

你会发现在顶部有一个@model语句,你可以指定视图所期望的对象类型。

@model MyFirstApp.Models.User

@model指令允许你通过使用强类型的Model对象来访问从控制器传递到视图的User对象。例如,在Details.cshtml视图中,通过使用强类型的Model对象传递User的每一个字段到DisplayNameForDisplayFor HTML Helper。

再来查看Index.cshtml文件和User控制器中的Index方法。注意在调用View方法时,是如何创建一个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来实现IUserRepository接口。将之前定义的UserContext.cs逻辑移到该类中,在UserRepository.cs 构造函数中采用依赖注入来注入一个数据库上下文(UserContext)到该仓储类。数据库上下文将被应用到仓储类中的每一个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构造函数中依赖注入UserRepository

再修改Controllers/UserController.cs文件,将private readonlyUserContext变量删除:

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

我们为Models/User.cs文件添加DisplayDataType注解,首先要添加必要的命名空间using System.ComponentModel.DataAnnotations;

再将属性在视图上显示成中文:

Display Attribute指定字段的显示名,DataTypeAttribute指定数据类型。

最终的显示效果如下:

打开Views/User/Index.cshtml,你会发现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允许服务器代码在Razor文件中参与创建和渲染HTML元素。在上述代码中,AnchorTagHelper从Controller Action动作方法和路由ID动态生成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 转换成发送给User控制器的Edit方法(带有值为4的Id参数)的请求。

查看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(过度提交)的方式。应该只把你需要改变的属性包含到[Bind] Attribute中。

[ValidateAntiForgeryToken] Attribute是用来防止伪造请求的,会与Views/User/Edit.cshtml视图文件生成的反伪造标记(Token)进行配对。Views/User/Edit.cshtml视图文件通过Form Tag Helper来生成反伪造标记(Token)。

<form asp-action="Edit">

Form Tag Helper生成一个隐藏的防伪标记必须和User控制器中的Eidt方法的[ValidateAntiForgeryToken]产生的防伪标记相匹配。

查看Edit.cshtml,会发现基架系统(Scaffolding System)会为User类的每一个属性生成用来呈现的<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标记。

  • Label Tag Helper用来显示字段的名字。
  • Input Tag Helper用来呈现HTML<input>元素。
  • Validation Tag Helper用来显示关联属性的验证信息。

最终在浏览器中为<form>元素所生成的HTML如下:

HTML<form>中的actionAttribute设置成POST到/User/Edit/idURL(所有<input>元素都在该<form>元素中)。当点击Save按钮时,表单数据会被发送(POST)到服务器。在</form>元素的上面显示了Form Tag Helper所生成的隐藏的XSRF反伪造标记。

处理POST请求

查看[HttpPost]版本的Edit方法:

[ValidateAntiForgeryToken]验证Form Tag Helper中的反伪造标记生成器所生成的隐藏的XSRF反伪造标记。

模型绑定(Model Binding)机制接受POST过来的表单数据并创建一个User对象并作为user参数。ModelState.IsValid方法验证从表单提交过来的数据可以用来修改一个User对象。如果数据有效,就可以进行保存。被更新的数据通过调用数据库的上下文(Database Context)的SaveChangesAsync方法来保存到数据库中。数据保存之后,代码将用户重定向到UserController类的Index方法。该页面会显示刚刚被改动后的最新的用户集合。

在表单被POST到服务器之前,客户端验证会检查所有字段上的验证规则,如果有任何验证错误,则会显示该错误信息,并且表单不会被发送到服务器。如果禁用了JS,将不会有客户端验证,但服务器会检测POST过来的数据是无效的,表单会重新显示错误信息。

参考文档

个人博客

我的个人博客

创建ASP.NET Core MVC应用程序(4)-添加CRUD动作方法和视图的更多相关文章

  1. 创建ASP.NET Core MVC应用程序(6)-添加验证

    创建ASP.NET Core MVC应用程序(6)-添加验证 DRY原则 DRY("Don't Repeat Yourself")是MVC的设计原则之一.ASP.NET MVC鼓励 ...

  2. 创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段

    创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段 添加查询功能 本文将实现通过Name查询用户信息. 首先更新GetAll方法以启用查询: public async ...

  3. 创建ASP.NET Core MVC应用程序(1)-添加Controller和View

    创建ASP.NET Core MVC应用程序(1)-添加Controller和View 参考文档:Getting started with ASP.NET Core MVC and Visual St ...

  4. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

  5. 创建ASP.NET Core MVC应用程序(2)-利用MySQL Connector NET连接到MySQL

    创建ASP.NET Core MVC应用程序(2)-利用MySQL Connector NET连接到MySQL 用惯.NET的研发人员都习惯性地使用SQLServer作为数据库.然而.NET Core ...

  6. 跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)

    作者:陈希章 发表于 2017年6月25日 谈一谈.NET 的跨平台 终于要写到这一篇了.跨平台的支持可以说是 Office 365 平台在设计伊始就考虑的目标.我在前面的文章已经提到过了,Micro ...

  7. 【翻译】使用Visual Studio创建Asp.Net Core MVC (一)

    This tutorial will teach you the basics of building an ASP.NET Core MVC web app using Visual Studio ...

  8. ASP.NET Core MVC应用程序中的后台工作任务

    在应用程序的内存中缓存常见数据(如查找)可以显着提高您的MVC Web应用程序性能和响应时间.当然,这些数据必须定期刷新. 当然你可以使用任何方法来更新数据,例如Redis中就提供了设定缓存对象的生命 ...

  9. Entity Framework Core系列之实战(ASP.NET Core MVC应用程序)

    本示例演示在ASP.NET 应用程序中使用EF CORE创建数据库并对其做基本的增删改查操作.当然我们默认你的机器上已经安装了.NET CORE SDK以及合适的IDE.本例使用的是Visual St ...

随机推荐

  1. Thrift-0.9.2编译安装

    确定安装好了boost1.54以上 确定libevent版本大于1.0 只编译生成cpp库 ./configure --without-java --without-lua --without-pyt ...

  2. ABP理论学习之内嵌资源文件

    返回总目录 本篇目录 介绍 创建内嵌文件 暴露内嵌文件 使用内嵌文件 介绍 在一个web应用中,有供客户端使用的javascript,css,xml等文件.它们一般是作为分离的文件被添加到web项目中 ...

  3. 《代码的未来》读书笔记:内存管理与GC那点事儿

    一.内存是有限的 近年来,我们的电脑内存都有好几个GB,也许你的电脑是4G,他的电脑是8G,公司服务器内存是32G或者64G.但是,无论内存容量有多大,总归不是无限的.实际上,随着内存容量的增加,软件 ...

  4. Senparc.Weixin.MP SDK 微信公众平台开发教程(十七):个性化菜单接口说明

    前不久微信上线了个性化菜单接口,Senparc.Weixin SDK也已经同步更新. 本次更新升级Senparc.Weixin.MP版本到v13.5.2,依赖Senparc.Weixin版本4.5.4 ...

  5. C语言 · 复习杂记

    /*=================================*/ /* 基础部分 */ /*=================================*/一:.CPP--C++文件: ...

  6. Atitit.软件开发的几大规则,法则,与原则Principle v3

    Atitit.软件开发的几大规则,法则,与原则Principle  v31.1. 修改历史22. 设计模式六大原则22.1. 设计模式六大原则(1):单一职责原则22.2. 设计模式六大原则(2):里 ...

  7. 用SQL语句创建四个表并完成相关题目-10月18日更新

    1. 查询Student表中的所有记录的Sname.Ssex和Class列. 2. 查询教师所有的单位即不重复的Depart列. 3. 查询Student表的所有记录. 4. 查询Score表中成绩在 ...

  8. 谈谈关键字final

    final:可用于修饰类.方法.变量,表示它修饰的类.方法和变量不可改变. (1)修饰变量:变量只能被赋值一次,赋值后不能更改.按照Java代码惯例,final变量就是常量,而且通常常量名要大写: ① ...

  9. 有吧友需要PDF的下载站点,好吧,我这边汇总一下

    [经验]谈谈怎么找自己想要的资源吧~ http://www.cnblogs.com/dunitian/p/4715482.html PDF Free Computer, Programming, Ma ...

  10. Linux常用网络命令

    1.tracepath tracepath追踪出到指定的目的地址的网络路径,并给出在路径上的每一跳(hop).如果你的网络有问题或是慢了,tracepath可以查出网络在哪里断了或是慢了. 命令格式: ...