.NET8 Razor页面数据库查询问题及规范实现咨询
问题背景
我在StackOverflow和谷歌上未找到解决方案,了解到直接从Razor页面调用控制器是不规范的,需重新考量实现方式。我的场景如下:
- 拥有一个带Microsoft Identity的.NET8 Blazor/Razor(非WASM)服务器端交互式Web应用;
- 该应用同时包含API控制器;
- 其中一个控制器可通过stringId查询数据库中存储的链接(例如:stringId为E4A1对应链接https://Google.ca/,调用API可返回该链接);
- API查询正常,但需在Razor页面中实现该查询功能。
我的尝试步骤:
- 在Razor页面中注入:
@inject ApplicationDbContext _DbContext
- LinkModel模型定义如下:
public class LinkModel { [Key] public int Id { get; set; } [Required] [MaxLength(8)] public string StringId { get; set; } = ""; [MaxLength(512)] public string Url { get; set; } = ""; }
- 在Razor页面的@code区域编写查询代码:
string strId = "E4A1"; LinkModel? objTable = await _DbContext.DbLinks.FirstOrDefaultAsync(w => w.StringId == strId);
但Visual Studio提示FirstOrDefaultAsync不存在。
于是我在LinkController中创建了静态类:
public static class LinkFunctions { public static async Task<string> GetUrlX(ApplicationDbContext objDbContext, string strHex32Id) { LinkModel? objTable = await objDbContext.DbLinks.FirstOrDefaultAsync(w => w.StringId == strHex32Id); return objTable.Url; } }
并在Razor页面中调用:
string strId = "E4A1"; string strUrl = await LinkFunctions.GetUrlX(_DbContext, strId);
此方法可行。
疑问与解答
1. 为何相同代码在Razor页面中无法运行?
核心原因是Razor页面的@code区域缺少必要的命名空间引用。FirstOrDefaultAsync是Entity Framework Core提供的异步扩展方法,定义在Microsoft.EntityFrameworkCore命名空间下。控制器类默认已引入该命名空间,所以静态类中的代码可以正常识别;而Razor页面未添加该引用,导致IDE无法找到方法。
只需在Razor页面顶部添加以下引用即可解决:
@using Microsoft.EntityFrameworkCore
2. 为何不建议从Razor页面调用控制器?
控制器属于请求处理层,职责是接收HTTP请求、封装API逻辑并返回响应;Razor页面属于UI展示层,负责渲染用户界面。直接调用控制器会打破分层架构的职责边界:
- 违反单一职责原则:控制器逻辑与UI层耦合,修改API逻辑可能影响UI代码,反之亦然。
- 测试难度提升:直接调用控制器方法无法模拟HTTP请求上下文,单元测试会变得复杂。
- 复用性差:控制器逻辑被UI层直接调用后,无法再被移动端、第三方服务等其他客户端复用。
3. 如何采用更规范的方式实现该功能?
推荐使用仓储模式+服务层的架构,将数据访问逻辑从UI层和控制器中解耦,实现职责分离与代码复用:
步骤1:定义仓储接口
创建仓储接口封装数据查询逻辑,实现数据访问层的抽象:
public interface ILinkRepository { Task<string?> GetUrlByStringIdAsync(string stringId); }
步骤2:实现仓储类
基于ApplicationDbContext实现仓储接口,专注于数据操作:
public class LinkRepository : ILinkRepository { private readonly ApplicationDbContext _dbContext; public LinkRepository(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<string?> GetUrlByStringIdAsync(string stringId) { var link = await _dbContext.DbLinks.FirstOrDefaultAsync(l => l.StringId == stringId); return link?.Url; } }
步骤3:注册服务
在Program.cs中注册仓储服务:
builder.Services.AddScoped<ILinkRepository, LinkRepository>();
步骤4:在Razor页面和控制器中复用
- Razor页面中使用:
@inject ILinkRepository _linkRepository @code { private string? _url; protected override async Task OnInitializedAsync() { string strId = "E4A1"; _url = await _linkRepository.GetUrlByStringIdAsync(strId); } }
- API控制器中使用:
[ApiController] [Route("api/[controller]")] public class LinkController : ControllerBase { private readonly ILinkRepository _linkRepository; public LinkController(ILinkRepository linkRepository) { _linkRepository = linkRepository; } [HttpGet("{stringId}")] public async Task<IActionResult> GetUrl(string stringId) { var url = await _linkRepository.GetUrlByStringIdAsync(stringId); if (url == null) return NotFound(); return Ok(url); } }
这种架构的优势:
- 职责清晰:UI层负责展示,仓储层负责数据操作,逻辑分离便于维护。
- 易于测试:可通过Mock仓储接口测试UI和控制器逻辑,无需依赖真实数据库。
- 复用性强:仓储逻辑可同时被Razor页面、API控制器及其他客户端调用。
内容的提问来源于stack exchange,提问作者Sigma3Wolf
相关产品推荐
相关产品推荐

