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

如何在AspNetCore HostedService中调用ServiceStack AutoQuery

问题背景

我了解ServiceStack是与AspNetCore不同的框架。假设有一个会员系统,用户通过ServiceStack AutoQuery筛选客户,系统需定期向符合条件的客户发送通讯,而符合条件的客户会随时间变化。因此相较于保存所有客户ID,保存QueryDb更合理——比如序列化QueryMerchantCustomers并持久化到数据库,在HostedService需要使用时反序列化。现在的问题是:如何在AspNetCore HostedService中调用以下ServiceStack服务方法?

public ObjectsResponse<Customer> Any(QueryMerchantCustomers qry)
{
    if (!_authHandler.VerifyJwt(Request, out var claimsPrincipal, "user", qry.MerchantGuid))
    {
        base.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        base.Response.EndRequest();
    }
    var res = new ObjectsResponse<Customer>();
    var customers = new List<Customer>();
    if (string.IsNullOrEmpty(qry.MerchantGuid))
    {
        res.Errors.Add("Merchant guid can not be null or empty");
        return res;
    }
    var merchant = _jointMerchantHandler.GetMerchantByGuidAsync(qry.MerchantGuid).GetAwaiter().GetResult();
    // make it re-usable in future
    if (qry.Page > 0 && qry.Limit > 0)
    {
        qry.Page -= 1;
        qry.Skip = qry.Page * qry.Limit;
        qry.Take = qry.Limit;
    }
    var q = AutoQuery.CreateQuery(qry, base.Request);
    q.And<UserDetail>(x => x.LicenseId == merchant.LicenseId && !x.IsDelete);
    var result = AutoQuery.Execute(qry, q);
    res.CountAll = result.Total;
    if (result.Results.Count > 0)
    {
        var details = _userDetailRepo.Select(x => x.LicenseId == merchant.LicenseId && Sql.In(x.UserId, result.Results.Select(x => x.Id).ToList()));
        foreach (var user in result.Results)
        {
            customers.Add(user.ToCustomer().MergeDetail(details.FirstOrDefault(x => x.UserId == user.Id)));
        }
    }
    res.Data = customers;
    return res;
}

解决方案

直接在HostedService中调用原方法会遇到ServiceStack请求上下文依赖(如Request、Response)、同步阻塞异步调用等问题,建议通过重构核心逻辑+依赖注入的方式实现:

1. 剥离核心查询逻辑,脱离ServiceStack上下文

把原方法中的业务逻辑抽成独立的可复用服务类,去掉对ServiceStack请求上下文的强依赖,同时修复同步阻塞异步调用的问题:

public class CustomerQueryService
{
    private readonly IAutoQueryDb _autoQuery;
    private readonly IAuthHandler _authHandler;
    private readonly IJointMerchantHandler _jointMerchantHandler;
    private readonly IUserDetailRepo _userDetailRepo;

    public CustomerQueryService(IAutoQueryDb autoQuery, IAuthHandler authHandler, 
        IJointMerchantHandler jointMerchantHandler, IUserDetailRepo userDetailRepo)
    {
        _autoQuery = autoQuery;
        _authHandler = authHandler;
        _jointMerchantHandler = jointMerchantHandler;
        _userDetailRepo = userDetailRepo;
    }

