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

如何在Startup.cs外部使用新BlobStorage重新初始化UserState

已在DI容器中初始化的UserState单例无法在运行时安全替换,强行修改单例注册会引发并发请求间的状态串扰、数据丢失问题,正确的实现思路是引入一层动态存储路由层,根据当前请求上下文自动切换底层Blob存储容器,不需要改动现有UserState的使用逻辑。

实现步骤

1. 实现自定义动态存储类

自定义一个实现IStorage接口的路由类,内部缓存不同容器对应的Blob存储实例,通过AsyncLocal在当前异步请求流传递目标容器名,自动把所有读写操作路由到对应容器:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Azure.Blobs;
using System.Collections.Concurrent;
using System.Threading;

public class DynamicBlobStorage : IStorage
{
    private readonly string _blobConnectionString;
    private readonly ConcurrentDictionary<string, IStorage> _containerStorageCache = new();
    private readonly string _defaultContainerName;
    // 基于异步流传递当前请求的目标容器,不会跨并发请求串值
    private static readonly AsyncLocal<string> _currentTurnContainerName = new();

    public DynamicBlobStorage(string blobConnectionString, string defaultContainerName)
    {
        _blobConnectionString = blobConnectionString;
        _defaultContainerName = defaultContainerName;
    }

    /// <summary>
    /// 在事件处理逻辑中调用,指定当前请求要使用的Blob容器名
    /// </summary>
    public static void UseContainer(string containerName)
    {
        if (string.IsNullOrWhiteSpace(containerName))
            throw new ArgumentException("容器名不能为空", nameof(containerName));
        _currentTurnContainerName.Value = containerName;
    }

    private IStorage GetCurrentTurnStorage()
    {
        var targetContainer = _currentTurnContainerName.Value ?? _defaultContainerName;
        // 缓存已初始化的存储实例,避免重复创建客户端增加开销
        return _containerStorageCache.GetOrAdd(targetContainer, container =>
            new BlobsStorage(_blobConnectionString, container)
        );
    }

    public Task DeleteAsync(string[] keys, CancellationToken cancellationToken = default)
    {
        return GetCurrentTurnStorage().DeleteAsync(keys, cancellationToken);
    }

    public Task<IDictionary<string, object>> ReadAsync(string[] keys, CancellationToken cancellationToken = default)
    {
        return GetCurrentTurnStorage().ReadAsync(keys, cancellationToken);
    }

    public Task WriteAsync(IDictionary<string, object> changes, CancellationToken cancellationToken = default)
    {
        return GetCurrentTurnStorage().WriteAsync(changes, cancellationToken);
    }
}

2. 调整Startup中的服务注册逻辑

把原来直接绑定BlobsStorage的注册替换为自定义动态存储,UserState、ConversationState的注册逻辑完全不需要改动,它们会自动依赖注入IStorage实例:

// 在ConfigureServices方法中添加
var blobConnectionString = Configuration.GetValue<string>("BlobStorageConnectionString");
var defaultContainerName = Configuration.GetValue<string>("DefaultUserStateContainerName");
// 注册动态存储为IStorage单例
services.AddSingleton<IStorage>(sp => new DynamicBlobStorage(blobConnectionString, defaultContainerName));

// 以下注册保持原有写法即可,无需修改
services.AddSingleton<UserState>();
services.AddSingleton<ConversationState>();
// 其余Bot、中间件注册保持不变

3. 在事件处理方法中指定目标容器

在OnEventActivityAsync方法中,从前端传来的事件里解析到容器名后,在访问UserState之前调用UseContainer方法指定当前请求要用的容器即可,后续所有状态读写(包括自动状态保存中间件的执行逻辑)都会自动路由到对应容器:

protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
    // 匹配你前端发送的事件名
    if (turnContext.Activity.Name == "SetUserStateContainer")
    {
        // 根据前端传参结构解析容器名,以下为示例取值逻辑
        var targetContainer = turnContext.Activity.Value?.ToString();
        if (!string.IsNullOrWhiteSpace(targetContainer))
        {
            // 建议此处增加容器名校验,过滤不符合Azure Blob命名规则的非法输入
            DynamicBlobStorage.UseContainer(targetContainer);
        }
    }

    await base.OnEventActivityAsync(turnContext, cancellationToken);
}
注意事项
  • 必须对前端传入的容器名做合法性校验,限制字符范围、长度,避免恶意输入引发存储访问错误
  • AsyncLocal的作用域天然覆盖单个Turn的完整异步执行流,请求结束后会自动回收,不需要手动清理,不会出现跨用户请求的容器名串扰
  • 存储实例缓存是必要优化,避免每次请求都新建Blob客户端带来不必要的性能开销
  • 如果同一个Turn内需要切换回默认容器,手动传入默认容器名调用UseContainer即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:21:49