Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating IServiceCollection extension methods for registering Refit clients #174

Merged
merged 29 commits into from
Oct 6, 2023

Conversation

christianhelle
Copy link
Owner

@christianhelle christianhelle commented Oct 4, 2023

Use case:

I'm too lazy to setup my Refit clients in using HttpClientFactory and I want all this bootstrapping code written for me

Example OpenAPI spec:

openapi: '3.0.0'
paths:
  /foo/{id}:
    delete:
      tags:
      - 'Foo'
      operationId: 'Delete foo'
      description: 'Delete the specified foo'
      parameters:
        - in: 'path'
          name: 'id'
          description: 'Foo ID'
          required: true
          schema:
            type: 'string'
      responses:
        '200':
          description: 'successful operation'

.refitter settings file

{
  "openApiPath": "openapi.yaml",
  "multipleInterfaces": "ByEndpoint",
  "dependencyInjectionSettings": {
    "baseUrl": "https://example.com/api/v3",
    "usePolly": true
  }
}

Generated code:

namespace GeneratedCode
{
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface IDeleteFooEndpoint
    {
        /// <summary>
        /// Delete the specified foo
        /// </summary>
        [Delete("/foo/{id}")]
        Task Execute(string id);
    }
}

namespace GeneratedCode
{
    using System;
    using Microsoft.Extensions.DependencyInjection;
    using Polly;
    using Polly.Contrib.WaitAndRetry;
    using Polly.Extensions.Http;

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection ConfigureRefitClients(this IServiceCollection services)
        {
            services
                .AddRefitClient<IDeleteFooEndpoint>()
                .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://example.com/api/v1"))
                .AddPolicyHandler(
                    HttpPolicyExtensions
                        .HandleTransientHttpError()
                        .WaitAndRetryAsync(
                            Backoff.DecorrelatedJitterBackoffV2(
                                TimeSpan.FromSeconds(1),
                                6)));

            return services;
        }
    }
}

Example usage of generated code:

using Refit;
using GeneratedCode;

var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureRefitClients();

var app = builder.Build();
app.MapDelete(
        "/foo/{id:int}",
        async (IDeleteFooEndpoint endpoint, int id) =>
        {
            try
            {
                return Results.Ok(await endpoint.Execute(id));
            }
            catch (ApiException e)
            {
                return Results.StatusCode((int)e.StatusCode);
            }
        })
    .WithName("DeleteFoo")
    .WithOpenApi();

app.Run();

Using HttpMessageHandlers

The changes here also support adding existing HttpMessageHandler implementations to the Refit client by specifying "httpMessageHandlers" it in the .refitter settings

{
  "openApiPath": "openapi.yaml",
  "multipleInterfaces": "ByEndpoint",
  "dependencyInjectionSettings": {
    "baseUrl": "https://example.com/api/v3",
    "usePolly": true,
    "httpMessageHandlers": [ "AuthorizationMessageHandler", "TelemetryMessageHandler" ]
  }
}

Which would generate the following:

namespace GeneratedCode
{
    using System;
    using Microsoft.Extensions.DependencyInjection;
    using Polly;
    using Polly.Contrib.WaitAndRetry;
    using Polly.Extensions.Http;

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection ConfigureRefitClients(this IServiceCollection services)
        {
            services
                .AddRefitClient<IDeleteFooEndpoint>()
                .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://example.com/api/v1"))
                .AddHttpMessageHandler<AuthorizationMessageHandler>()
                .AddHttpMessageHandler<TelemetryMessageHandler>()
                .AddPolicyHandler(
                    HttpPolicyExtensions
                        .HandleTransientHttpError()
                        .WaitAndRetryAsync(
                            Backoff.DecorrelatedJitterBackoffV2(
                                TimeSpan.FromSeconds(1),
                                6)));

            return services;
        }
    }
}

The generate IServiceCollection methods above require that the specified HttpMessageHandler implementations are already registered to the IoC container

