ASP.NET Core 2.0引入了一种新的方法来构建一个名为Razor Pages的网站。我非常喜欢Razor Pages,但我需要弄清楚如何做一些事情。在本文中,我将介绍如何处理AJAX请求。事实证明,有两种方法可以实现处理返回json或其他数据的HTTP请求的C#代码。
ASP.NET Core的Razor Pages通常用于向客户端呈现HTML页面,但有时候我们也可以使用Razor Pages处理AJAX请求,并向客户端提供数据。这可以通过两种方式完成:第一、通过一个页面的多个处理程序方式,第二、使用单个Razor Pages页面处理。
本文描述的两种方法,江湖人士认为第二种方法更好。本文还介绍了如何通过AJAX处理HTTP POST请求,因为需要在请求中添加防伪令牌。
Razor Pages的简要介绍
在ASP.NET Core和ASP.NET MVC中提供网页的经典方法是通过
controller->actions->views 方法。控制器包含许多处理URL特定部分的方法(称为 actions )。例如, /Orders/Index 将在名为Orders Controller 的控制器中运行名为Index的方法。
Razor页面提供了一种不同的,更简单、更好的方法。控制器已经消失,操作方法移动到目录中的每个URL类中。例如,使用Razor Pages, URL地址: /Orders/Index 将在名为Orders的目录中查找名为Index的Razor Pages视图。
/ Order / Index的Razor Pages视图包含两个文件:
- Order / Index.cshtml – 转换为HTML的Razor Pages视图(必须在那里)
- Order / Index.cshtml.cs – Razor Pages的PageModel,其中包含代码(可选),您可以理解为aspx页面的后置代码。
PageModel文件是可选的,但是如果你有任何数量的C#代码,那么这个文件就是放置它的地方。没有PageModel文件的唯一原因是如果您的页面是静态的(即没有动态数据),或者代码如此简单,您可以使用razor语法编写它。(Razor语法现在非常强大,有功能等,但我仍然认为PageModel文件是代码的正确位置,通常在Razor Pages出现之前将其写入控制器的操作方法)。
我真正喜欢Razor Pages的是他们遵循单一责任原则(SRP),因为这两个文件处理特定的URL而不是其他任何东西 – 它们非常专注并且符合SOLID 软件设计原则。
以下是在默认Razor Pages应用程序中实现“about”页面的两个示例文件。这显示了他们如何关注一件事 – 关于页面。
About.cshtml
@page
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@Model.Message</h3>
Use this area to provide additional information.
About.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPageApp.Pages
{
public class AboutModel : PageModel
{
public string Message { get; set; }
public void OnGet()
{
Message = "Your application description page.";
}
}
}
解决ajax请求的问题
在任何Web应用程序中,您必须遇到要填写或更改页面上的某些数据而不进行全部刷新的位置(请参阅此AJAX简介)。AJAX确实有助于创建一个快速,用户友好的Web界面。
下面示例应用是一个带过滤器的应用程序,用户可以选择他们想要如何过滤书籍列表。一旦用户选择了过滤器类型,我就使用AJAX请求将值放入“过滤条件”下拉列表中(由于用户尚未选择过滤器类型,因此下图中显示为灰色)。
我知道如何在经典的controller-> action-> view方法中执行此操作(向控制器添加另一个操作),但是如何使用Razor Pages?
本文介绍两种方式,我将描述两种方式(提示:但我更喜欢第二种方式!)。然后我将介绍处理使用HTTP POST的AJAX请求,因为还有额外的安全问题需要处理。
请注意,我的示例适用于AJAX,但它们也适用于为外部应用程序实现WebAPI。但是,带有swagger的ASP.NET Core很可能是全面开发WebAPI的更好解决方案。
Ajax处理方式1: 一个页面的多个处理程序
在典型的PageModel文件中,您已经命名了名为OnGet,OnPut,OnGetAsync等的方法。这些方法处理对Razor Page所在文件夹定义的URL的不同类型的HTTP请求,以及Razor Page的名称。但是有一种方法可以定义对此命名约定之外的方法的调用,称为一个页面多个处理程序。Microsoft提供的示例为特定页面提供了不同的选项,比如注册用户的两种不同方式。但你也可以用它来进行AJAX调用。
处理程序的默认格式是将“?handler = YourMethodName”添加到URL。所以我的第一个解决方案是改变我的JQuery ajax调用,如下所示:
$.ajax({
url: '/?handler=Filter',
data: {
FilterBy: filterByValue
}
})
.done(function(result) {
然后我将我的Index.cshtml.cs文件更改为如下所示 :
public class IndexModel : PageModel
{
private readonly IListBooksService _listService;
private readonly IBookFilterDropdownService _filterService;
public IndexModel(IListBooksService listService,
IBookFilterDropdownService filterService)
{
_listService = listService;
_filterService = filterService;
}
public SortFilterPageOptions SortFilterPageData { get; private set; }
public IEnumerable<BookListDto> BooksList { get; private set; }
public void OnGet(SortFilterPageOptions options)
{
BooksList = _listService
.SortFilterPage(options)
.ToList();
SortFilterPageData = options;
}
public JsonResult OnGetFilter(BooksFilterBy filterBy)
{
return new JsonResult(_filterService.GetFilterDropDownValues(filterBy));
}
}
最后四行(OnGetFilter方法)是值得注意的。这是名为Filter的命名页面处理程序,我正在接收HTTP GET请求(稍后我将讨论HTTP POST AJAX请求)。
以上代码是可以正常工作的,但它使的Index PageModel不遵循单一职责原则,因为它处理两种类型的请求。因此,我寻找了一个更加SOLID的设计,我将在下面介绍。
Ajax处理方式2:使用单个Razor Pages页面处理
使用单个Razor Pages呈现HTML时,它一样的可以处理任何ajax请求,并返回json或其他任何数据类型,这时我们可以建立专用的Razor Pages来处理ajax请求,这有点像10年前我们建立ashx一般处理程序来处理ajax请求,见下方代码:
Filter.cshtml
下面是一个Razor Pages的代码。
@page
@model RazorPageApp.Pages.FilterModel
@{
ViewData["Title"] = "Filter";
}
<h2>Filter</h2>
必须有一个.cshtml文件,因为这会设置路由。通过反复试验,我发现必须有前两行代码,其余的非必须,可以删除保持页面干净纯洁。
Filter.cshtml.cs
代码现在只包含过滤器代码,这更加清晰。
public class FilterModel : PageModel
{
private readonly IBookFilterDropdownService _filterService;
public FilterModel(IBookFilterDropdownService filterService)
{
_filterService = filterService;
}
public JsonResult OnGet(BooksFilterBy filterBy)
{
return new JsonResult(_filterService.GetFilterDropDownValues(filterBy));
}
}
ajax调用现在有一个’/ Filter’的URL,如下所示:
$.ajax({
url: '/Filter',
data: {
FilterBy: filterByValue
}
})
.done(function(result) {
//做你想做的事情。
});
我认为这更加符合 SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转 )原则。过滤器有自己的PageModel,只处理过滤器,我的Index PageModel没有被AJAX过滤器代码“污染”。我确实需要一个Filter.cshtml来定义路由,但它非常简单(并且ASP.NET Core默认.cshtml很好)。这是我选择的解决方案,因为它更简单,更容易更改,没有任何副作用。
但我还没有完成,因为我们需要使用XSRF / CSRF安全功能处理POST请求。
如何使用XSRF / CSRF安全功能处理POST请求
Razor Pages提供防伪令牌/验证,以阻止HTTP POST页面请求中可能存在的安全问题,例如表单(hurray! – 默认情况下是安全的)。防伪令牌(您可能将其称为[ValidateAntiForgeryToken]属性)会停止跨站点请求伪造(称为XSRF或CSRF)。
但是,对于任何AJAX POST,我们必须自己提供防伪标记。这有两个部分:
1.确保防伪令牌位于AXJAX请求所在的页面中
如果你问的话,Razor会创建正确的令牌,但是AJAX没有默认值。因此,您需要做一些事情来让razor添加防伪标记。最简单的方法是使用Html助手@ Html.AntiForgeryToken(),它将添加令牌。但是如果你已经有一个包含method =“post”的表单标签,那么razor就已经添加了防伪标记。
在我的情况下,我有一个表单,但它使用默认的HTTP GET,所以我不得不将@ Html.AntiForgeryToken()代码添加到我的Razor页面。
注意:JavaScript中的反伪造令牌的 Microsoft文档通过添加函数显示另一种方式,但我习惯使用JQuery获取值。两者都有效,但我展示了JQuery的方式。
2.将防伪令牌添加到您的请求数据中
您需要将防伪标记添加到JQuery AJAX请求中。ASP.NET Core正在查找的默认名称是请求标头中的RequestVerificationToken,值应为防伪标记。这是我修改过的JavaScript来添加它。
$.ajax({
url: '/Filter',
type: 'POST',
data: {
FilterBy: filterByValue
},
headers: {
RequestVerificationToken:
$('input:hidden[name="__RequestVerificationToken"]').val()
})
.done(function(result) {
//做你想做的事情。
});
我的方法使用JQuery查找隐藏的反令牌输入标记并提取值以发送回ASP.NET Core。完成后,您的AJAX POST将起作用 – 如果您遇到其中任何一个错误,您将在POST AJAX请求中获得状态400。
在POST中处理json数据
对于json数据的POST,您需要[FromBody]的不同属性,否则您将无法获取数据 。 通常,json返回用于返回更复杂的对象。在这个例子中,我使用了一个Person类,以下JavaScript代码显示了返回json内容类型的ajax调用 – 请注意,contentType设置为json,并且数据必须是 JSON.stringify 才能工作 。
var person = {"FirstName":"Andrew","LastName":"Lock","Age":"31"};
$.ajax({
url: '/People',
type = 'POST',
contentType = 'application/json; charset=utf-8',
headers = {
RequestVerificationToken:
$('input:hidden[name="__RequestVerificationToken"]').val()
},
data: JSON.stringify(person)
})
.done(function(result) {
//处理你的事件
});
json数据将返回到一个类中,在本例中为Person类,如下所示。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
这是一个简单的Razor PageModel,它接受json数据并将其绑定到该Person类的新实例。
public class PeopleModel : PageModel
{
public void OnPost([FromBody]Person person)
{
//do something with the person class
}
}
结论
我花了一些时间来研究在Razor页面中处理AJAX请求的最佳方法 – 我甚至还学习了一些写这篇文章的东西!但现在我有一种方法可行,它遵循SOLID软件原则。
我非常喜欢Razor Pages,主要是因为它将每个页面分成了自己的,单一的Razor页面(类控制器的结构,所有动作混合在一起,很混乱,很难处理)。我可能会在未来写更多关于Razor Pages的内容,所以请密切关注本站信息。