We’re continuing our series on building HTTP APIs with .NET 5. In the first post in this series we talked about building well-described APIs using OpenAPI, and then followed that up taking a deeper dive into some of the open-source tools and packages you can use to make your HTTP API development easier. In this post, the third in the series, we’ll talk about how Visual Studio Connected Services gives you a convenient way of generating .NET clients for your HTTP APIs so your .NET apps can use the APIs via the Internet. Let’s get right to it!
- Creating Discoverable HTTP APIs with ASP.NET Core 5 Web API
- Open-source HTTP API packages and tools
- Generating HTTP API clients using Visual Studio Connected Services (this post)
- App Building with Azure API Management, Power Apps, and Logic Apps
Visual Studio Connected Services
Building an HTTP API is only useful when the API can be called from apps or other APIs. Consuming an HTTP API isn’t complex, but it does require a good amount of boilerplate, and often redundant, code. When using .NET code to call to a back-end API, the steps are relatively predictable. Developers create instances of the HttpClient
class to initiate HTTP calls to an endpoint. Serialization and deserialization code needs to be written to serialize the request and responses to and from JSON, XML, or some other form of content.
While this isn’t a complex set of code, it becomes redundant quickly. Visual Studio Connected Services makes use of NSwag for generating strongly-typed clients from OpenAPI specification documents, and gRPC clients or servers from proto files.
Visual Studio Connected Services
By right-clicking on any project, you can add a Connected Service. Connected Services can be a variety of things – they can range from full-blown Azure Services like Key Vault or Cosmos DB that you’ll need to use in your app. Connected Services also enables you to use OpenAPI and gRPC services written by yourself or other folks on your team. The first step in generating an OpenAPI client is to right-click your project in Visual Studio and selecting “Add Connected Service.”
Visual Studio for Mac is also enabled with these features. You can right-click a project’s Connected Services node in Visual Studio for Mac and select the Open Service Gallery command to access the Connected Services gallery.
Once you’re in the Connected Services experience within Visual Studio you’ll see that generating a client for either a RESTful API described with OpenAPI or a gRPC API described with proto is right up front.
When you click the Add button within the Visual Studio Connected Services panel, you’ll be prompted to provide either a file path or a URL to a live API.
Once the OpenAPI file or URL is loaded by Visual Studio, the .csproj
file you’re generating the OpenAPI client code into will be wired up to generate the client code on build. So, once you rebuild your project, the client code will be added to your project. A .csproj
file from the sample Visual Studio Solution that has been set up with an OpenAPI Connected Service is shown below.
<ItemGroup>
<OpenApiReference Include="..\ContosoOnlineOrders.Api\bin\Debug\net5.0\ContosoOnlineOrders.Api.json" Namespace="ContosoOnlineOrders.ConsoleClient">
<Link>OpenAPIs\ContosoOnlineOrders.Api.json</Link>
</OpenApiReference>
</ItemGroup>
This client project will have the client code used by my app to access the API each time the project is built. When working locally, with a Web API project in the same solution as a client project that will use that API, it is handy to also set the Web API project to generate the OpenAPI specification document on build. This way, a solution build results in a new OpenAPI file being generated at build-time and the client getting generated at the same time.
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OpenApiDocumentName>1.1</OpenApiDocumentName>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<Target Name="Generate OpenAPI Specification Document" AfterTargets="Build">
<Exec Command="dotnet swagger tofile --serializeasv2 --output $(OutputPath)$(AssemblyName).json $(OutputPath)$(AssemblyName).dll $(OpenApiDocumentName)" ContinueOnError="true" />
</Target>
Well-described APIs generate better client code
As mentioned in the first post of this series, it is extremely important when building HTTP APIs using Web API to use the Name
property with your HTTP verb attributes.
[HttpGet("/orders/{id}", Name = nameof(GetOrder))]
public async Task <ActionResult<Order>> GetOrder([FromRoute] Guid id)
{
// ...
}
Since OpenAPI generation uses the value of the Name
property to define the operationId
attribute value for each of the API’s endpoints, and since most client SDK generators use that operationId
attribute to name the generated client methods, the generated code quality will be impacted by poorly-designed or described APIs. If you experience issues generating clients using Connected Services, first check the API description for the presence of operationId
values in the spec.
Calling the HTTP API using the generated client
Because of the magic of OpenAPI, we now have a strongly typed .NET client that understands the back-end HTTP API. That means we can now focus on our actual business logic, leaving the HTTP internals to the generated client.
static async Task Main(string[] args)
{
using var httpClient = new HttpClient();
var apiClient = new ContosoOnlineOrders_ApiClient(httpClient);
// create a product
await apiClient.CreateProductAsync("1.1", new CreateProductRequest
{
Id = 1000,
InventoryCount = 0,
Name = "Test Product"
});
// update a product's inventory
await apiClient.UpdateProductInventoryAsync(1000, "1.1",
new InventoryUpdateRequest
{
CountToAdd = 50,
ProductId = 1000
});
// get all products
await apiClient.GetProductsAsync("1.1");
// get one product
await apiClient.GetProductAsync(1000, "1.1");
// create a new order
Guid orderId = Guid.NewGuid();
await apiClient.CreateOrderAsync("1.1", new Order
{
Id = orderId,
Items = new CartItem[]
{
new CartItem { ProductId = 1000, Quantity = 10 }
}
});
// get one order
await apiClient.GetOrderAsync(orderId, "1.1");
// get all orders
await apiClient.GetOrdersAsync("1.1");
// check an order's inventory
await apiClient.CheckInventoryAsync(orderId, "1.1");
// ship an order
await apiClient.ShipOrderAsync(orderId, "1.1");
}
Using a generated API client is good for two reasons: 1. The first time through, we start with working HTTP client code quickly. 2. When the backend HTTP service is updated, we can refresh our client in the same Connected Services dialog. This will grab the updated OpenAPI definition and build an updated client, automatically for us.
So, while it’s taken a little time to set up both the server and client side code, the real benefit here is that we now have automated the workflow on both ends. Updates to the service are painless, and if something in the server API change breaks our client code, we have a pretty good chance in catching it in our strongly typed client code.
Code Generation Internals
We’ve worked to make this as easy as possible to set up and use within Visual Studio, so you don’t really need to understand how the code is being generated to use it. However, it’s good to know how the code generation process works in case you need to extend, automate, or troubleshoot it.
The Visual Studio Connected Services code generation tooling is built on top of the Microsoft.dotnet-openapi global tool. You can install it using the following command:
dotnet tool install -g Microsoft.dotnet-openapi
This tool includes three commands: – Add: Adds a new OpenAPI reference by file or URL – Remove: Removes an existing reference – Refresh: Updates the OpenAPI reference with the latest content from the download URL
As an example of the kind of under the hood extensibility options available, the Add
command includes a --code-generator
option, which supports both NSwagCSharp
(the default) and NSwagTypeScript
. As mentioned earlier, we’re using NSwag to actually generate the code, so when you build your project in Visual Studio, here’s what’s actually happening:
- Visual Studio invokes the
dotnet-openapi
global tool using the parameters you specified when adding the service reference dotnet-openapi
usesNSwag
to generate the client code using a partial class
It’s important to note that the generated client is in a partial class. If you need to extend your HTTP client, you should do it in another partial class file, so that your customizations aren’t discarded if you regenerate the client code.
Summary
So far in this series, we’ve seen how to write great HTTP applications with .NET. The first two posts showed you how to leverage .NET features and open source tools to build great HTTP services. In this post, you learned how to generate .NET code to consume HTTP services. In the final post in this series, you’ll see how you can use API Management to expose your .NET HTTP APIs to low-code solutions like Power Apps, Logic Apps, and Azure Functions.
I don't understand why Microsoft chose to remove the REST API client generator that was present in VS until 2019 16.8 and replaced it with the OpenAPI generator in 2019 16.9.
First of all, it doesn't even generate compileable code when having more than one controller, see bugreport
Second of all, it leaves out all the interfaces and synchronous method calls that the old generator generated which were really helpful for mocking and unit testing.
Also concepts like...
I prefer the old way. I'm developing a client that connects to an API with a lot of endpoints and DTOs. The old way generated a folder with a file for every class, more convinient IMHO. The new way generates only a file with, in my case, 25.224 lines of code. It's even hard to Visual Studio to work with such a big file!
Can we get the old experience (or both and select the one...
I thought I'd try this with an existing solution with three projects a Web API, a Blazor client and a shared class library with DTOs.
The generated code of course contains duplicates of each of the shared DTOs (slaps forehead). I suppose this codegen tool is currently best used with a public API that you don't own in which having the generated types might be super useful.
Still it would be nice if it still worked...
Any ideas what is going wrong when encountering the following error on client code generation from an OpenAPI file?
If you execute ‘Restore NuGet Packages’ you’ll see the error’s details. I had this error, because generated code wanted ‘Newtonsoft.Json v12.0.2’ while my project already referenced newer ‘Newtonsoft.Json v12.0.3’.
I've also written a Visual Studio plugin (see marketplace) which makes it possible to generate a RestEase interface.
See the project here: https://github.com/StefH/RestEase-Client-Generator
Note that not all OpenAPI swagger files do yet work, it's a work in progress solution.
Hi, a link error, click on “App Building with Azure API Management, Power Apps, and Logic Apps” but jump over this page.
yes ,agree:
On
https://devblogs.microsoft.com/aspnet/generating-http-api-clients-using-visual-studio-connected-services/
“App Building with Azure API Management, Power Apps, and Logic Apps”
should link to:
https://devblogs.microsoft.com/aspnet/app-building-with-azure-api-management-functions-power-apps-and-logic-apps/
Hi, just updated studio and checked out this case. It works horribly, the constructed class contains a lot of duplicates and compiles with an error. I can send you by mail the address of our OpenAPI site, where you will find many problems in the generated code. Where can I report this?
I have the same experience.
When generating from a swagger.json file, a class “swaggerClient” is generated for each endpoint (“controller”), resulting in a number of duplicate classes.
It's great to finally have at least something for client generation!
But I can't not mention some concerns:
1. Dependency on the third-party tool which might stop to be actively supported at some point (e.g. this seems to be the destiny of Newtonsoft.JSON). But that could be OK if not 2;
2. Generated client code is not included in the project and in the source control. That means ability to compile some "old" project may...
All very valid questions. The nature of this is that we do the best job we can, like other code generators, of creating boilerplate code, the mundane parts of it that we know 97% of any developer will need to do. We try to leave the remaining details to you, so you can do what you do within the generated code, but not TO the generated code [directly].
I hope that helps!
“This is correct. If you look under the /obj folder”
Since when does one check in the contents of the obj folder into the source control?
But without the code, the project is not complete and not reproducibly compileable at any given point in the future.
Hi, Thanks for your reply!
Regarding authentication: yes, I know I can wire up any authentication approach I want to the HttpClient. As well as I can do any HTTP request I want (but with the tools I don't need to). It would be nice if client generator tools could generate authentication code in addition to requests to an API. E.g. it could use IdentityModel library for OpenidConnect or MSAL for AAD. As far...