如何在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
相关产品推荐
相关产品推荐