    public async Task<ObjectsResponse<Customer>> QueryMerchantCustomersAsync(QueryMerchantCustomers qry, IRequest? request = null)
    {
        var res = new ObjectsResponse<Customer>();
        
        // 基础参数校验
        if (string.IsNullOrEmpty(qry.MerchantGuid))
        {
            res.Errors.Add("商户GUID不能为空");
            return res;
        }

        // 仅当有ServiceStack请求上下文时才做JWT验证(HostedService调用时跳过)
        if (request != null)
        {
            if (!_authHandler.VerifyJwt(request, out _, "user", qry.MerchantGuid))
            {
                res.Errors.Add("未授权");
                return res;
            }
        }

        // 异步获取商户信息
        var merchant = await _jointMerchantHandler.GetMerchantByGuidAsync(qry.MerchantGuid);
        if (merchant == null)
        {
            res.Errors.Add("未找到对应商户");
            return res;
        }

        // 处理分页参数
        if (qry.Page > 0 && qry.Limit > 0)
        {
            qry.Page -= 1;
            qry.Skip = qry.Page * qry.Limit;
            qry.Take = qry.Limit;
        }

        // 创建AutoQuery查询,HostedService调用时用NullRequest替代真实请求上下文
        var autoQueryRequest = request ?? new NullRequest();
        var q = _autoQuery.CreateQuery(qry, autoQueryRequest);
        q.And<UserDetail>(x => x.LicenseId == merchant.LicenseId && !x.IsDelete);

        // 异步执行AutoQuery
        var result = await _autoQuery.ExecuteAsync(qry, q);
        res.CountAll = result.Total;

        // 转换用户数据为Customer模型
        if (result.Results.Count > 0)
        {
            var userIds = result.Results.Select(x => x.Id).ToList();
            var details = await _userDetailRepo.SelectAsync(x => x.LicenseId == merchant.LicenseId && Sql.In(x.UserId, userIds));
            
            res.Data = result.Results
                .Select(user => user.ToCustomer().MergeDetail(details.FirstOrDefault(x => x.UserId == user.Id)))
                .ToList();
        }

        return res;
    }
}

原ServiceStack服务类可简化为:

public class MerchantCustomersService : Service
{
    private readonly CustomerQueryService _customerQueryService;

    public MerchantCustomersService(CustomerQueryService customerQueryService)
    {
        _customerQueryService = customerQueryService;
    }

    public async Task<ObjectsResponse<Customer>> Any(QueryMerchantCustomers qry)
    {
        var res = await _customerQueryService.QueryMerchantCustomersAsync(qry, Request);
        
        if (res.Errors.Any(e => e == "未授权"))
        {
            Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            Response.EndRequest();
        }
        
        return res;
    }
}

2. 在HostedService中注入并调用重构后的服务

AspNetCore HostedService为单例,需通过IServiceScopeFactory创建作用域获取Scoped生命周期的服务:

public class CustomerCommunicationHostedService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<CustomerCommunicationHostedService> _logger;

    public CustomerCommunicationHostedService(IServiceScopeFactory scopeFactory, 
        ILogger<CustomerCommunicationHostedService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = _scopeFactory.CreateScope();
                var customerQueryService = scope.ServiceProvider.GetRequiredService<CustomerQueryService>();
                var storedQueryRepo = scope.ServiceProvider.GetRequiredService<IStoredQueryRepo>(); // 存储序列化查询的仓储

                // 从数据库获取所有持久化的QueryMerchantCustomers
                var storedQueries = await storedQueryRepo.GetAllAsync(stoppingToken);
                foreach (var storedQuery in storedQueries)
                {
                    // 反序列化查询对象
                    var qry = JsonSerializer.Deserialize<QueryMerchantCustomers>(storedQuery.SerializedQuery);
                    if (qry == null) continue;

                    // 调用查询方法(无需传入Request,自动跳过JWT验证)
                    var response = await customerQueryService.QueryMerchantCustomersAsync(qry);
                    
                    if (!response.Errors.Any() && response.Data != null)
                    {
                        // 执行发送通讯的逻辑(如邮件、短信)
                        await SendCommunicationAsync(response.Data, stoppingToken);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "执行客户通讯任务时出错");
            }

            // 定期执行,示例为每小时一次
            await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
        }
    }

    private async Task SendCommunicationAsync(List<Customer> customers, CancellationToken stoppingToken)
    {
        // 实现具体的通讯发送逻辑
        foreach (var customer in customers)
        {
            // 模拟发送操作
            await Task.Delay(100, stoppingToken);
        }
    }
}

3. 注册依赖到AspNetCore容器

在Program.cs中注册所有相关服务:

var builder = WebApplication.CreateBuilder(args);

// 注册核心查询服务
builder.Services.AddScoped<CustomerQueryService>();
// 注册HostedService
builder.Services.AddHostedService<CustomerCommunicationHostedService>();
// 注册其他依赖:IAuthHandler、IJointMerchantHandler、IUserDetailRepo等
builder.Services.AddServiceStack(new AppHost()); // 注册ServiceStack框架

var app = builder.Build();
app.UseServiceStack();
app.Run();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:25:13