You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从JavaScript调用ASP.NET Razor Page Handler方法遇400错误

ASP.NET Razor Page 调用OnPost Handler时400 Bad Request错误排查与解决

问题说明

通过JavaScript调用ASP.NET Razor Page的OnPost Handler方法时,持续收到400 Bad Request错误,Razor Page模型仅包含OnPost和OnGet两个Handler,需求是通过JS发起POST请求执行业务逻辑。

现有代码

JavaScript 请求代码

async function fetchMakeCredentialOptions(formData) {
    let response = await fetch('Account/PasswordLessRegistration?handler=Submit',
    {
        method: "POST",
        body: formData, 
        header:
        {
            'Accept': 'application/json'
        }
    });
    let data = await response.json();
    return data;
}

Razor Page C# 代码

namespace IdentityServerHost.Pages.PasswordLessLogin
{
     public class Index : PageModel
    {
        private IFido2 _fido2;
        public static readonly DevelopmentInMemoryStore PasswordlessStore = new DevelopmentInMemoryStore();
        private readonly AdminIdentityDbContext _context;
        [BindProperty]
        public InputModel Input { get; set; }

        public Index(IFido2 fido2, AdminIdentityDbContext context)
        {
            _fido2 = fido2;
            _context = context;
        }
        private string FormatException(Exception e)
        {
            return string.Format("{0}{1}", e.Message, e.InnerException != null ? " (" + e.InnerException.Message + ")" : "");
        }
        public async Task<IActionResult> OnGet()
        {
            return Page();
        }
        public IActionResult OnPost([FromForm] string username,
                                                [FromForm] string displayName,
                                                [FromForm] string attType,
                                                [FromForm] string authType,
                                                [FromForm] bool requireResidentKey,
                                                [FromForm] string userVerification)
        {
            try
            {
                // 用户必须已存在于Identity系统中
                var identityUser = _context.Users.FirstOrDefault(user => user.UserName == "anis");
                if (identityUser == null)
                {
                    throw new Exception("用户未找到");
                }

                if (!HttpContext.User.IsAuthenticated())
                {
                    throw new Exception("用户未认证");
                };

                // 1. 通过用户名从数据库获取用户(示例中自动创建不存在的用户)
                var user = PasswordlessStore.GetOrAddUser(username, () => new Fido2User
                {
                    DisplayName = displayName,
                    Name = username,
                    Id = Encoding.UTF8.GetBytes(username) // 需要用户ID的字节表示
                });

                // 2. 获取用户已有的密钥
                var existingKeys = PasswordlessStore.GetCredentialsByUser(user).Select(c => c.Descriptor).ToList();

                // 3. 创建凭证选项
                var authenticatorSelection = new AuthenticatorSelection
                {
                    RequireResidentKey = requireResidentKey,
                    UserVerification = userVerification.ToEnum<UserVerificationRequirement>()
                };

                if (!string.IsNullOrEmpty(authType))
                    authenticatorSelection.AuthenticatorAttachment = authType.ToEnum<AuthenticatorAttachment>();

                var exts = new AuthenticationExtensionsClientInputs()
                {
                    Extensions = true,
                    UserVerificationIndex = true,
                    Location = true,
                    UserVerificationMethod = true,
                    BiometricAuthenticatorPerformanceBounds = new AuthenticatorBiometricPerfBounds
                    {
                        FAR = float.MaxValue,
                        FRR = float.MaxValue
                    }
                };
                var options = _fido2.RequestNewCredential(user, existingKeys, authenticatorSelection, attType.ToEnum<AttestationConveyancePreference>(), exts);
                // 4. 临时存储选项,可使用会话/内存缓存/Redis/数据库
                HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson());

                // 5. 将选项返回给客户端
                return new JsonResult(options);
            }
            catch (Exception e)
            {
                return new JsonResult(new CredentialCreateOptions { Status = "error", ErrorMessage = FormatException(e) });
            }
            return new JsonResult(true);
        }
    }
}

FormData 构造代码

var data = new FormData();
data.append('username', username);
data.append('displayName', displayName);
data.append('attType', attestation_type);
data.append('authType', authenticator_attachment);
data.append('userVerification', user_verification);
data.append('requireResidentKey', require_resident_key);

错误原因与修复方案

1. Handler 名称不匹配

JS请求中指定了handler=Submit,但C#中的方法是OnPost(无Handler后缀)。Razor Pages中,自定义Handler需要遵循OnPost{HandlerName}的命名规则,或者请求时不指定handler参数(调用默认的OnPost)。

修复方式二选一:

  • 去掉JS请求中的?handler=Submit,直接请求对应页面路径
  • 将C#中的方法名改为OnPostSubmit,保持请求中的handler=Submit参数

2. 请求头拼写错误

JS代码中header应为复数headers,拼写错误导致请求头设置失效。

3. 缺少Anti-Forgery Token验证

Razor Pages默认启用Anti-Forgery验证,POST请求必须携带__RequestVerificationToken,否则会返回400错误。

添加方式二选一:

  • 在FormData中添加令牌:data.append('__RequestVerificationToken', document.querySelector('input[name="__RequestVerificationToken"]').value);
  • 在请求头中添加令牌:
headers:
{
    'Accept': 'application/json',
    'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
}

注意:页面中需要先通过@Html.AntiForgeryToken()生成令牌输入框。

4. 路由路径可能错误

JS请求路径是Account/PasswordLessRegistration,但C#的Page位于PasswordLessLogin/Index,需要确认实际路由路径是否正确。可以通过查看页面的实际URL,或者在Razor Page顶部添加@page "/Account/PasswordLessRegistration"来指定路由。

修复后的完整JS代码示例

async function fetchMakeCredentialOptions(formData) {
    // 添加Anti-Forgery Token到FormData
    formData.append('__RequestVerificationToken', document.querySelector('input[name="__RequestVerificationToken"]').value);
    
    let response = await fetch('PasswordLessLogin/Index', // 修正为正确的路由路径
    {
        method: "POST",
        body: formData, 
        headers: // 修正拼写为headers
        {
            'Accept': 'application/json'
        }
    });
    let data = await response.json();
    return data;
}

内容的提问来源于stack exchange,提问作者Anis Bouzidi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:50:54