Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to DI NSwag auto-generated client

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.

like image 392
mmix Avatar asked Sep 26 '21 08:09

mmix


People also ask

What is difference between NSwag and swashbuckle?

Open API and NSwag provide limited supports for enum , however, Swashbuckle supports even less. NSwag does support namespace and enum, however, not worrking well with the Swagger definition file generated by Swashbuckle.

What is NSwag file?

NSwag is open source project for generating Swagger documents and integrating Swagger UI or ReDoc into ASP.NET Core web APIs. Additionally, NSwag offers approaches to generate C# and TypeScript client code for your API.

What is NSwag C#?

NSwag is a Swagger/OpenAPI 2.0 and 3.0 toolchain for . NET, . NET Core, Web API, ASP.NET Core, TypeScript (jQuery, AngularJS, Angular 2+, Aurelia, KnockoutJS and more) and other platforms, written in C#. The OpenAPI/Swagger specification uses JSON and JSON Schema to describe a RESTful web API.


2 Answers

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.

like image 183
mmix Avatar answered Oct 12 '22 17:10

mmix


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
    {
    }
}
like image 1
DaggeJ Avatar answered Oct 12 '22 18:10

DaggeJ