ASP.NET Core Razor 编辑表单

上一章节我们介绍了标签助手和 HTML 助手,也使用标签助手和 HTML 助手分别创建了一个职工列表,感觉好像有点喜欢上标签助手和 HTML 助手了,正好之前我们只讲解了如何列出数据,没有讲解如何创建表单来添加和修改数据

要不本章节我们就来讲讲? 顺带多用用标签助手和 HTML 助手

本章中,我们将继续讨论标签助手。 与此同时,还会给 HelloWorld 应用程序中添加一项新功能,并使其能够编辑现有员工的详细信息

我们将首先在每位员工旁边上添加一个链接,以便访问 HomeController 上的 "编辑" 操作,同时将页面美化一下

修改 Index.cshtml 为如下内容

@model HomePageViewModel
@{
    ViewBag.Title = "职工列表";
}
<style>
    body {margin:10px auto;text-align:center}
   table {
        margin:0 auto;
        width:90%

   }
   table, th, td {
        border:1px solid #eee;
        border-collapse:collapse;
        border-spacing:0;
        padding:5px;
        text-align:center
   }

   .txt-left {
       text-align:left;
   }
</style>
<h1>职工列表</h1>
<table>
    <tr>
        <td>ID</td>
        <td>姓名</td>
        <td class="txt-left">操作</td>
    </tr>
    @foreach (var employee in Model.Employees)
    {
    <tr>
        <td>@employee.ID</td>
        <td>@employee.Name</td>
        <td class="txt-left"><a asp-action="Detail" asp-route-Id="@employee.ID">详情</a> &nbsp; <a asp-controller="Home" asp-action="Edit"
   asp-route-id="@employee.ID">编辑</a></td>
    </tr>
    }
</table>

刷新浏览器,显示结果如下

如果我们此时点击任意一个 编辑 ,则会抛出 404 页面不存在错误

我们先来给 HomeController 添加一个 Edit 方法,接受一个 int 类型的 id 参数,然后返回一个 IActionResult 的结果,方法内容如下

[HttpGet]
public IActionResult Edit(int id)
{

    var model = new HomePageViewModel();
    SQLEmployeeData sqlData = new SQLEmployeeData(_context);

    Employee employee = sqlData.Get(id);

    if ( employee == null ) {
        return RedirectToAction("Index");
    }

    return View(employee);
}

这段代码出现了很多新面孔,比如 [HttpGet] 特性,有关 C# 特性的知识,请访问我们的 C# 基础教程:特性 ( Attribute )

ASP.NET Core 所有以 Http 开头的特性用于标识该方法只接受特定的 HTTP 请求方法

ASP.NET Core 支持下图七个 HTTP 请求方法特性

特性 说明
HttpGet 标识某个方法只支持 HTTP GET 请求方法
HttpPut 标识某个方法只支持 HTTP PUT 请求方法
HttpHead 标识某个方法只支持 HTTP HEAD 请求方法
HttpPost 标识某个方法只支持 HTTP POST 请求方法
HttpPatch 标识某个方法只支持 HTTP PATCH 请求方法
HttpDelete 标识某个方法只支持 HTTP DELETE 请求方法
HttpOptions 标识某个方法只支持 HTTP OPTIONS 请求方法

关于这些 HTTP 请求方法的介绍,可以访问我们的 HTTP 基础教程:HTTP 请求方法

第二个新面孔就是方法的返回值 IActionResult ,我们在 ASP.NET Core 动作结果 有提到,IActionResult 是所有返回结果的必须实现的接口,用这个作为返回值,也就是说该方法可以接受任意返回值。

第三个新面孔就是下面这段代码

if ( employee == null ) {
    return RedirectToAction("Index");
}

意思是是如果 employee 的结果为空,也就是没有在数据库里找到 id 对应的记录,则重定向到 Index 方法,从某些方面说也就是重定向到首页

RedirectTo 家族非常庞大,数量之多,几乎涵盖了各种情况下的重定向

