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

.NET Razor Pages Scoped依赖注入回发时属性值丢失问题求助

解决Razor Pages页面回发时属性值丢失的问题

问题根源

用AddScoped注册的IDataService每个HTTP请求都会生成新实例,回发属于新请求,所以之前存储的属性值自然清零;AddSingleton是全局单例,所有页面、用户共用,不符合“仅当前页面回发保留,刷新/跳转清除”的需求。

可行解决方案

方案一:用TempData存储状态

TempData专为跨请求传递数据设计,默认读取后自动标记删除,刚好匹配“回发保留,跳转/刷新清除”的需求。

步骤1:添加TempData扩展方法

用来序列化/反序列化复杂类型和二进制数据:

public static class TempDataExtensions
{
    public static void Set<T>(this ITempDataDictionary tempData, string key, T value)
    {
        tempData[key] = JsonSerializer.Serialize(value);
    }

    public static T? Get<T>(this ITempDataDictionary tempData, string key)
    {
        if (tempData.TryGetValue(key, out var obj))
        {
            return JsonSerializer.Deserialize<T>((string)obj);
        }
        return default;
    }

    public static void SetBytes(this ITempDataDictionary tempData, string key, byte[]? value)
    {
        if (value == null)
        {
            tempData.Remove(key);
            return;
        }
        tempData[key] = Convert.ToBase64String(value);
    }

    public static byte[]? GetBytes(this ITempDataDictionary tempData, string key)
    {
        if (tempData.TryGetValue(key, out var obj))
        {
            return Convert.FromBase64String((string)obj);
        }
        return default;
    }
}

步骤2:修改页面模型

在OnGet中恢复数据,OnPost中保存数据:

public class ProfileModel : PageModel
{
    private readonly ILogger<ProfileModel> _loggerService;
    private readonly IUserService _userService; // 假设用于获取初始用户数据

    public Models.User? User { get; set; }
    public byte[]? LogoData { get; set; }
    public byte[]? SignatureData { get; set; }

    public ProfileModel(ILogger<ProfileModel> loggerService, IUserService userService)
    {
        _loggerService = loggerService;
        _userService = userService;
    }

    public void OnGet()
    {
        // 从TempData恢复数据
        User = TempData.Get<Models.User>("User");
        LogoData = TempData.GetBytes("LogoData");
        SignatureData = TempData.GetBytes("SignatureData");

        // 首次加载时从服务获取初始数据
        if (User == null)
        {
            User = _userService.GetCurrentUser(User.Identity?.Name);
        }

        // 保留TempData,确保回发后还能读取
        TempData.Keep("User");
        TempData.Keep("LogoData");
        TempData.Keep("SignatureData");
    }

    public async Task<IActionResult> OnPostRecreateApiKeyAsync()
    {
        // 业务逻辑:比如重新生成API Key
        // ...

        // 更新TempData中的数据
        TempData.Set("User", User);
        TempData.SetBytes("LogoData", LogoData);
        TempData.SetBytes("SignatureData", SignatureData);

        return Page();
    }
}

方案二:用Session存储状态

Session适合存储较多数据(包括二进制数据),生命周期为用户会话(直到超时或关闭浏览器),但需要手动管理清除逻辑。

步骤1:启用Session

在Program.cs中添加Session服务和中间件:

// 注册Session服务
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置会话超时
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// 注册HttpContextAccessor(用于在服务中获取Session)
builder.Services.AddHttpContextAccessor();

// 配置中间件(注意顺序:在UseRouting之后,UseAuthorization之前)
var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession(); // 启用Session
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();

app.Run();

步骤2:修改DataService用Session存取数据

public class DataService : IDataService
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private ISession Session => _httpContextAccessor.HttpContext!.Session;

    public DataService(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Models.User? User
    {
        get => Session.Get<Models.User>("User");
        set => Session.Set("User", value);
    }

    public byte[]? LogoData
    {
        get => Session.GetBytes("LogoData");
        set => Session.SetBytes("LogoData", value);
    }

    public byte[]? SignatureData
    {
        get => Session.GetBytes("SignatureData");
        set => Session.SetBytes("SignatureData", value);
    }
}

// Session扩展方法
public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T? Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }

    public static void SetBytes(this ISession session, string key, byte[]? value)
    {
        if (value == null)
        {
            session.Remove(key);
            return;
        }
        session.Set(key, value);
    }

    public static byte[]? GetBytes(this ISession session, string key)
    {
        session.TryGetValue(key, out var value);
        return value;
    }
}

步骤3:页面模型中使用DataService

和原有代码逻辑一致,注入IDataService后直接使用即可:

public class ProfileModel : PageModel
{
    private readonly ILogger<ProfileModel> _loggerService;
    private readonly IDataService _dataService;
    private readonly IUserService _userService;

    public Models.User? User { get; set; }
    public byte[]? LogoData { get; set; }
    public byte[]? SignatureData { get; set; }

    public ProfileModel(ILogger<ProfileModel> loggerService, IDataService dataService, IUserService userService)
    {
        _loggerService = loggerService;
        _dataService = dataService;
        _userService = userService;
    }

    public void OnGet()
    {
        // 从DataService恢复数据
        User = _dataService.User;
        LogoData = _dataService.LogoData;
        SignatureData = _dataService.SignatureData;

        // 首次加载时初始化
        if (User == null)
        {
            User = _userService.GetCurrentUser(User.Identity?.Name);
            _dataService.User = User;
        }
    }

    public async Task<IActionResult> OnPostRecreateApiKeyAsync()
    {
        // 业务逻辑...

        // 更新DataService中的数据
        _dataService.User = User;
        _dataService.LogoData = LogoData;
        _dataService.SignatureData = SignatureData;

        return Page();
    }
}

方案三:隐藏字段(仅适合简单小数据)

如果数据量小且无二进制数据,可以在表单中添加隐藏字段,让数据随回发请求传递:

<form method="post">
    <!-- 你的表单控件 -->
    <input type="hidden" asp-for="User.Id" />
    <input type="hidden" asp-for="User.Name" />
    <!-- 其他需要保留的User属性 -->
    
    <button type="submit">提交</button>
</form>

页面模型会自动绑定隐藏字段的值,无需额外存储。但二进制数据(如LogoData)不适合用这种方式,会导致请求体积过大。

方案对比

方案优点缺点
TempData自动管理生命周期,使用简单,符合需求存储容量有限,不适合超大数据
Session支持大数据和二进制数据,存储灵活需要配置中间件,生命周期是会话级(需手动清除)
隐藏字段无需依赖额外服务,实现简单不支持二进制数据,数据量大时请求过大

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:09:53