在这个假设的场景中,我在页面上显示消息列表。
一段 javascript -
Edit(id)
- 从服务器检索用于编辑消息的正确 HTML,并将其注入到页面上。
另一个 javascript 片段 -
Save(id)
- 将 AJAX POST 返回到服务器并将结果注入回页面。
根据 Razor Pages 的 AntiForgery 规则,POST 会触发 404,因为它缺少正确的令牌。由于我没有使用
<form>
,内置的防伪框架不会自动启动并提供令牌。
并且,由于理论上我可以选择并行编辑多条消息,因此我不确定如何正确实现
@Html.AntiForgeryToken()
字段。我是为每条消息生成一个令牌,然后以某种方式将其收集起来并与我的 POST 一起发送,还是......?
我想知道是否有一种正确的方法来处理这种情况,考虑到防伪功能,或者我是否必须忍受它并围绕它构建?我可以使用
[IgnoreAntiforgeryToken]
轻松解决这个问题,但这显然有其自身的后果。
暂时,
MessageManager.Messages
只是静态List<TextMessage>
@foreach (var item in Model.Messages) {
<div id="[email protected]">
<label class="form-label">@item.Type:</label>
@{
var result = Markdown.ToHtml(item.Message, pipeline);
@Html.Raw(result)
}
</div>
<div class="mb-3 text-end">
<a href="#" onclick="Edit(@item.Id)">✏</a>
<a href="#" onclick="Delete(@item.Id)">❌</a>
</div>
}
@section Scripts {
<script>
function Edit(messageId) {
$.get(
"/Messages/_EditMessage?messageId=" + messageId,
function (str) {
$('#message_' + messageId).html(str);
}
);
}
function Save(messageId, content) {
$.post(
"/Messages/_EditMessage?messageId=" + messageId,
{ NewMessage: $('#NewMessage_' + messageId).val() },
function (str) {
$('#message_' + messageId).html(str);
}
);
}
</script>
}
@if (Model.Edit) {
<div class="mb-3">
<label class="form-label">@Model.Message.Type</label>
<textarea class="form-control" id="[email protected]" rows="@(Model.Message.Message.Count(c => c == '\n') + 1)">@Model.Message.Message</textarea>
</div>
<div>
<button class="btn btn-outline-success" onclick="Save(@Model.Message.Id)">Save</button>
</div>
}
else {
var result = Markdown.ToHtml(Model.Message.Message, pipeline);
<label class="form-label">@Model.Message.Type:</label>
@Html.Raw(result)
}
[IgnoreAntiforgeryToken]
public class _EditMessageModel : PageModel
{
public TextMessage Message { get; set; } = null!;
public bool Edit { get; set; }
[BindProperty] public string NewMessage { get; set; } = string.Empty;
public IActionResult OnGet(int messageId) {
var message = MessageManager.Messages.FirstOrDefault(m => m.Id == messageId);
if (message is null)
return NotFound(messageId);
Message = message;
Edit = true;
return Page();
}
public IActionResult OnPost(int messageId) {
var message = MessageManager.Messages.FirstOrDefault(m => m.Id == messageId);
if (message is null)
return NotFound(messageId);
Message = message;
Message.Message = NewMessage;
Edit = false;
return Page();
}
}
我是否为每条消息生成一个令牌,然后以某种方式将其收集起来并与我的 POST 一起发送,或者......?
是的,您需要检索令牌并将其添加到每个
POST
请求中。
根据 learnrazorpages.com 上的页面:
如果在请求中省略该值,服务器将返回 400 Bad Request 结果。
这个SO答案提供了一个函数,可以从您的问题中调用
save()
函数。在 post
上,该函数从 input
创建的隐藏 @Html.AntiForgeryToken()
字段中检索防伪令牌。在 @Html.AntiForgeryToken()
页面正文中添加 View.cshtml
。
为了简单起见,每次调用
save()
函数时都会检索令牌。但是,您可以在 JavaScript 中调用一次 addAntiForgeryToken()
函数,存储其值,然后将令牌值附加到每个后续 post
请求中。
@section Scripts {
<script>
function Edit(messageId) {
$.get(
"/Messages/_EditMessage?messageId=" + messageId,
function (str) {
$('#message_' + messageId).html(str);
}
);
}
function Save(messageId, content) {
let data = { NewMessage: messageId };
data = addAntiForgeryToken(data);
console.log(data);
$.post(
"/Messages/_EditMessage?messageId=" + messageId,
data,
function (str) {
$('#message-response').html(str);
}
);
}
// CSRF (XSRF) security
// https://stackoverflow.com/questions/73037094/how-to-do-an-ajax-post-with-mvc-antiforgerytoken
function addAntiForgeryToken(data) {
//if the object is undefined, create a new one.
if (!data) {
data = {};
}
//add token
const tokenInput = $('input[name=__RequestVerificationToken]');
if (tokenInput.length) {
data.__RequestVerificationToken = tokenInput.val();
}
return data;
}
</script>
}