Skip to content

V7 Generator/Revise dependency injection extension methods#1242

Open
Kwok-he-Chu wants to merge 15 commits intomainfrom
v7-generator/revise-dependency-injection-extension-methods
Open

V7 Generator/Revise dependency injection extension methods#1242
Kwok-he-Chu wants to merge 15 commits intomainfrom
v7-generator/revise-dependency-injection-extension-methods

Conversation

@Kwok-he-Chu
Copy link
Member

@Kwok-he-Chu Kwok-he-Chu commented Feb 4, 2026

Description

This PR addresses Dependency Injection for the .NET Library. Developers can now configure and inject the service they want to use.

Before:
It wasn't possible to initialize any Service with the correct URL by injecting them as follows:

services.AddSingleton<IPaymentsService, PaymentsService>()`

the underlying HttpClient wouldn't know the URL at this point. The work-around for developers (on the consuming-side) would be:

services.AddSingleton<IPaymentsService, PaymentsService>()
     .AddHttpClient(client => client.BaseAddress => Extensions.ConstructLiveUrl(adyenOptions /* contains the Environment and LiveEndpointUrl*/, "https://checkout-test.adyen.com/v71")); // Example

This is not ideal. This PR addresses this:

After:
The BaseURL is now constructed once in the constructor of the Service. For this to work, we introduced AdyenOptionsProvider to make this work for all services.


Improvements

  • ConstructUrl(AdyenOptions, "example https://checkout-test.adyen.com/v71") is called inside the Service-constructor
    • Introduced AdyenOptionsProvider to retrieve the AdyenOptions used to Construct the Url based on the Environment and LiveEndpointUrlPrefix
  • Allow developers to pass ServiceLifeTime (singleton, scoped, transients), default: Singleton
  • Introduce Configure{{apiName}} to allow developers to inject any service (see tested scenarios), example where: {{apiName}} = Checkout
  • Introduce Configure{{apiName}}Defaults to inject all services automatically (see tested scenarios), example where: {{apiName}} = Checkout
  • Added tests to Checkout and AcsWebhooks
  • Updated api_doc.mustache with correct snippets
  • Better documentation in JsonSerializerOptionsProvider

Tested scenarios

Extending the tests in [1] the CheckoutAPI (covering APIs) and [2] AcsWebhooks (covering the webhooks).

Dependency Injection using Configure{{ApiName}}

  1. Configure using the ConfigureCheckout-method and allow the developer to dependency inject individual services from the Checkout API:
IHost host = Host.CreateDefaultBuilder()
    .ConfigureCheckout((context, services, config) => // ConfigureCheckout(...)
    {
        config.ConfigureAdyenOptions(options =>
        {
            // Set your ADYEN_API_KEY or use get it from your environment using context.Configuration["ADYEN_API_KEY"].
            options.AdyenApiKey = context.Configuration["ADYEN_API_KEY"];

            // Set your environment, e.g. `Test` or `Live`.
            options.Environment = AdyenEnvironment.Test;

            // For the Live environment, additionally include your LiveEndpointUrlPrefix.
            options.LiveEndpointUrlPrefix = "your-live-endpoint-url-prefix";
        });

        // Inject individual services that you need.
        services.AddSingleton<IPaymentsService, PaymentsService>().AddHttpClient(); // Add to IOC container
    })
    .Build();

Dependency Injection using Configure{{ApiName}}Defaults

  1. Configure using the ConfigureCheckoutDefaults-method and automatically injects all services from the Checkout API:
IHost host = Host.CreateDefaultBuilder()
    .ConfigureCheckoutDefaults((context, services, config) => // // ConfigureCheckoutDefaults(...)
    {
        config.ConfigureAdyenOptions(options =>
        {
            // Set your ADYEN_API_KEY or use get it from your environment using context.Configuration["ADYEN_API_KEY"].
            options.AdyenApiKey = context.Configuration["ADYEN_API_KEY"];

            // Set your environment, e.g. `Test` or `Live`.
            options.Environment = AdyenEnvironment.Test;

            // For the Live environment, additionally include your LiveEndpointUrlPrefix.
            options.LiveEndpointUrlPrefix = "your-live-endpoint-url-prefix";
        });
    })
    .Build();

Roll-out plan

…th a ServiceLifeTime

Handles both webhook handler and services
… to api.mustache

HostConfiguration.mustache also no longer adds all services as a singleton, this is moved to ServiceCollectionExtension.mustache
…esulting in no event-triggers

This allows developers to initialize the Service standalone, but gives the option to override certain behavior
…uilder

HostBuilder: Configures configuration
Services: initializes the implementation of services
Allow the Options to be fetched through a Provider class, rather than injection it directly.
Fix comments JsonSerializerOptionsProvider with correct references to the classes
@Kwok-he-Chu Kwok-he-Chu self-assigned this Feb 4, 2026
@Kwok-he-Chu Kwok-he-Chu requested review from a team as code owners February 4, 2026 21:09
@gemini-code-assist
Copy link

Summary of Changes

Hello @Kwok-he-Chu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the dependency injection patterns within the .NET Library, making service configuration more intuitive and less error-prone. By introducing an AdyenOptionsProvider and new IHostBuilder extension methods, it automates the construction of HttpClient base URLs and offers streamlined options for registering services, either individually or as a complete set for an API. These changes aim to improve developer experience by reducing boilerplate and enhancing the clarity of service setup.

Highlights

  • Streamlined Base URL Configuration: The BaseURL for HttpClient instances is now automatically constructed within the service constructor using an AdyenOptionsProvider, eliminating the need for manual AddHttpClient calls by developers.
  • New Default Service Injection Method: Introduced Configure{{ApiName}}Defaults to automatically register all services for a given API, simplifying setup for common use cases.
  • Flexible Service Injection: Developers retain the ability to inject individual services using Configure{{ApiName}} for fine-grained control over their dependencies.
  • Centralized Options Management: The new AdyenOptionsProvider centralizes access to AdyenOptions, ensuring consistent configuration across services.
  • Enhanced Test Coverage and Documentation: Integration tests were extended for CheckoutAPI and AcsWebhooks, and documentation for api_doc.mustache and JsonSerializerOptionsProvider was improved.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Adyen.IntegrationTest/Checkout/PaymentsServiceIntegrationTest.cs
    • Refactored test setup to use a class-level IHost and _paymentsApiService.
    • Integrated AddAllCheckoutServices for simplified checkout service setup.
    • Updated HttpClient configuration in tests to align with new DI patterns.
    • Temporarily disabled one test method by renaming it to xGiven_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds.
  • Adyen.Test/AcsWebhooks/AcsWebhooksTest.cs
    • Added necessary using statements for Checkout.Extensions, Core.Auth, and Core.Options.
    • Configured AdyenHmacKey via AdyenOptions and registered AcsWebhooksHandler.
    • Introduced a new test to verify HMAC key token provision.
  • Adyen.Test/BalanceControl/BalanceControlTest.cs
    • Integrated AddBalanceControlService into existing test setups for Balance Control services.
  • Adyen.Test/BalancePlatform/AccountHolders/AccountHolderTest.cs
    • Integrated AddAccountHoldersService into existing test setups for Account Holders services.
  • Adyen.Test/Checkout/CheckoutTest.cs
    • Added new using statements for Checkout.Services and Core.Auth.
    • Introduced several new unit tests to validate new DI patterns for API key tokens, AddAllCheckoutServices, ConfigureCheckoutDefaults, and both explicit and implicit PaymentsServiceEvents injection.
  • Adyen.Test/Checkout/PaymentsTest.cs
    • Updated HttpClient configuration in tests to use the new AddPaymentsService method.
    • Integrated AddPaymentsService into URL verification tests.
  • Adyen.Test/PaymentsApp/PaymentsAppServiceTest.cs
    • Integrated AddPaymentsAppService into existing test setups for Payments App services.
  • Adyen/TransactionWebhooks/Client/AdyenOptionsProvider.cs
    • New file: Defines a class to encapsulate and provide AdyenOptions for dependency injection.
  • templates-v7/csharp/libraries/generichost/AdyenOptionsProvider.mustache
    • New template file for generating the AdyenOptionsProvider class.
  • templates-v7/csharp/libraries/generichost/HostConfiguration.mustache
    • Removed static BASE_URL field and the Add{{apiName}}HttpClients method.
    • Integrated AdyenOptionsProvider registration and simplified token provider registrations within ConfigureAdyenOptions.
  • templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache
    • Updated XML documentation for Configure{{apiName}} to clarify its role in initializing HostConfiguration without adding services by default.
    • Introduced Configure{{apiName}}Defaults, a new extension method for initializing HostConfiguration and automatically adding all API services.
  • templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache
    • Refactored service registration extensions, removing the old Add{{apiName}}Services method.
    • Introduced new methods: AddAll{{apiName}}Services, Add{{apiName}}Handler, and Add{{classname}} for more granular and flexible service registration.
  • templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache
    • Improved XML documentation for clarity, specifically adding references.
  • templates-v7/csharp/libraries/generichost/api.mustache
    • Modified API service constructors to accept AdyenOptionsProvider for base URL construction.
    • Made the Events property nullable and added null-conditional checks for event invocation.
  • templates-v7/csharp/libraries/generichost/api_doc.mustache
    • Updated API documentation snippets to reflect the new Configure{{apiName}} and Configure{{apiName}}Defaults usage examples.
Activity
  • The pull request introduces new dependency injection patterns, tested through two main scenarios: configuring individual services using Configure{{ApiName}} and automatically injecting all services with Configure{{ApiName}}Defaults.
  • Existing integration tests in CheckoutAPI and AcsWebhooks were extended to cover these new DI configurations.
  • Documentation for api_doc.mustache was updated with new code snippets, and JsonSerializerOptionsProvider received improved documentation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves the dependency injection experience for the library by introducing more idiomatic .NET patterns. The new Configure{{ApiName}} and Configure{{ApiName}}Defaults extension methods, along with the individual Add{{Service}} methods, provide developers with more flexibility and reduce boilerplate code. The core change of handling BaseAddress construction within the service constructors is a great improvement.

My review includes a few suggestions to enhance the changes further:

  • Addressing a test that appears to be disabled in a non-standard way.
  • Refactoring duplicated code in the new tests to improve maintainability.
  • Cleaning up unused parameters in one of the new extension methods.

Comment on lines 29 to +34
hostBuilder.ConfigureServices((context, services) =>
{
HostConfiguration hostConfiguration = new HostConfiguration(services);

hostConfigurationOptions(context, services, hostConfiguration);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The httpClientOptions and httpClientBuilderOptions parameters of the Configure{{apiName}} method are no longer used within its body. This can be misleading for developers. Consider removing them from the method signature to reflect that HttpClient configuration should now be done via the new IServiceCollection extension methods.


[TestMethod]
public async Task Given_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds()
public async Task xGiven_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test method Given_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds has been renamed to xGiven_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds, which effectively disables it. Is this intentional? If the test needs to be temporarily disabled, it's better to use the [Ignore] attribute for clarity, along with a comment explaining why it's ignored. For example:

[TestMethod]
[Ignore("Reason for ignoring this test")]
public async Task Given_Payments_When_CardDetails_With_Empty_RequestOptions_Succeeds()
{
    // ...
}

If this test is no longer needed, it should be removed.

Comment on lines +57 to +168
public void Given_ConfigureCheckout_When_AddAllCheckoutServices_Is_Called_Result_Is_Not_Null()
{
// Arrange
IHost testHost = Host.CreateDefaultBuilder()
.ConfigureCheckout((context, services, config) =>
{
config.ConfigureAdyenOptions(options =>
{
options.Environment = AdyenEnvironment.Test;
options.AdyenApiKey = "ADYEN_API_KEY";
});

services.AddAllCheckoutServices();
})
.Build();

// Act

// 1. Services
var donationsService = testHost.Services.GetRequiredService<IDonationsService>();
var modificationsService = testHost.Services.GetRequiredService<IModificationsService>();
var ordersService = testHost.Services.GetRequiredService<IOrdersService>();
var paymentLinksService = testHost.Services.GetRequiredService<IPaymentLinksService>();
var paymentsService = testHost.Services.GetRequiredService<IPaymentsService>();
var recurringService = testHost.Services.GetRequiredService<IRecurringService>();
var utilityService = testHost.Services.GetRequiredService<IUtilityService>();

// 2. ServiceEvents
var donationsServiceEvents = testHost.Services.GetRequiredService<DonationsServiceEvents>();
var modificationsServiceEvents = testHost.Services.GetRequiredService<ModificationsServiceEvents>();
var ordersServiceEvents = testHost.Services.GetRequiredService<OrdersServiceEvents>();
var paymentLinksServiceEvents = testHost.Services.GetRequiredService<PaymentLinksServiceEvents>();
var paymentsServiceEvents = testHost.Services.GetRequiredService<PaymentsServiceEvents>();
var recurringServiceEvents = testHost.Services.GetRequiredService<RecurringServiceEvents>();
var utilityServiceEvents = testHost.Services.GetRequiredService<UtilityServiceEvents>();

// Assert

// 1. Services
Assert.IsNotNull(donationsService);
Assert.IsNotNull(modificationsService);
Assert.IsNotNull(ordersService);
Assert.IsNotNull(paymentLinksService);
Assert.IsNotNull(paymentsService);
Assert.IsNotNull(recurringService);
Assert.IsNotNull(utilityService);

// 2. ServiceEvents
Assert.IsNotNull(donationsServiceEvents);
Assert.IsNotNull(modificationsServiceEvents);
Assert.IsNotNull(ordersServiceEvents);
Assert.IsNotNull(paymentLinksServiceEvents);
Assert.IsNotNull(paymentsServiceEvents);
Assert.IsNotNull(recurringServiceEvents);
Assert.IsNotNull(utilityServiceEvents);
}

[TestMethod]
public void Given_ConfigureCheckoutDefaults_When_Configure_AdyenOptions_Is_Called_Result_Is_Not_Null()
{
// Arrange
IHost testHost = Host.CreateDefaultBuilder()
.ConfigureCheckoutDefaults((context, services, config) =>
{
config.ConfigureAdyenOptions(options =>
{
options.Environment = AdyenEnvironment.Test;
options.AdyenApiKey = "ADYEN_API_KEY";
});
})
.Build();

// Act

// 1. Services
var donationsService = testHost.Services.GetRequiredService<IDonationsService>();
var modificationsService = testHost.Services.GetRequiredService<IModificationsService>();
var ordersService = testHost.Services.GetRequiredService<IOrdersService>();
var paymentLinksService = testHost.Services.GetRequiredService<IPaymentLinksService>();
var paymentsService = testHost.Services.GetRequiredService<IPaymentsService>();
var recurringService = testHost.Services.GetRequiredService<IRecurringService>();
var utilityService = testHost.Services.GetRequiredService<IUtilityService>();

// 2. ServiceEvents
var donationsServiceEvents = testHost.Services.GetRequiredService<DonationsServiceEvents>();
var modificationsServiceEvents = testHost.Services.GetRequiredService<ModificationsServiceEvents>();
var ordersServiceEvents = testHost.Services.GetRequiredService<OrdersServiceEvents>();
var paymentLinksServiceEvents = testHost.Services.GetRequiredService<PaymentLinksServiceEvents>();
var paymentsServiceEvents = testHost.Services.GetRequiredService<PaymentsServiceEvents>();
var recurringServiceEvents = testHost.Services.GetRequiredService<RecurringServiceEvents>();
var utilityServiceEvents = testHost.Services.GetRequiredService<UtilityServiceEvents>();

// Assert

// 1. Services
Assert.IsNotNull(donationsService);
Assert.IsNotNull(modificationsService);
Assert.IsNotNull(ordersService);
Assert.IsNotNull(paymentLinksService);
Assert.IsNotNull(paymentsService);
Assert.IsNotNull(recurringService);
Assert.IsNotNull(utilityService);

// 2. ServiceEvents
Assert.IsNotNull(donationsServiceEvents);
Assert.IsNotNull(modificationsServiceEvents);
Assert.IsNotNull(ordersServiceEvents);
Assert.IsNotNull(paymentLinksServiceEvents);
Assert.IsNotNull(paymentsServiceEvents);
Assert.IsNotNull(recurringServiceEvents);
Assert.IsNotNull(utilityServiceEvents);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test methods Given_ConfigureCheckout_When_AddAllCheckoutServices_Is_Called_Result_Is_Not_Null and Given_ConfigureCheckoutDefaults_When_Configure_AdyenOptions_Is_Called_Result_Is_Not_Null contain a large block of identical code for resolving and asserting services. This duplication can make the tests harder to maintain.

Consider extracting the common assertion logic into a private helper method to reduce duplication and improve readability.

For example, you could create a method like this:

private void AssertAllCheckoutServicesAreRegistered(IHost host)
{
    // 1. Services
    Assert.IsNotNull(host.Services.GetRequiredService<IDonationsService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IModificationsService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IOrdersService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IPaymentLinksService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IPaymentsService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IRecurringService>());
    Assert.IsNotNull(host.Services.GetRequiredService<IUtilityService>());
    
    // 2. ServiceEvents
    Assert.IsNotNull(host.Services.GetRequiredService<DonationsServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<ModificationsServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<OrdersServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<PaymentLinksServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<PaymentsServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<RecurringServiceEvents>());
    Assert.IsNotNull(host.Services.GetRequiredService<UtilityServiceEvents>());
}

And then call it from both test methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant