Fix Autorest Problems Generating a C# Client with .NET 6

This is a follow up post for the post Fix Autorest Problems when Generating Client with Swashbuckle swagger.json I wrote last year. I was curious what had changed with new versions of .net and the latest autorest code generator.
The experience was much smoother this time around! Much fewer changes were necessary to generate a client.

I think that the main change is a use of the new CSharp code generator as default in autorest.

To get started I created a standard netcore API project with OpenApi enabled. It contains two simple controllers. Here is a repository with the project: https://github.com/AngelaE/open-api-netcore6-swashbuckle

Fix the OpenAPI Specification – ERROR (DuplicateOperation)

As expected the client generation throws an error. But on the upside there is only one this time!

The new CSharp generator seems to auto generate operation IDs. However, they are clashing in our case, because the get and post method have the same name.

ERROR (DuplicateOperation): Duplicate Operation '' > 'Authors' detected(This is most likely due to 2 operation using the same 'operationId' or 'tags'). Duplicates have those paths:
  - get /Authors
  - post /Authors
  - get /Authors/{id}
FATAL: Error: 1 errors occured -- cannot continue.
  Error: Plugin checker reported failure.

It could be a good idea to assign operation IDs anyway, even if you do not have duplicate operations. Check the swashbuckle documentation for details. The documentation is for older netcore versions. For netcore 6 you need to adapt and add this code to the Program.cs file to generate operation IDs. The operation ID can be customized by adding the SwaggerOperation attribute to the method.

[SwaggerOperation(OperationId = "GetAll")] // generates OperationId '<controller>_GetAll'
public IEnumerable<Author> GetAllAuthors()

Tidy Up

Improvement 1: Remove Unnecessary Content Types

By default the swagger.json contains many content types in the response section. When the controller specifies which content types are consumed/produced, the generated OpenAPI will only list relevant types. There are better ways to specify this for every controller by default.

[Consumes(contentType: "application/json")]
[Produces(contentType: "application/json")]

Improvement 2: Serialize Enumerations as String

Enumerations are serialized as integers by default. However, it is common to use the enum values in APIs. Using integer values makes it possible for the numbers to get out of sync on client/service without anybody noticing. To use descriptive strings, add the json options to the ConfigureServices method.

Fix Autorest Problems
services.AddControllers()  
  .AddJsonOptions(o => {     
    o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

This approach will not work for enumerations with custom values. See the this post for a potential solution. Another option would be to not expose the custom enumeration and instead use a DTO with a default enum (no assigned values) which is mapped to the custom enumeration.

Generate a C# Client

The default generator is now @autorest/csharp. The new generator produces completely different client code from the legacy version. One very obvious difference is that the generated code contains a separate client per controller. That could be useful if different client options are necessary. That coule be a different number or retries or timeout for a controller. More details are in the autorest.csharp documentation.

Fix Compiler Errors

When I tried to build the C# client, there were type errors around the objects ClientDiagnostics, RawRequestUriBuilder, IUtf8JsonSerializable and some others. The following changes in the project file solved the problem for me (thanks to stackoverflow).

<PropertyGroup>
  <IncludeGeneratorSharedCode>true</IncludeGeneratorSharedCode>
  <RestoreAdditionalProjectSources>https://azuresdkartifacts.blob.core.windows.net/azure-sdk-tools/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>	
	
<ItemGroup>
  <PackageReference Include="Azure.Core" Version="1.22.0" />
  <PackageReference Include="Microsoft.Azure.AutoRest.CSharp" Version="3.0.0-beta.20210311.1" PrivateAssets="All" />
</ItemGroup>

Final Words

I did not have a real play with the new Autorest CSharp generated client, but it seems to work much smoother than the old client. There are much fewer problems generating the C# client. Unfortunately the generated code is not backwards compatible for existing projects. I am also keen to try a different generator which does not depend on other packages.

Angela Evans

Senior Software Engineer at Diligent in Christchurch, New Zealand