最近新建 Asp.net Core MVC 项目的时候不小心选错了个模板,发现了一种新的项目模板。它使用cshtml视图模板,但是没有Controller文件夹。后来才发现这是ASP.NET Core框架新推出的Razor Pages技术。

什么是Razor Pages

“Razor Pages 使编码更加简单更加富有生产力”这是微软说的==!。Razor Pages 简化了传统的mvc模式,仅仅使用视图跟模型来完成网页的渲染跟业务逻辑的处理。模型里包含了数据跟方法,通过绑定技术跟视图建立联系,这就有点像服务端的绑定技术。下面使用一个标准的CRUD示例来演示Razor Pages的开发,并且简单的探索一下它是如何工作的。

新建Razor Pages项目

在visual studio中新建Razor Pages项目。



项目结构



新建项目的目录结构比MVC项目简单。它没有Controllers目录,Pages有点像MVC项目的Views目录,里面存放了cshtml模板。随便点开一个cshtml文件,发现它都包含了一个cs文件。这是跟MVC项目最大的不同,这个结构让人回忆起那古老的WebForm技术,o(╥﹏╥)o 。

新建Razor Page

我们模拟开发一个学生管理系统。一共包含4个页面:列表页面、新增页面、修改页面、删除页面。首先我们新建一个列表页面。

在Pages目录下面新建Student目录。在Student目录下新建4个Razor page名叫:List、Add、Update、Delete。



建好后目录结构是这样:

模拟数据访问仓储

由于这是个演示项目,所以我们使用静态变量来简单模拟下数据持久。

在项目下新建一个Data目录,在目录下新建Student实体类:

    public class Student
{
public int Id { get; set; }
public string Name { get; set; } public string Class { get; set; } public int Age { get; set; } public string Sex { get; set; }
}

在Data目录下新建IStudentRepository跟StudentRepository类:

    public interface IStudentRepository
{
List<Student> List(); Student Get(int id); bool Add(Student student); bool Update(Student student); bool Delete(int id);
} public class StudentRepository : IStudentRepository
{
private static List<Student> Students = new List<Student> {
new Student{ Id=1, Name="小红", Age=10, Class="1班", Sex="女"},
new Student{ Id=2, Name="小明", Age=11, Class="2班", Sex="男"},
new Student{ Id=3, Name="小强", Age=12, Class="3班", Sex="男"}
}; public bool Add(Student student)
{
Students.Add(student); return true;
} public bool Delete(int id)
{
var stu = Students.FirstOrDefault(s => s.Id == id);
if (stu != null)
{
Students.Remove(stu);
} return true;
} public Student Get(int id)
{
return Students.FirstOrDefault(s=>s.Id == id);
} public List<Student> List()
{
return Students;
} public bool Update(Student student)
{
var stu = Students.FirstOrDefault(s=>s.Id == student.Id);
if (stu != null)
{
Students.Remove(stu);
} Students.Add(student);
return true;
}
}

我们新建了一个IRepository接口,里面有几个基本的crud的方法。然后新建一个实现类,并且使用静态变量保存数据,模拟数据持久化。

当然还得在DI容器中注册一下:

  public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
//注册repository
services.AddScoped<IStudentRepository, StudentRepository>();
}

实现列表(student/list)页面

列表页面用来展现所有的学生信息。

修改ListModel类:

    public class ListModel : PageModel
{
private readonly IStudentRepository _studentRepository;
public List<Student> Students { get; set; }
public ListModel(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
} public void OnGet()
{
Students = _studentRepository.List();
}
}

修改List.cshtml模板:

@page
@model RazorPageCRUD.ListModel
@{
ViewData["Title"] = "List";
} <h1>List</h1> <p>
<a class="btn btn-primary" asp-page="Add">Add</a>
</p>
<table class="table">
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
<th>Class</th>
<th>Sex</th>
<th></th>
</tr>
@foreach (var student in Model.Students)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
<td>@student.Age</td>
<td>@student.Class</td>
<td>@student.Sex</td>
<td>
<a class="btn btn-primary" asp-page="Update" asp-route-id="@student.Id">Update</a>
<a class="btn btn-danger" href="/student/delete?id=@student.Id" >Delete</a>
</td>
</tr>
} </table>

ListModel类混合了MVC的Controller跟Model的概念。它本身可以认为是MVC里面的那个Model,它包含的数据可以被razor试图引擎使用,用来生成html,比如它的Students属性;但是它又包含方法,可以用来处理业务逻辑,这个方法可以认为是Controller中的Action。方法通过特殊的前缀来跟前端的请求做绑定,比如OnGet方法就是对Get请求作出响应,OnPost则是对Post请求作出响应。