This commit introduces a new property to RefitGeneratorSettings, 'ServiceCollectionRegistration'. This new setting controls whether generated interfaces are registered in a .NET Core DI container. The setting is included in the 'GenerateCommand' and 'Settings' classes, and can be controlled via the '--service-collection-registration' command line option.
This commit removes the serviceCollectionRegistration setting from both GenerateCommand.cs and Settings.cs. The reason for removal is to simplify the generation process by no longer generating code for registering interfaces with Microsoft.Extensions.DependencyInjection. The decision is due to the fact that such registration should be left to the consuming project, allowing for more flexibility and control.
This commit includes the implementation of a new DependencyInjectionGenerator class which is used to generate Dependency Injection configurations for Refit clients in a service collection. Furthermore, we also added tests to verify the code produced by the generator. This change makes it easier to set up Refit clients with built-in Polly support and multiple HttpMessageHandlers.
DependencyInjectionGenerator class was altered to static. The modification ensures the class has just one instance throughout the application, enhancing the efficiency and minimizing resource usage. This will also avoid potential multithreading issues.
Updated the type of the 'interfaceNames' parameter from IEnumerable<string> to string[] in the Generate method in DependencyInjectionGenerator.cs. Also, an additional condition is added in the if block to check if 'interfaceNames' is empty. This was necessary because the subsequent code execution assumes that 'interfaceNames' contains at least one item and will fail otherwise.
Added functionality to record interface names in Refitter.Core. Now, multiple interface generator and interface generator have been modified to capture interface names during interface generation. This will allow us to use the interface names for further improvements or capabilities in the future.
Added DependencyInjectionGenerator invocation to the RefitGenerator class to support dependency injection setup. Refactored `GenerateCode()` function to make it more legible. Also, extended unit tests in SwaggerPetstoreTests to validate the code generation with dependency injection settings. These changes aim to enhance the code testability and maintainability by introducing dependency injection setup and verifying its correct usage.
In RefitGenerator.cs, the code is improved with increase in code readability by adding a new line before appending new namespace imports to meet the code standard.

In ProjectFileContents.cs, new package references have been added to enhance the project functionalities with updated version of DependencyInjection and Polly.

In MultipleInterfacesByEndpointWithIoCTests, the newly added file runs specific test scenarios to verify the Multiple Interfaces By Endpoint functionality.

In DependencyInjectionGenerator.cs, adjusted the code formatting to reduce redundancy and complexity. Inclusion of certain return statements and new lines are to meet the coding standards. This will help make the code more readable and understandable.
Updated the API endpoints in the tests to support 'delete' operation instead of 'get'. The changes are made to align the tests with the new API specifications where the focus is more on delete operations. Also, a new test is added to check if 'IServiceCollectionExtensions' are generated properly. Module 'RefitGeneratorSettings' was updated to avoid generating contracts during testing.
This commit updates the 'Remove' method in the DependencyInjectionGenerator.cs file to remove two characters instead of three from the end of the 'code' StringBuilder. It then adds a new line to maintain consistent formatting and to ensure that the "return services;" line is properly separated from the preceding lines of code.
Removed the endpoint tests for `IGetFooDetailsEndpoint`, `IGetAllFoosEndpoint`, `IGetBarDetailsEndpoint`, `IGetAllBarsEndpoint` and added new tests for `IDeleteFooEndpoint` and `IDeleteBarEndpoint`. The changes represent a reorientation of testing towards delete functionality instead of get methods.
Furthermore, the name of `ServiceCollectionExtensions` was changed to `IServiceCollectionExtensions` in `DependencyInjectionGenerator.cs` for consistency with Interface naming conventions.
@christianhelle christianhelle added the enhancement New feature, bug fix, or request label Oct 4, 2023
@codecov
Copy link

codecov bot commented Oct 4, 2023

Codecov Report

Merging #174 (1b4adfc) into main (54a9b90) will increase coverage by 0.33%.
Report is 5 commits behind head on main.
The diff coverage is 100.00%.

❗ Current head 1b4adfc differs from pull request most recent head 8461a77. Consider uploading reports for the commit 8461a77 to get more accurate results

@@            Coverage Diff             @@
##             main     #174      +/-   ##
==========================================
+ Coverage   98.37%   98.70%   +0.33%     
==========================================
  Files          41       47       +6     
  Lines        1353     1626     +273     
