2

我在Azure Service Fabric中有一個無狀態服務,並且使用了Microsoft.Extensions.DependencyInjection,但是對於其他任何DI框架都存在同樣的問題。在我的Program.cs中,我創建了一個ServiceCollection,添加了所有(但是一個)我的註冊,創建服務提供者並將其傳遞給我的服務的構造函數。任何具有外部入口的服務方法將創建一個新的服務範圍並調用主要的業務邏輯類。問題在於我想要具有作用域生命週期的其中一個類需要一個值,該值是請求本身的輸入參數。這是我想要實現的代碼片段。在範圍內配置/註冊depressive注入範圍服務

internal sealed class MyService : StatelessService, IMyService 
{ 
    private IServiceProvider _serviceProvider; 
    private IServiceScopeFactory _scopeFactory; 

    public MyService(StatelessServiceContext context, IServiceProvider serviceProvider) 
     : base(context) 
    { 
     _serviceProvider = serviceProvider; 
     _scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>(); 
    } 

    public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken) 
    { 
     using (var scope = _scopeFactory.CreateScope()) 
     { 
      var requestContext = new RequestContext(correlationId); 
      //IServiceCollection serviceCollection = ??; 
      //serviceCollection.AddScoped<RequestContext>(di => requestContext); 

      var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>(); 
      return await businessLogic.ProcessAsync(request, cancellationToken); 
     } 
    } 
} 

取消令牌已經過去到處,包括不直接使用它,所以它可以傳遞給那些使用它的依賴類,我想避免這樣做,與同一請求上下文。

我的MVC API中存在同樣的問題。我可以創建一箇中間件,它將從HTTP標頭中提取相關標識,所以API控制器不需要像我的服務結構服務那樣處理它。我可以使它工作的一種方法是給RequestContext一個默認的構造函數,並有一個可變的相關ID。但是,在請求期間關聯ID沒有被改變是絕對關鍵的,所以我真的很喜歡在上下文類中擁有隻獲取屬性的安全性。

我現在最好的想法是擁有一個有SetCorrelationId方法的作用域RequestContextFactory,而RequestContext註冊只是簡單地調用工廠來獲取一個實例。如果在設置id之前請求一個新實例,工廠可以拋出一個異常,以確保不會創建無id的上下文,但它不是一個好的解決方案。

如何使用依賴注入框架清理註冊只讀對象,其中值取決於傳入請求?

回答

1

我只是想寫一個RequestContextFactory,因爲我正在寫原始問題,最後我花了時間來測試這個想法。它的代碼實際上比我預期的要少,並且運行良好,所以這將成爲我現在的解決方案。但是,名字工廠是錯誤的。我不知道該怎麼稱呼它。

首先,定義上下文和工廠類。我甚至增加了一些驗證檢查進廠,以確保它的工作我所期望的方式:

public class RequestContext 
{ 
    public RequestContext(string correlationId) 
    { 
     CorrelationId = correlationId; 
    } 

    public string CorrelationId { get; } 
} 

public class RequestContextFactory 
{ 
    private RequestContext _requestContext; 
    private bool _used = false; 

    public void SetContext(RequestContext requestContext) 
    { 
     if (_requestContext != null || requestContext == null) 
     { 
      throw new InvalidOperationException(); 
     } 

     _requestContext = requestContext; 
    } 

    public RequestContext GetContext() 
    { 
     if (_used || _requestContext == null) 
     { 
      throw new InvalidOperationException(); 
     } 

     _used = true; 
     return _requestContext; 
    } 
} 

然後,添加註冊到您的DI容器:

 services.AddScoped<RequestContextFactory>(); 
     services.AddScoped<RequestContext>(di => di.GetRequiredService<RequestContextFactory>().GetContext()); 

最後,服務織物服務方法看起來是這樣

public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken) 
    { 
     using (var scope = _scopeFactory.CreateScope()) 
     { 
      var requestContext = new RequestContext(correlationId); 
      var requestContextFactory = scope.ServiceProvider.GetRequiredService<RequestContextFactory>(); 
      requestContextFactory.SetContext(requestContext); 

      var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>(); 
      return await businessLogic.ProcessAsync(request, cancellationToken); 
     } 
    } 

隼中間件可以是這個樣子

public async Task Invoke(HttpContext httpContext) 
    { 
     RequestContext requestContext = new RequestContext(Guid.NewGuid().ToString()); 
     var factory = httpContext.RequestServices.GetRequiredService<RequestContextFactory>(); 
     factory.SetContext(requestContext); 
     httpContext.Response.Headers["X-CorrelationId"] = requestContext.CorrelationId; 

     await _next(httpContext); 
    } 

然後,只需執行正常的操作,並將RequestContext參數添加到任何需要獲取相關ID的類的構造函數中(或者您在請求上下文中放置的任何其他信息)