什么是Razor Pages?
Razor pages是ASP.NET Core 2.0的新特性,它被设计用来更快的开发页面,比传统的MVC模式更便捷。
创建项目
为了使用Razor Pages,你须在安装Core 2.0。
在VS中,你可以通过选择File -> New Project,然后选择ASP.NET Core Web Application来创建一个新的Core Razor Pages项目:
现在,选择Web Application点击OK创建项目:
VS创建的Razor Page项目和其他的Core web项目的结构非常类似,不同的是多了一个Pages文件夹:
初始化项目的一些代码
public class Employee
{
[DisplayName("First Name:")]
[Required(ErrorMessage = "Please enter the employee's first name")]
public string FirstName { get; set; }
[DisplayName("Last Name:")]
[Required(ErrorMessage = "Please enter the employee's last name")]
public string LastName { get; set; }
[DisplayName("Date of Birth:")]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
[DisplayName("ID:")]
[Required(ErrorMessage = "Please enter the employee's ID")]
public int ID { get; set; }
}
public interface IEmployeeRepository
{
Employee GetByID(int id);
List<Employee> GetAll();
}
public class EmployeeRepository : IEmployeeRepository
{
public List<Employee> GetAll()
{
List<Employee> employees = new List<Employee>();
employees.Add(GetByID(1));
employees.Add(GetByID(2));
employees.Add(GetByID(3));
return employees;
}
public Employee GetByID(int id)
{
if(id == 1)
{
return new Employee()
{
FirstName = "Spencer",
LastName = "Strasmore",
DateOfBirth = new DateTime(1978, 11, 16),
ID = 1
};
}
if(id == 2)
{
return new Employee()
{
FirstName = "Ricky",
LastName = "Jerret",
DateOfBirth = new DateTime(1989, 3, 30),
ID = 2
};
}
if(id == 3)
{
return new Employee()
{
FirstName = "Vernon",
LastName = "Littlefield",
DateOfBirth = new DateTime(1992, 7, 3),
ID = 3
};
}
return null;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IEmployeeRepository, EmployeeRepository>();
...
}
新建首页
现在我们准备添加一个Razor Page用来展现employee列表。首先在Pages文件夹下面创建一个Employees文件夹:
右键Employees文件夹选择Add -> New Item弹出如下窗口:
选择Razor Page,命名为Index,点击OK。VS将创建一个index.cshtml文件和一个index.cshtml.cs文件:
现在,我们来看看index.cshtml文件默认的内容:
@page
@model IndexModel
@{
}
@page
指令表示这个是一个Razor Page,@model
表示这个页面的模型是一个IndexModel
的实例。
但是IndexModel
在哪呢?它在Index.cshmtl.cs文件里面:
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
注意OnGet()
方法。这个是Razor Page Get页面的一个标准的方法。
怎么在这个页面显示所有的employee呢?
展示Employee
首先,修改IndexModel
,添加一个Employee集合的属性,修改OnGet()
方法,以此来从数据源中加载所有的Employee。
public class IndexModel : PageModel
{
private IEmployeeRepository _employeeRepo;
public List<Employee> Employees { get; set; }
public IndexModel(IEmployeeRepository userRepo)
{
_employeeRepo = userRepo;
}
public void OnGet()
{
Employees = _employeeRepo.GetAll();
}
}
修改Index.cshtml文件来展示employee:
@page
@using ASPNETCoreRazorPagesDemo.Pages.Employees
@model IndexModel
<h2>Employees</h2>
<a asp-page="Add">Add an Employee</a>
<h3>@Model.Message</h3>
<table>
<thead>
<tr>
<th>
Name
</th>
<th>
Date of Birth
</th>
<th>
Employee ID
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach(var employee in Model.Employees)
{
<tr>
<td>
@(employee.FirstName + " " + employee.LastName)
</td>
<td>
@employee.DateOfBirth.ToShortDateString()
</td>
<td>
@employee.ID
</td>
<td>
<a asp-page="Edit" asp-route-id="@employee.ID">Edit</a>
</td>
</tr>
}
</tbody>
</table>
创建一个新增Employee的页面
在Employees文件夹中创建一个名为Add的Razor Page文件。
修改这个cshtml文件,添加一个Add表单:
<h2>Add an Employee</h2>
<a asp-page="Index">Back to Employees</a>
<form method="post">
<div>
<div>
<label asp-for="Employee.FirstName"></label>
<input type="text" asp-for="Employee.FirstName" />
<span asp-validation-for="Employee.FirstName"></span>
</div>
<div>
<label asp-for="Employee.LastName"></label>
<input type="text" asp-for="Employee.LastName" />
<span asp-validation-for="Employee.LastName"></span>
</div>
<div>
<label asp-for="Employee.DateOfBirth"></label>
<input type="text" asp-for="Employee.DateOfBirth" />
<span asp-validation-for="Employee.DateOfBirth"></span>
</div>
<div>
<label asp-for="Employee.ID"></label>
<input type="text" asp-for="Employee.ID" />
<span asp-validation-for="Employee.ID"></span>
</div>
</div>
<div>
<input type="submit" value="Add as Manager" asp-page-handler="JoinManager" />
<input type="submit" value="Add as Employee" asp-page-handler="JoinEmployee"/>
</div>
</form>
注意我们有两个不同的提交按钮。每个按钮都有一个asp-page-handler
属性,并都有一个唯一的值。
在这个演示中我们不需要code-behind类,因此我们删除了Add.cshtml.cs
文件,直接在Add.cshtml
文件中引入了@function
指令。我们将使用OnPostJoinManageAsync()
和OnPostJoinEmployeeAsync()
方法。
为了在Razor Page页面包含C#模型,得先引入@function
指令:
@functions
{
[TempData]
public string Message { get; set; }
[BindProperty]
public Employee Employee { get; set; }
public async Task<IActionResult> OnPostJoinManagerAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Message = Employee.FirstName + " " + Employee.LastName + " (ID " + Employee.ID.ToString() + ") would have been added as a Manager.";
return RedirectToPage("Index");
}
public async Task<IActionResult> OnPostJoinEmployeeAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Message = Employee.FirstName + " " + Employee.LastName + " (ID " + Employee.ID.ToString() + ") would have been added as an Employee.";
return RedirectToPage("Index");
}
}
会自动检查ModelState
来验证输入是否合法。这两个OnPostXXXAsync()方法返回的是Page()
和RedirectToPage("index")
,这两个都是在2.0中才有的。
最后请注意asp-page-hanlder
和OnPostJoinManagerAsync()
OnPostJoinEmployeeAsync()
方法的对应关系。
创建编辑页面
如上一样创建一个名为Edit的Razor Page,和Add页面不同的是,这次我们保留了Edit.cshtml.cs文件。
在这个文件中,我们需要修改模型,从数据源中加载Emploee:
public class EditModel : PageModel
{
private IEmployeeRepository _employeeRepo;
public EditModel(IEmployeeRepository employeeRepo)
{
_employeeRepo = employeeRepo;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Employee Employee { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Employee = _employeeRepo.GetByID(id);
if(Employee == null)
{
return RedirectToPage("Index");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if(!ModelState.IsValid)
{
return Page();
}
Message = Employee.FirstName + " " + Employee.LastName + ", ID " + Employee.ID.ToString() + " would have been saved.";
return RedirectToPage("Index");
}
}
.cshtml文件的内容如下:
@page "{id:int}"
@model ASPNETCoreRazorPagesDemo.Pages.Employees.EditModel
<h2>Add an Employee</h2>
<a asp-page="Index">Back to Employees</a>
<form method="post">
<div>
<div>
<label asp-for="Employee.FirstName"></label>
<input type="text" asp-for="Employee.FirstName" />
<span asp-validation-for="Employee.FirstName"></span>
</div>
<div>
<label asp-for="Employee.LastName"></label>
<input type="text" asp-for="Employee.LastName" />
<span asp-validation-for="Employee.LastName"></span>
</div>
<div>
<label asp-for="Employee.DateOfBirth"></label>
<input type="text" asp-for="Employee.DateOfBirth" />
<span asp-validation-for="Employee.DateOfBirth"></span>
</div>
<div>
<label asp-for="Employee.ID"></label>
<input type="text" asp-for="Employee.ID" />
<span asp-validation-for="Employee.ID"></span>
</div>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
路由
首先,如果你想要在路由上包含一些数据,你需要修改.cshtml文件的@page
指令。在我们的Edit页面是这样的:
@page "{id:int}"
@page
后面的字符串是路由的一部分。因为Edit页是Employees/Edit,因此完整的路由应该是/Employees/Edit/2。
接下来看看RedirectToPage()
是怎么工作的,先看看Add页面:
return RedirectToPage("Index");
如果在文件夹中存在Index文件,那么用户会被重定向到这个页面,否则会抛出一个异常。如果一个路由映射到了两个不同的页面, Core会抛出AmbiguousActionException
。
Razor Page的特性
- Anti-forgery会自动包含在每个Razor Page页面。
- 不在需要ViewModels了,因为我们有了
PageModel
。
Razor Page的内聚性比较好,因为所有和一个页面相关的数据都在和这个页面相关的一个独立的文件里面。 (不在需要在搞一个Controller, ViewModel了)。