Proper way to DI NSwag auto-generated client
Asked Answered
P

2

12

What is the preferred way to make a VS connected service (NSwag) injected into classes/controllers. I have found a lot of suggestions on the net to use this form:

services.AddHttpClient<IClient, Client>((provider, client) =>
    {
        client.BaseAddress = new System.Uri("https://some.baseurl/");
    });

However this results in the error

{"errorMessage":"Unable to resolve service for type 'System.String' while attempting to activate 'xxx.Client'."}

This comes from the auto-generated client class in obj, which seems to force a string BaseUrl in constructor, which of course the DI cannot resolve:

public Client(string baseUrl, System.Net.Http.HttpClient httpClient)
{
    BaseUrl = baseUrl;
    _httpClient = httpClient;
    _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}

This base URL is later forced into the url builder code, so it cannot really be bypassed. However, even the solutions on the net which use partial extensions to client classes seem to completely ignore baseUrl in auto-gen class (like here). As if it does not exist (which is weird, has the NSwag before generated different constructors?). The class is being generated via csproj:

  <ItemGroup>
    <OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" Namespace="xxx" ClassName="Client">
      <SourceUri>https://localhost:44353/swagger/v1/swagger.json</SourceUri>
    </OpenApiReference>
  </ItemGroup>

And this results in the targeted build call:

2>GenerateNSwagCSharp:
2>  "C:\.<path>./tools/Win/NSwag.exe" openapi2csclient /className:Client /namespace:xxx /input:"C:\<projpath>\OpenAPIs\swagger.json" /output:"obj\swaggerClient.cs"
2>NSwag command line tool for .NET 4.6.1+ WinX64, toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))

So, how is this being done? potentially, without creating another proxy class for a proxy class, I would rather that DI handles my object lifetimes. I would also like to avoid NSwagStudio if possible and would like to keep the tooling supplied by VS.

Palma answered 26/9, 2021 at 8:50 Comment(2)
Will you please stop removing my visual-studio-2022 tag. This has worked perfectly on 2019, and its a regression issue.Palma
I had the same problem but was able to get DI to work by unchecking "Use the base URL for the request" in NSwagStudio.Arv
P
19

Ok, I actually solved this problem by poking around through OpenApiReference, but it requires manual modification of csproj file. Additional Options node has to be added to OpenApiReference item group, to instruct NSwag to NOT expose BaseUrl and to also generate an interface, which eases the work with setting up DI without additional code.

Visual Studio team should really add these two checkboxes to Connected Services screens/configuration for OpenAPI.

<ItemGroup>
  <OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" Namespace="xxx" ClassName="Client">
    <SourceUri>https://localhost:44353/swagger/v1/swagger.json</SourceUri>
    <Options>/UseBaseUrl:false /GenerateClientInterfaces:true</Options>
  </OpenApiReference>
</ItemGroup>

Now there is only an HttpClient constructor, andNSwag client proxy uses base address from it, so AddHttpClient works properly through DI.

Palma answered 26/9, 2021 at 13:3 Comment(1)
Visual Studio team should really add these two checkboxes to Connected Services screens/configuration for OpenAPI. Have you added a feature request for Visual Studio via Send Feedback -> Suggest a feature in Visual Studio? If yes, could you post a link for me to follow and take additional steps to help complete this functionality?Zachariah
I
3

As the generated class is marked partial you can provide an additional constructor and mark it with the [ActivatorUtilitiesConstructor] attribute. Applying the attribute makes sure that constructor is used with Dependency Injection. You can also implement the interface in your partial extension. Here's an example of this;

public partial class MyApiClient : IMyApiClient
{
    [ActivatorUtilitiesConstructor] // This ctor will be used by DI
    public MyApiClient(HttpClient httpClient, IOptions<MyApiClientOptions> clientOptions)
    : this(clientOptions.Value.Url, httpClient) // Call generated ctor
    {
    }
}
Interested answered 3/12, 2021 at 11:27 Comment(1)
I accept it as a viable solution, however, I do not think I should have to write code to fix regression issues in IDE. swagger itself supports this codegen process, this is the issue with Visual Studio 2022 no longer supporting it. I have opened a ticket with them, but as usual, they are ignoring it.Palma

© 2022 - 2024 — McMap. All rights reserved.