C
C#2mo ago
Mile

Managing Scoped Service Lifecycles with a Singleton Factory in .NET: Best Practices?

Hello everyone, I'm working on implementing a factory pattern for payment services in a .NET application and I'm unsure about properly managing the lifecycle of services. Here's the relevant part of my code:
public class ProviderServiceFactory
{
private readonly IServiceProvider _serviceProvider;
public ProviderServiceFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IPaymentService CreateProviderService(ProviderDataDto providerDataDto)
{
var scoped = _serviceProvider.CreateScope();
var service = providerDataDto.IntegrationName switch
{
IntegrationName.PayPal => scoped.ServiceProvider.GetRequiredService<PayPalService>(),
IntegrationName.Stripe => scoped.ServiceProvider.GetRequiredService<StripeService>(),
_ => throw new ArgumentException("Invalid payment service type.")
};
service.Initialize(providerDataDto.CompanyMarketIntegrationKeyValues);
return service;
}
}
public class ProviderServiceFactory
{
private readonly IServiceProvider _serviceProvider;
public ProviderServiceFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IPaymentService CreateProviderService(ProviderDataDto providerDataDto)
{
var scoped = _serviceProvider.CreateScope();
var service = providerDataDto.IntegrationName switch
{
IntegrationName.PayPal => scoped.ServiceProvider.GetRequiredService<PayPalService>(),
IntegrationName.Stripe => scoped.ServiceProvider.GetRequiredService<StripeService>(),
_ => throw new ArgumentException("Invalid payment service type.")
};
service.Initialize(providerDataDto.CompanyMarketIntegrationKeyValues);
return service;
}
}
And here's how I'm using it in a handler:
public async Task<Result<Uri>> Handle(AddPaymentMethodCommand command, CancellationToken cancellationToken)
{
// Validation and other logic..
Result<ProviderDataDto> providerDataResult = await _mediator.Send(new GetProviderDataQuery(command.CompanyMarketId, INTEGRATION_TYPE), cancellationToken);
if (!providerDataResult.IsSuccess)
{
return Result<Uri>.Failure(providerDataResult.Error);
}
IPaymentService paymentService = _providerServiceFactory.CreateProviderService(providerDataResult.Value);
// Create payment method, save to database...
Result<Uri> result = await paymentService.InitiatePaymentMethodTokenization(paymentMethod);
// Handle result...
}
public async Task<Result<Uri>> Handle(AddPaymentMethodCommand command, CancellationToken cancellationToken)
{
// Validation and other logic..
Result<ProviderDataDto> providerDataResult = await _mediator.Send(new GetProviderDataQuery(command.CompanyMarketId, INTEGRATION_TYPE), cancellationToken);
if (!providerDataResult.IsSuccess)
{
return Result<Uri>.Failure(providerDataResult.Error);
}
IPaymentService paymentService = _providerServiceFactory.CreateProviderService(providerDataResult.Value);
// Create payment method, save to database...
Result<Uri> result = await paymentService.InitiatePaymentMethodTokenization(paymentMethod);
// Handle result...
}
The ProviderServiceFactory is registered as a Singleton, while the payment services (like PayPalService) are registered as Scoped.
23 Replies
Mile
MileOP2mo ago
My question is: How can I ensure that the scope is properly disposed of after using the service in the Handle method? Is there a better way to implement this pattern that would better respect the Scoped lifecycle of the services? Any insights or best practices would be greatly appreciated.
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
SleepWellPupper
SleepWellPupper2mo ago
I mean if you want to insist on the factory you can register it as scoped, no? The service provider injected would be scoped to that as well, you could avoid creating a scope yourself.
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
SleepWellPupper
SleepWellPupper2mo ago
scope would be destroyed before even getting the instance, I don't agree with this solution.
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
Yeah, that's the issue, I don't know which PaymentProvider Service I need to instantiate each call of method "Handle"
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
I tried registering Add<IPaymentService> and that didn't work will try this
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
2 for now, but in the future it is going to be more
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
closer to 5-10
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
I just need to pass initialization parameters within Initialize:
paymentService.Initialize(providerDataDto.CompanyMarketIntegrationKeyValues);
paymentService.Initialize(providerDataDto.CompanyMarketIntegrationKeyValues);
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
hm, I need to do it once, these parameters are store id, store key (for client)
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
these parameters don't change, so once per application start, but payment service lifecycle is per request (scoped)
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
yes
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View
Mile
MileOP2mo ago
parameters that I've passed within Initialize will be fetched from database on each request I'll register each payment provider service as singleton and return reference to them using ServiceResolver
public class ServiceResolver
{
private readonly IServiceProvider _serviceProvider;

public ServiceResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public IPaymentService ResolvePaymentService(IntegrationName integrationName)
{
return integrationName switch
{
IntegrationName.PayPal => _serviceProvider.GetRequiredService<PayPalService>(),
IntegrationName.Stripe => _serviceProvider.GetRequiredService<StripeService>(),
_ => throw new ArgumentException("Invalid payment service type.")
};
}
}
public class ServiceResolver
{
private readonly IServiceProvider _serviceProvider;

public ServiceResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public IPaymentService ResolvePaymentService(IntegrationName integrationName)
{
return integrationName switch
{
IntegrationName.PayPal => _serviceProvider.GetRequiredService<PayPalService>(),
IntegrationName.Stripe => _serviceProvider.GetRequiredService<StripeService>(),
_ => throw new ArgumentException("Invalid payment service type.")
};
}
}
Want results from more Discord servers?
Add your server