运行一下并且访问/student/list:



列表页面可以正常运行了。

使用asp-page进行页面间导航

列表页面上有几个按钮,比如新增、删除等,点击的时候希望跳转至不同的页面,可以使用asp-page属性来实现。asp-page属性不是html自带的属性,显然这是Razor Pages为我们提供的。

<p>
<a class="btn btn-primary" asp-page="Add">Add</a>
</p>

上面的代码在a元素上添加了asp-page="Add",表示点击这个a连接会跳转至同级目录的Add页面。html页面之间的导航不管框架怎么封装无非就是url之间的跳转。显然这里asp-page最后会翻译成一个url,看看生成的页面源码:

<a class="btn btn-primary" href="/Student/Add">Add</a>

跟我们想的一样,最后asp-page被翻译成了href="/Student/Add"。

使用asp-route-xxx进行传参

页面间光导航还不够,更多的时候我们还需要进行页面间的传参。比如我们的更新按钮,需要跳转至Update页面并且传递一个id过去。

<a class="btn btn-primary" asp-page="Update" asp-route-id="@student.Id">Update</a>

我们使用asp-route-id来进行传参。像这里的a元素进行传参,无非是放到url的querystring上。让我们看一下生成的html源码:

<a class="btn btn-primary" href="/Student/Update?id=2">Update</a>

不出所料最后id作为queryString被组装到了url上。

上面演示了Razor Pages的导航跟传参,使用了几个框架内置的属性,但其实我们根本可以不用这些东西就可以完成,使用标准的html方式来完成,比如删除按钮:

<a class="btn btn-danger" href="/student/delete?id=@student.Id" >Delete</a>

上面的写法完全可以工作,并且更加清晰明了,谁看了都知道是啥意思。

小小的吐槽下微软:像asp-page这种封装我是不太喜欢的,因为它掩盖了html、http工作的本质原理。这样会造成很多同学知道使用asp-page怎么写,但是换个框架就不知道怎么搞了。我见过号称精通asp.net的同学,但是对html、特别是对http一无所知。当你了解了真相后,甭管你用什么技术,看起来其实都是一样的,都是套路。

实现新增(student/add)页面

新增页面提供几个输入框输入学生信息,并且可以提交到后台。

修改AddModel类:

   public class AddModel : PageModel
{
private readonly IStudentRepository _studentRepository;
public AddModel(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public void OnGet()
{
} [BindProperty]
public Student Student { get; set; } public IActionResult OnPostSave()
{
_studentRepository.Add(Student);
return RedirectToPage("List");
}
}

修改Add.cshtml页面

@page
@model RazorPageCRUD.AddModel
@{
ViewData["Title"] = "Add";
} <h1>Add</h1> <form method="post">
<div class="form-group">
<label>Id</label>
<input type="number" asp-for="Student.Id" class="form-control" />
</div>
<div class="form-group">
<label>Name</label>
<input type="text" asp-for="Student.Name" class="form-control" />
</div>
<div class="form-group">
<label>Age</label>
<input type="number" asp-for="Student.Age" class="form-control" />
</div>
<div class="form-group">
<label>Class</label>
<input type="text" asp-for="Student.Class" class="form-control" />
</div>
<div class="form-group">
<label>Sex</label>
<input type="text" asp-for="Student.Sex" class="form-control" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" asp-page-handler="Save">Save</button>
<a asp-page="list" class="btn btn-dark">Cancel</a>
</div>
</form>

Add页面使用一个form表单作为容器,里面的文本框使用asp-for跟Model的Student属性建立联系。

运行一下:



asp-for会把关联的属性字段的值作为input元素的value的值,会把关联的属性名+字段的名称作为input元素的name属性的值。看看生成的html源码:

<input type="text" class="form-control" id="Student_Name" name="Student.Name" value="">

使用asp-page-handler来映射模型方法

我们的Save是一次POST提交,显然我们需要一个后台方法来接受这次请求并处理它。使用asp-page-handler="Save"可以跟模型的OnPostSave方法做映射。OnPost前缀表示对POST请求做响应,这又有点像webapi。那么asp-page-handler为什么能映射模型的方法呢?继续看看生成的源码:

<button type="submit" class="btn btn-primary" formaction="/Student/Add?handler=Save">Save</button>

看到这里就明白了。最后生成的button上有个formaction属性,值为/Student/Add?handler=Save。formaction相当于在form元素上指定action属性的提交地址,并且在url上附带了一个参数handler=save,这样后台就能查找具体要执行哪个方法了。不过据我的经验formaction属性存在浏览器兼容问题。

使用BindPropertyAttribute进行参数绑定

光能映射后台方法还不够,我们还需要把前端的数据提交到后台,并且拿到它。这里可以使用BindPropertyAttribute来自动完成提交的表单数据跟模型属性之间的映射。这样我们的方法可以是无参的方法。


[BindProperty]
public Student Student { get; set; }

看到这里突然有种MVVM模式的既视感了。虽然不是实时的双向绑定,但是也实现了简单的前后端绑定技术。另外提一句既然我们前端的数据是通过表单提交,那么跟mvc一样,使用FromFormAttribute其实一样可以进行参数绑定的。

public IActionResult OnPostSave([FromForm] Stuend student)

这有获取表单数据毫无问题。

在后台方法进行页面导航

当保存成功后需要使页面跳转到列表页面,可以使用RedirectToPage等方法进行跳转,OnPostSave方法的返回值类型也改成IActionResult,这就非常mvc了,跟action方法一模一样的套路。

public IActionResult OnPostSave()
{
_studentRepository.Add(Student);
return RedirectToPage("List");
}

修改编辑(student/update)页面

修改,删除页面就没什么好多讲的了,使用前面的知识点轻松就能实现。

修改cshtml模板:

@page
@model RazorPageCRUD.UpdateModel
@{
ViewData["Title"] = "Update";
} <h1>Update</h1> <form method="post">
<div class="form-group">
<label>Id</label>
<input type="number" asp-for="Student.Id" class="form-control" />
</div>
<div class="form-group">
<label>Name</label>
<input type="text" asp-for="Student.Name" class="form-control" />
</div>
<div class="form-group">
<label>Age</label>
<input type="number" asp-for="Student.Age" class="form-control" />
</div>
<div class="form-group">
<label>Class</label>
<input type="text" asp-for="Student.Class" class="form-control" />
</div>
<div class="form-group">
<label>Sex</label>
<input type="text" asp-for="Student.Sex" class="form-control" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" asp-page-handler="Edit">Save</button>
<a asp-page="list" class="btn btn-dark">Cancel</a>
</div>
</form>

修改UpdateModel类:

