9:输入Model和防止重复Post
♣ 视频地址:https://www.bilibili.com/video/av38392956/?p=8
这节讲输入Model,通过Action传入一个Model,传入进来的Model通常是用来建立数据,或者是修改数据
想要从客户端传进来Model,那么用户就需要输入Model及Model信息,在Html里输入Model信息通常是使用Form,所有我们想输入的信息肯定得有一个页面并且页面里有Form,所以首先我们需要个方法导航到这个Form页面
我们可以通过HttpGet请求进入某个Action,然后Action返回含有Form的View就可以了
那么Form里面的栏位是如何对应Model的各个字段呢,就是通过name属性来对应上的
• <input type="text" name="FirstName">
那么对应上之后是怎么传递呢?用http post来做操作
• HTTP POST
下面我们做跳转含有Form的页面,首先在Index.cshtml页加上一个跳转
<div> <a asp-action="Create">添加一个学生</a> </div>
然后在HomeController里添加Action
public IActionResult Create() { return View(); }
接着我们添加Create视图
@using Tutorial.Web.Model @model StudentCreateViewModel <h1>创建学生</h1> <form action="/" method="post"> <input asp-for="FirstName" /><br /><br /> <input asp-for="LastName" /><br /><br /> <input asp-for="BirthDate" type="date" /><br /><br /> <select asp-for="Gender" asp-items="Html.GetEnumSelectList<Gender>()"> </select> <br /><br /> <button type="submit" name="save">保存</button> </form>
上面有个Gender类,是我从Model里新加的一个枚举
namespace Tutorial.Web.Model { public enum Gender { 男 = 1, 女 = 0, 其他 = 2 } }
我们运行,并且看下它生成的Html代码,可以看到有一个input,它的类型是hidden,它的value是一串token,我们看它的name是“RequestVerificationToken”,它是用来验证这个请求的,用来防止CSRF(跨站请求伪造)
通过这个token,我们可以保证提交进来的Form是从我的网站的这个页面提交的,而不是一些恶意网站
下面我们研究如何把Form里的栏位转化成一个Model,实际这个不是我们手动来做的,而是MVC框架自动去做的
我们Form表单的action的地址就是本身进来的地址/Home/Create,这个地址是HttpGet请求,但是提交之后走的是post请求,HttpPost,所以我们回到HomeController,继续建立一个名为Create,但是Method为HttpPost,这里我们新建了一个ViewModel,就是StudentCreateViewModel,为什么不用Student类呢?
因为我们要尽可能的保证Form里传递的参数和我们接收类型所有的参数要一直,Student有个Id属性,而Form表单里没有,不一致,所以我们新建一个StudentCreateViewModel来对应Form表单里的各项
[HttpPost] public IActionResult Create(StudentCreateViewModel student) { var newstudent = new Student { FirstName = student.FirstName, LastName = student.LastName, BirthDate = student.BirthDate, Gender = student.Gender }; var newModel = _repository.Add(newstudent); return View("Detail", newModel); }
然后我们在接口里添加,如下图
接着我们去服务类InMemoryRepository去实现这个接口
public Student Add(Student newModel) { var maxId = _students.Max(x => x.Id); newModel.Id = maxId + 1; _students.Add(newModel); return newModel; }
现在呢,我们来完善下Detail这个视图
<html> <head> <meta name="viewport" content="width=device-width" /> <title>Detail</title> </head> <body> <div>学生信息</div> <div>ID:@Model.Id</div> <div> 姓名:@Model.FirstName @Model.LastName </div> <div> 性别:@Model.Gender </div> <div> 出生日期:@Model.BirthDate.ToString("yyyy-MM-dd") </div> <a asp-action="Index">返回Home</a> </body> </html>
这时候我们就运行,添加会发现在明细页可以展示新添的信息,如下图左,但是点击返回Home的时候,数据却没加载出来,如下图右
那为什么新添的数据没有?这是因为我们注册容器InMemoryRepository的时候选择它的声明周期是Scoped,就是每一次Http请求都会创建一个新的实例,所以在跳回Home页面的时候,又变成了新的请求,那解决办法就是我们把它改成Singleton即可
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IWelcomeService, WelcomeService>(); services.AddSingleton<IRepository<Student>, InMemoryRepository>(); }
这时候我们再运行看下结果,发现添加进来了,如下图
这时候还会有个问题,就是我们再添加后的页面F5刷新,就会提示重新提交表单,记住这个地址/Home/Create
这时候我们一直点继续,就会一直提交,就会出现下面的场景,如图
那接下来我们就来解决,或者避免这个问题
POST-REDIRECT-GET
我们post之后,重定向REDIRECT新的页面,然后在新的页面获取GET数据,我们改下代码,如下图
这时候我们添加一个人,可以看到地址变成了/Home/Detail/4,而不是上面之前那个/Home/Create了,这时候我们刷新页面也不会重新提交表单了
然后返回Home,可以看到数据都正确,如下图