Skip to main content

Generating HTTP API clients using Visual Studio Connected Services

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!

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.”

Adding a Connected Service in Visual Studio

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.

Launching the Connected Services Gallery in Visual Studio for Mac

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.

Connected Services experience in Visual Studio

Connected Services experience in Visual Studio for Mac

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.

Adding a Connected Service in Visual Studio

Adding a Connected Service in Visual Studio for Mac

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:

  1. Visual Studio invokes the dotnet-openapi global tool using the parameters you specified when adding the service reference
  2. dotnet-openapi uses NSwag 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.

The post Generating HTTP API clients using Visual Studio Connected Services appeared first on ASP.NET Blog.



Comments

Popular posts from this blog