    public class UpdateModel : PageModel
{
private readonly IStudentRepository _studentRepository;
public UpdateModel(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public void OnGet(int id)
{
Student = _studentRepository.Get(id);
} [BindProperty]
public Student Student { get; set; } public IActionResult OnPostEdit()
{
_studentRepository.Update(Student); return RedirectToPage("List");
}
}

运行一下:

修改删除(student/delete)页面

删除页面跟前面一样没什么好多讲的了,使用前面的知识点轻松就能实现。

修改Delete.cshtml模板:

@page
@model RazorPageCRUD.DeleteModel
@{
ViewData["Title"] = "Delete";
} <h1>Delete</h1>
<h2 class="text-danger">
确定删除?
</h2>
<form method="post">
<div class="form-group">
Id: @Model.Student.Id
</div>
<div class="form-group">
Name:@Model.Student.Name
</div>
<div class="form-group">
Age: @Model.Student.Age
</div>
<div class="form-group">
Class: @Model.Student.Class
</div>
<div class="form-group">
Sex: @Model.Student.Sex
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" asp-page-handler="Delete" asp-route-id="@Model.Student.Id">Delete</button>
<a asp-page="list" class="btn btn-dark">Cancel</a>
</div>
</form>

修改DeleteModel类:

     public class DeleteModel : PageModel
{
private readonly IStudentRepository _studentRepository;
public DeleteModel(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
} public void OnGet(int id)
{
Student = _studentRepository.Get(id);
} public Student Student { get; set; } public IActionResult OnPostDelete(int id)
{
_studentRepository.Delete(id); return RedirectToPage("list");
}
}

运行一下:

总结

通过上的简单示例,对Razor Pages有了大概的了解。Razor Pages本质上对MVC模式的简化,后台模型聚合了Controller跟Model的的概念。并且提供了一些内置html属性实现绑定技术。有人说Razor Pages是WebForm的继任者,我倒不觉得。个人觉得它更像是MVC/MVVM的一种混合。[BindProperty]有点像WPF里的依赖属性,OnPostXXX方法就像是Command命令;又或者[BindProperty]像VUE的Data属性上的字段,OnPostXXX像Methods里的方法;又或者整个Model像极了angularjs的$scope,混合了数据跟方法。只是Razor Pages毕竟是服务端渲染,不能进行实时双向绑定而已。最后,说实话通过简单的体验,Razor Pages开发模式跟MVC模式相比并未有什么特殊的优点,不知道后续发展会如何。

ASP.NET Core Razor Pages 初探的更多相关文章

  1. ASP.NET Core Razor Pages

