If you're a .NET developer building web apps or microservices, odds are at some point you're going to want to call an HTTP API from an ASP.NET Core app. This post covers how to create a client using NSwag, with the appropriate settings for using it with
Typed Clients and
For a proper introduction to these concepts I encourage you to read Steve Gordon's HttpClientFactory in ASP.NET Core 2.1 series, An Introduction to HttpClientFactory and Defining Named and Typed Clients, but the really short version is:
HttpClientFactory- This was introduced to solve the problems around managing the lifetime of
HttpClients and their handlers, which historically has been problematic.
- Typed Clients - This is a feature of
HttpClientFactorywhich lets you register a class which simply wraps
HttpClient(i.e. takes one in its constructor), thus giving you a typed client to your API, while not owning the lifetime of any
HttpClientFactorydo its thing.
Where does NSwag fit into this?
NSwag is, on one hand, like Swashbuckle, in that with a couple of lines of code you can have your ASP.NET Core application serving up a swagger doc and swagger ui within your app at runtime. Then it also has the functionality of AutoRest, where it can take a swagger file and generate a C# typed client for you (and other languages such as TypeScript).
There are a couple of things that impress me about NSwag:
- The number of integrations - it's a swiss army knife! Just take a minute to read the README, you quickly get the idea, it can generate swagger or OpenAPI file not just at runtime, but at build time from your web api assembly. Then you can generate clients in various languages with lots of settings to play around with.
- Rico Suter, who is the primary maintainer, is continuously contributing to it and is so responsive to issue, and checking in fixes and features. It's like he has a team running his GitHub account!
Starting with swagger.json
We are going to be generating a client from
swagger.json, if you need to produce this from your own ASP.NET Core application then you can generate a
swagger.json using the NSwag CLI, see here.
Generating the Client in Build
For this we will use the
NSwag.ConsoleCore CLI tool package (we could also use the NSwag.MSBuild package, the process is largely the same). I'm using the Pet Store swagger, and start by dropping the
swagger.json into my project folder.
There are 2 ways to pass config into the NSwag commands, one is via command line args, the other is via a json config file which is what I'll be using here.
First create an empty library (here we will target
netstandard2.0) and add the following snippet to an
ItemGroup in your
dotnet restore we can now run in the project folder
dotnet nswag new, this will create us a default config file, we can strip it down so it looks like this. Here's a trimmed down version:
Here are settings that I changed from their defaults:
generateClientInterfaces- This gives us an interface for our typed client, abstracting the implementation away. This may seem like something you wouldn't think of doing without, but there is merit in using the implementation of the typed client under test and mocking at the
HttpClientlevel (shout out to httpclient-interception).
InjectHttpClient- This creates a constructor to the client that takes in a
HttpClient, which is what we need to use it as a typed client with
disposeHttpClient- It mostly doesn't matter if you call
HttpClientwhen used with
HttpClientFactory, it's mostly a no-op. However, for correctness sake, it would be weird for the typed client to dispose something it doesn't own.
generateOptionalParameters- By default NSwag will create 2 methods per operation, one with and one without a
CancellationToken, enabling this will combine them.
generateJsonMethods- By default NSwag will add instance methods that serialize and deserialize types, they just delegate to one-liner static methods from Json.NET. I like my POCOs nice and plain, so I disable this.
useBaseUrl- This is an important one, we want to use the
HttpClientonly, we don't want to override that with a property on the typed client (which is what will happen by default), so we set this to
false. We can configure the
BaseAddressat the time we register the typed client.
exceptionClass- The type of exception that is thrown on error, by default it would be
SwaggerException, which feels like an implementation leak.
classStyle- By default nswag uses the style
Inpcwhich is short for
INotifyPropertyChange, which is a strange default, I can only think it would be useful if you were building an MVVM client app and wanted to reuse the classes to bind to directly (still feels wrong to me). By selecting
Poco, we end up with just a typical class with getter and setter properties.
You can see I use variables in this file, such as
"$(InputSwagger)", we can pass these in via the nswag command.
What we want to do is to just do a
dotnet build and to have the project automatically create the generated
.cs file, and include it in the files to build. So here's the
Things to note:
- The only thing in this file that is specific to the Pet Store swagger, is the
<ClientName>PetStore</ClientName>, feel free to use this as a recipe for your own clients.
- We hook our targets before
CoreCompile, giving us the opportunity to add files to compile.
- The generated file goes into the
IntermediateOutputPath, this is the configuration specific folder inside of
obj, this is exactly how
AssemblyInfo.csis generated, and is likely already part of your
- We have included the required
- Variables are passed into the
<EmbedAllSources>true</EmbedAllSources>gives us one
.dllwhich contains an embedded PDB file, which contains all of the source within it for debugging purposes. You could also use symbol packages, which in my option is a step backwards from this solution.
Using the Client with
Right, we have our package/project, let's use it!
Let's create a new ASP.NET Core Web API using the template, and add a reference to our client project and add the
Microsoft.Extensions.Http package. See here for an example.
Now we can register our client in the
ConfigureServices method, like this:
And that's it! Now we can start using our client, here's an example from our controller:
You can see all of the code here.