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

.NET8 Razor页面数据库查询问题及规范实现咨询

问题背景

我在StackOverflow和谷歌上未找到解决方案,了解到直接从Razor页面调用控制器是不规范的,需重新考量实现方式。我的场景如下:

  • 拥有一个带Microsoft Identity的.NET8 Blazor/Razor(非WASM)服务器端交互式Web应用;
  • 该应用同时包含API控制器;
  • 其中一个控制器可通过stringId查询数据库中存储的链接(例如:stringId为E4A1对应链接https://Google.ca/,调用API可返回该链接);
  • API查询正常,但需在Razor页面中实现该查询功能。

我的尝试步骤:

  1. 在Razor页面中注入:
@inject ApplicationDbContext _DbContext
  1. 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; } = "";
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:24:56