    Razor 页面是Asp.Net Core2.0新增的一个功能.Razor 页面是 ASP.NET Core MVC 的一个新特性,它可以使基于页面的编码方式更简单高效. 环境:vs2017 .net ...

  2. ASP.NET Core - Razor页面之Handlers处理方法

    简介 在前一篇文章中,我们讨论了Razor页面.今天我们来谈谈处理方法(Handlers). 我们知道可以将代码和模型放在 .cshtml 文件里面或与 .cshtml 匹配的 .cshtml.cs ...

  3. ASP.NET Core:Pages

    ylbtech-ASP.NET Core:Pages 1.返回顶部 1._Layout.cshtm <!DOCTYPE html> <html> <head> &l ...

  4. .NET Core Razor Pages中ajax get和post的使用

    ASP.NET Core Razor Pages Web项目大部分情况下使用继承与PageModel中的方法直接调用就可以(asp-page),但是有些时候需要使用ajax调用,更方便些.那么如何使用 ...

  5. ASP.NET Core - Razor 页面简介

    简介 随着ASP.NET Core 2 即将来临,最热门的新事物是Razor页面.在之前的一篇文章中,我们简要介绍了ASP.NET Core Razor 页面. Razor页面是ASP.NET Cor ...

  6. ASP.NET Core Razor中处理Ajax请求

    如何ASP.NET Core Razor中处理Ajax请求 在ASP.NET Core Razor(以下简称Razor)刚出来的时候,看了一下官方的文档,一直没怎么用过.今天闲来无事,准备用Rozor ...

  7. 学习ASP.NET Core Razor 编程系列一

    一. 概述 .NET Core 1.0发布的时候就想进行学习的,不过根据微软的以往的发布规律1.0版可以认为是大众测试版,2.0才算稳定.现在2.1都已经发布了预览版,之前对其"不稳定&qu ...

  8. 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. coding++:java-全局异常处理

    本次使用工具:SpringBoot   <version>1.5.19.RELEASE</version> Code: AbstractException: package m ...

  2. Feign客户端实现RPC 调用

    1,springcloud 中支持http调用的两种方式,RestTemplate,Feign客户端 2,Feign 客户端是一个声明式(注解方式)http 远程调用工具 3,实现方式如下: 第一步: ...

  3. 在Centos7下搭建大数据环境,即Zookeeper+Hadoop+HBase

    1. 所需软件下载链接(建议直接复制链接到迅雷下载更快): ①hadoop-2.7.6.tar.gz: wget http://mirrors.tuna.tsinghua.edu.cn/apache/ ...

  4. [noip模拟赛]某种数列问题<dp>

    某种数列问题  (jx.cpp/c/pas) 1000MS 256MB 众所周知,chenzeyu97有无数的妹子(阿掉!>_<),而且他还有很多恶趣味的问题,继上次纠结于一排妹子的排法以 ...

  5. 听说你还搞不定java中的==和equals?

    相信很多读者关于==和equals懂了又懵,懵了又懂,如此循环,事实上可能是因为看到的博客文章之类的太多了,长篇大论,加上一段时间的洗礼之后就迷路了.本篇文章再一次理清楚.当然如果觉得本文太啰嗦的话, ...

  6. Python第十三章-网络编程

    网络编程 一.网络编程基础 python 的网络编程模块主要支持两种Internet协议: TCP 和 UDP. 1.1通信协议 通信协议也叫网络传输协议或简称为传送协议(Communications ...

  7. SpringBoot系列之学习教程汇总

    对应SpringBoot系列博客专栏,例子代码,本博客不定时更新 一.配置篇 SpringBoot系列之@PropertySource读取yaml文件     >> source down ...

  8. FileReader与URL.createObjectURL实现图片、视频上传前预览

    之前做图片.视频上传预览常用的方案是先把文件上传到服务器,等服务器返回文件的地址后,再把该地址字符串赋给img或video的src属性,这才实现所谓的文件预览.实际上这只是文件“上传后再预览”,这既浪 ...

  9. 基于.NetCore3.1搭建项目系列 —— 使用Swagger导出文档 (补充篇)

    前言 在上一篇导出文档番外篇中,我们已经熟悉了怎样根据json数据导出word的文档,生成接口文档,而在这一篇,将对上一篇进行完善补充,增加多种导出方式,实现更加完善的导出功能. 回顾 1. 获取Sw ...

  10. CF632(div.2)C. Eugene and an array

    https://codeforces.ml/contest/1333/problem/C 大概题意是规定和为0的数组为不合格数组,询问给定数组中共有多少个合格子数组. 解题 子数组的数量 一个长度为 ...