==========================================
+ Hits         1331     1605     +274     
  Misses          6        6              
+ Partials       16       15       -1     
Flag Coverage Δ
unittests 98.70% <100.00%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
src/Refitter.Core/DependencyInjectionGenerator.cs 100.00% <100.00%> (ø)
src/Refitter.Core/RefitGeneratedCode.cs 100.00% <100.00%> (ø)
src/Refitter.Core/RefitGenerator.cs 99.35% <100.00%> (+0.02%) ⬆️
src/Refitter.Core/RefitGeneratorSettings.cs 100.00% <100.00%> (ø)
src/Refitter.Core/RefitInterfaceGenerator.cs 100.00% <100.00%> (ø)
...itter.Core/RefitMultipleInterfaceByTagGenerator.cs 94.28% <100.00%> (+1.74%) ⬆️
...c/Refitter.Core/RefitMultipleInterfaceGenerator.cs 100.00% <100.00%> (ø)
...efitter.Tests/DependencyInjectionGeneratorTests.cs 100.00% <100.00%> (ø)
...atedMultipleInterfaceByTagEndpointsSkippedTests.cs 100.00% <100.00%> (ø)
...amples/MultipleInterfacesByEndpointWithIoCTests.cs 100.00% <100.00%> (ø)
... and 1 more

@christianhelle christianhelle marked this pull request as ready for review October 4, 2023 20:05
@christianhelle christianhelle self-assigned this Oct 4, 2023
@christianhelle
Copy link
Owner Author

You might find this interesting @Noblix

The changes were made in DependencyInjectionGenerator and associated test case. The changes allow for the base URL to be null, which makes it possible to configure Refit clients without a baseUrl. Earlier, it was mandatory to provide a baseUrl but with these changes, we can configure clients without specifying any baseUrl. These changes provide flexibility in configuring Refit clients and hence improve the usability of the library.
This change allows 'FirstBackoffRetryInSeconds' property in the 'RefitGeneratorSettings' to accept double values, rather than integers only. This is done to provide more precision for backoff retry times.
Modified DependencyInjectionGeneratorTests to include Polly retry parameters. These additions will allow us to test and validate the Polly retry functionality within our Dependency Injection framework.
This commit adds a new test class, DeprecatedMultipleInterfaceByTagEndpointsSkippedTests, which verifies that deprecated endpoints tagged by interfaces are correctly skipped when generating code. This ensures that users do not unintentionally use deprecated API endpoints.
Removed unnecessary white spaces and added a new unit test 'Can_Generate_Without_BaseUrl'. This unit test will validate if Dependency Injection code can generate without providing a BaseUrl. This allows users to provide the BaseUrl at runtime.
Updated README files in a few directories to reflect new settings for dependency injection and Polly configuration. This includes detailed descriptions and example of usage for each new setting such as `baseUrl`, `usePolly`, `pollyMaxRetryCount`, and more. These updates provide users with additional control on HttpClient configuration, retry policies, and service collection configurations.

[skip ci]
A new class `TelemetryDelegatingHandler` is added to track outbound HTTP requests. It logs every outbound HTTP request along with its method and URL. It is registered as a transient dependency and will be added to the HTTP handlers via the `AddRefitClient` extension method. Changes have also been made in the `MinimalApi.csproj` and `Program.cs` files to reflect this update. Telemetry can be very useful to gain insights, especially about bottlenecks and to improve performance. Newline issue at the end of the files is also addressed.

[skip ci]
This commit adds two new fields to the "dependencyInjectionSettings" in the petstore refitter. 'pollyMaxRetryCount' is added with a value of 3, to specify the maximum number of retries. Also, 'firstBackoffRetryInSeconds' is added with a value of 0.5 to indicate the backoff delay before the first retry. These settings will ensure more reliable request handling by implementing retries in case of failure.

[skip ci]
@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 5, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
0.0% 0.0% Duplication

@christianhelle christianhelle merged commit c0607de into main Oct 6, 2023
@christianhelle christianhelle deleted the ioc-registration branch October 6, 2023 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature, bug fix, or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant