ASP.NET Core Razor Pages 初探
最近新建 http:// Asp.net Core MVC 项目的时候不小心选错了个模板,发现了一种新的项目模板。它使用cshtml视图模板,但是没有Controller文件夹。后来才发现这是 http:// 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/[email protected]"
>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/[email protected]"
>Delete</a>
上面的写法完全可以工作,并且更加清晰明了,谁看了都知道是啥意思。
小小的吐槽下微软:像asp-page这种封装我是不太喜欢的,因为它掩盖了html、http工作的本质原理。这样会造成很多同学知道使用asp-page怎么写,但是换个框架就不知道怎么搞了。我见过号称精通
http://
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模式相比并未有什么特殊的优点,不知道后续发展会如何。
关注我的公众号一起玩转技术