好了,重启我们的应用程序,然后刷新浏览器,自然,肯定是出错了,因为缺少对应的视图

那么,我们就在 Views/Home 下添加一个视图 Edit.cshtml 吧,添加方法和之前添加 Index.cshtmlDetail.cshtml 一样,我们就不做介绍了,然后输入以下内容

@model Employee 
@{ 
   ViewBag.Title = $"编辑 {Model.Name}"; 
} 
<h1>Edit @Model.Name</h1>  

<form asp-action="Edit" method="post"> 
   <div> 
      <label asp-for="Name">员工姓名</label> 
      <input asp-for="Name" /> 
      <span asp-validation-for = "Name"></span> 
   </div> 

   <div> 
      <input type = "submit" value = "保存" /> 
   </div> 
</form>

我们来解释下下面这段代码

保存我们的 Edit.cshtml,刷新浏览器,还是报错了。这次的错误是提示 Employee 类没有找到

修改 _ViewImports.cshtml 添加 @using HelloWorld.Models,添加完成后 _ViewImports.cshtml 的内容如下

@namespace HelloWorld.Views
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using HelloWorld.Controllers
@using HelloWorld.Models

保存,然后刷新浏览器,这回显示正确了

当然了,不管我们是否编辑了员工姓名,如果点击保存,还是会报错,提示 404 不存在

这是因为我们刚刚添加的方法只适用于 HTTP GET 请求方法

我们需要再重载一个 Edit 方法用于接受 HTTP POST 请求

[HttpPost]
public IActionResult Edit(int id, EmployeeEditViewModel input)
{
    SQLEmployeeData sqlData = new SQLEmployeeData( _context );
    var employee = sqlData.Get(id);

    if (employee != null && ModelState.IsValid)
    {
        employee.Name = input.Name;
        _context.SaveChanges();
        return RedirectToAction("Detail", new { id = employee.ID });
    }
    return View(employee);
}

根据我们的路由规则,编辑表单应始终使用包含了 id 的 URL 来传递,如 /home/edit/1, 而显示表单和提交表单数据的最大差别就是 HTTP 请求方法,提交表单使用 [HttpPost] 特性

ASP.NET Core MVC 框架可以从 URL 中提起 id 数据并作为参数传递给动作方法

至于表单传递的其它数据,比如员工姓名,则使用另一个模型来接受不了,这个模型就是 EmployeeEditViewModel

EmployeeEditViewModel

EmployeeEditViewModel 用来从 HTTP POST 请求方法中接受传递的表单数据

EmployeeEditViewModel 到底长啥样呢,其实它跟 HomePageViewModel 差不多

我们再添加一下代码到 HomePageViewModel 后面

public class EmployeeEditViewModel {
  [Required, MaxLength(80)]
  public string Name { get; set; }
}

HomePageViewModel 包含了一个属性 Name,注意,Name 的名称和大小写必须和表单一模一样。

然后使用 C# 特性来限制 Name 字段必须输入数据 ( Required ),且数据的最大长度不得超过 80 ( MaxLength(80) )

因为表单只有一个字段 Name ,所以模型也就只有一个属性 Name,很简单,我们就不多做阐述了。

