在Razor Pages中如何通过jQuery调用C#后端接口?
解决方案:Razor Pages按需加载数据与JWT令牌处理
环境与需求回顾
- 环境:ASP.NET Core 6.0、Razor Pages
- 认证:JWT Bearer令牌存储在Session Storage
- 核心需求:页面展示对象列表,每个对象的详情数据按需加载(不预查询),同时避免暴露原始数据
一、令牌获取的最优方式
不需要将Bearer令牌传递给分部视图,直接从前端Session Storage读取即可——令牌本就存储在前端,无需通过后端渲染传递,既减少冗余又避免令牌出现在HTML源码中。
二、两种按需加载方案(均避免原始数据暴露)
方案1:调用授权API端点(推荐,前后端分离式处理)
后端创建授权API控制器,在控制器内处理原始数据,只返回安全的展示字段,前端通过AJAX携带令牌请求。
1. 后端API控制器实现
[Authorize] [ApiController] [Route("api/parts")] public class PartsApiController : ControllerBase { private readonly IPartDataService _dataService; public PartsApiController(IPartDataService dataService) { _dataService = dataService; } [HttpGet("{partId}/details")] public async Task<IActionResult> GetPartDetails(int partId) { // 查询原始数据 var rawData = await _dataService.GetPartInnerDataAsync(partId); // 仅返回需要展示的字段,过滤敏感/原始数据 var safeViewModel = new { DisplayName = rawData.FullName, Status = rawData.Status.ToString(), UpdateTime = rawData.LastUpdated.ToString("yyyy-MM-dd HH:mm") }; return Ok(safeViewModel); } }
2. 前端分部视图JS修改
@model dynamic <div class="card"> <div class="card-header"> <button type="button" class="btn" data-bs-toggle="collapse" data-bs-target="#partContent_@Model.PartData.partId" data-id="@Model.PartData.partId" onclick="queryInnerData(this);"> @Model.PartData.DisplayName </button> </div> <div class="collapse" id="partContent_@Model.PartData.partId"> <div class="card card-body"> <div id="innerData_@Model.PartData.partId"></div> </div> </div> </div> <script type="text/javascript"> async function queryInnerData(sender) { const partId = sender.getAttribute('data-id'); const targetDiv = document.getElementById(`innerData_${partId}`); // 避免重复加载 if (targetDiv.innerHTML.trim() !== '') return; // 从Session Storage取令牌(替换成你的令牌存储key) const token = sessionStorage.getItem('auth_token'); if (!token) { targetDiv.innerHTML = '<p class="text-danger">请重新登录</p>'; return; } try { const response = await fetch(`/api/parts/${partId}/details`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error('数据加载失败'); const data = await response.json(); // 渲染安全数据 targetDiv.innerHTML = ` <p><strong>状态:</strong>${data.Status}</p> <p><strong>更新时间:</strong>${data.UpdateTime}</p> `; } catch (error) { targetDiv.innerHTML = `<p class="text-danger">${error.message}</p>`; } } </script>
方案2:异步加载Razor分部视图(贴合Razor Pages生态)
通过请求Razor Page的处理方法,直接返回渲染好的HTML片段,后端完全控制输出内容,前端看不到任何原始数据。
1. 后端PageModel添加处理方法
public class IndexModel : PageModel { private readonly IPartDataService _dataService; public List<PartModel> Parts; public string Id; public IndexModel(IPartDataService dataService) { _dataService = dataService; } public async Task<IActionResult> OnGetAsync(string Id) { // 加载初始列表数据 Parts = await _dataService.GetPartsAsync(Id); return Page(); } [Authorize] public async Task<IActionResult> OnGetPartDetailsPartial(int partId) { // 查询并处理数据,返回安全的视图模型 var rawData = await _dataService.GetPartInnerDataAsync(partId); var viewModel = new PartDetailsViewModel { Status = rawData.Status.ToString(), UpdateTime = rawData.LastUpdated.ToString("yyyy-MM-dd HH:mm") }; // 返回渲染好的分部视图 return Partial("_PartDetailsPartial", viewModel); } }
2. 创建分部视图_PartDetailsPartial.cshtml
@model PartDetailsViewModel <p><strong>状态:</strong>@Model.Status</p> <p><strong>更新时间:</strong>@Model.UpdateTime</p>
3. 前端JS修改(需携带AntiForgery令牌)
async function queryInnerData(sender) { const partId = sender.getAttribute('data-id'); const targetDiv = document.getElementById(`innerData_${partId}`); if (targetDiv.innerHTML.trim() !== '') return; const token = sessionStorage.getItem('auth_token'); if (!token) { targetDiv.innerHTML = '<p class="text-danger">请重新登录</p>'; return; } try { const response = await fetch(`?handler=PartDetailsPartial&partId=${partId}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, // Razor Pages必须携带AntiForgery令牌 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value } }); if (!response.ok) throw new Error('数据加载失败'); const html = await response.text(); targetDiv.innerHTML = html; } catch (error) { targetDiv.innerHTML = `<p class="text-danger">${error.message}</p>`; } }
注意:确保页面包含
@Html.AntiForgeryToken()(通常布局页已默认添加)。
核心优化总结
- 令牌安全:直接从Session Storage读取,避免后端传递导致的冗余与暴露风险
- 数据安全:后端统一处理原始数据,仅返回需要展示的内容或渲染好的HTML
- 性能优化:添加重复加载判断,避免无效请求
- 用户体验:完善错误处理,提供清晰的状态提示
内容的提问来源于stack exchange,提问作者ato
相关产品推荐
相关产品推荐