最后,使用 Required 等注解需要引入命名空间 System.ComponentModel.DataAnnotations `

ASP.NET Core MVC 参数模型绑定

回到 public IActionResult Edit(int id, EmployeeEditViewModel input) 方法,相比你也很好奇,ASP.NET Core MVC 是怎么给我们的 Edit 的两个参数传递实际的值的

我查阅了很多文档,原来 ASP.NET Core 使用了一种叫 模型绑定 的方法

模型绑定 是将 HTTP 请求的各种数据映射到动作方法参数的一种方法,参数即可以是简单类型,如字符串、整数或浮点数,也可以是复杂类型,如一个类。

模型绑定 查找数据源的顺序是

  1. Form values: 通过 HTTP POST 方法提交的数据,例如 Name
  2. Route values: 路由器解析的参数,例如 id
  3. Query String: URI 中的查询字符串部分的数据,也就是 ? 后面那一串

而模型绑定按照上面的顺序,只要找到了数据,就会停止继续查找。假设我们我们的 URI 为 /home/edit/1 ,而我们的表单有一个字段 id 值为 2 ,那么 Edit 方法的值为 2 而不是一,这是因为已经在 Form 表单中找到了 id 值,所以就不会继续查找了

对于复杂的数据,比如 HomePageViewModel,它的属性也是有要求的

  1. 要求接收数据的属性必须是 public 的,如果不是,那么就没法注入值
  2. 要求必须有一个无参数的构造函数

当发生绑定时,类只会使用公共默认构造函数来实例化,然后在设置属性值。

ModelState

当一个参数绑定了数据后,模型绑定就会停止查找该名称的值,并继续绑定下一个参数。

如果某个参数绑定失败,那么 MVC 框架也不会抛出任何错误,而是设置 ModelState 的属性为 false,仅此而已

因此,我们可以通过检查 ModelState 的值来检查数据格式是否正确

_context.SaveChanges();

_context.SaveChanges(); 用于保存验证后的数据到数据库

其实 模型绑定 还有很多内容,但是限于篇幅,我们就不再继续了,如果你想深入阅读,可以查阅 ASP.NET 官方文档

运行范例

我们回到 HomeController.cs,当所有的修改完成后,完整的代码如下

HomeController

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;

using HelloWorld.Models;

namespace HelloWorld.Controllers
{
    public class HomeController: Controller
    {

        private readonly HelloWorldDBContext _context;

        public HomeController(HelloWorldDBContext context)
        {
            _context = context;
        }

        public ViewResult Index()
        {

            var model = new HomePageViewModel();
            SQLEmployeeData sqlData = new SQLEmployeeData(_context);
            model.Employees = sqlData.GetAll();

            return View(model);
        }

        public ViewResult Detail(int id)
        {

            var model = new HomePageViewModel();
            SQLEmployeeData sqlData = new SQLEmployeeData(_context);

            Employee employee = sqlData.Get(id);

            return View(employee);
        }

        [HttpGet]
        public IActionResult Edit(int id)
        {

            var model = new HomePageViewModel();
            SQLEmployeeData sqlData = new SQLEmployeeData(_context);

            Employee employee = sqlData.Get(id);

            if ( employee == null ) {
                return RedirectToAction("Index");
            }

            return View(employee);
        }

        [HttpPost]
        public IActionResult Edit(int id, EmployeeEditViewModel input)
        {
            SQLEmployeeData sqlData = new SQLEmployeeData( _context );
            var employee = sqlData.Get(id);

            if (employee != null && ModelState.IsValid)
            {
                employee.Name = input.Name;
                _context.SaveChanges();
                return RedirectToAction("Detail", new { id = employee.ID });
            }
            return View(employee);
        }

    }  

    public class SQLEmployeeData 
    { 
        private HelloWorldDBContext _context { get; set; }

        public SQLEmployeeData(HelloWorldDBContext context)
        { 
            _context = context;
        } 

        public void Add(Employee emp)
        { 
            _context.Add(emp); 
            _context.SaveChanges(); 
        } 

        public Employee Get(int ID)
        { 
            return _context.Employees.FirstOrDefault(e => e.ID == ID); 
        } 

        public IEnumerable<Employee> GetAll() 
        { 
            return _context.Employees.ToList<Employee>(); 
        } 
    } 

    public class HomePageViewModel
    { 
        public IEnumerable<Employee> Employees { get; set; } 
    } 
} 

保存 HomeController.cs,重启应用程序,然后刷新浏览器,结果如下

点击 ID1编辑,显示如下

我们将 李白 改成 李太白

点击保存

然后点击返回首页

ASP.NET Core MVC 的基础教程终于学习完了

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.