As the title implies, this post will be a bit of a mix of two topics: contract first approach to developing APIs, while still making some use of ASP.NET Core and other libraries features (e.g. Swashbuckle and NSwag).
I wouldn’t say contract-first development is a controversial take, but it sure doesn’t seem like the most common approach in .NET land, as we tend to do a lot of code first. Let’s go right into the subject.
Contract first: what and why
Even if you’re not aware of what contract first means, you might have your suspicions based on the name: it means developing the contract before developing the code that fulfills it.
The contract can take many forms, be it an OpenAPI document describing an HTTP based JSON API (the subject of this post), proto files describing a gRPC service, or even a “good” old WSDL file, describing a SOAP service.
For these three examples presented above, in .NET land, as far as I’m aware, only in the gRPC case is it common do define the service with the proto files before implementing it (and even then, it’s probably being done in parallel). For HTTP APIs, the most common is to use some tool like Swashbuckle or NSwag, to generate the OpenAPI document based on the metadata exposed by ASP.NET Core, and in WCF we had similar capabilities to generate WSDL files based on our code.
There’s nothing wrong with a code first approach, and it’s probably the best option for many cases, be it because it’s a very simple API, we just want an auto-generated UI to do some experiments, or both client and server will be developed by the same team, so we don’t care too much about the contract, but would like to have it anyway, so we could generate client code automatically.
However, there are situations where going with a contract first approach is a better option, particularly when you’re developing APIs that someone else will use, be it external (e.g. providing an API for a partner to call into), or internal (e.g. there are different teams within the company, developing different microservices). In these situations, we don’t want to lose ourselves in technical details of our development stack of choice, but focus on the features and how to expose them to our API clients. The work of defining the API might even be a joint effort between client and API developers, to ensure the best possible experience is created.
Going contract first
Hopefully, the potential advantages of contract first are clear by now. As most of the APIs I’ve developed weren’t for me to use, using this approach made a lot of sense. Additionally, I’ve worked in places where we had API review processes, where API developers, consumers and others with expertise on the subject could provide their feedback.
So, how do we get started with this approach? Well… we write the contract first 😅. In the case of an HTTP API, this means creating an OpenAPI document.
Now, there’s a couple of approaches, a “pure” one, and another I would call contract first-ish. The pure approach to contract first, as you might suspect, it to write the OpenAPI document manually, no code generation magic. As for contract first-ish, we could write C# code, but just enough to generate the contract, i.e. define the controllers, action method signatures, DTOs and that sort of thing.
The contract first-ish approach can work, but not without its caveats. In particular, depending on the complexity of the API, we might end up having to dig deep into the capabilities of the code generation tools, doing all kinds of tricks, so it finally generates the OpenAPI document exactly how we want it (been there, done that 😅).
Given my past experience with the contract first-ish approach, I’m more convinced that the pure approach is a better option in general. Besides, it’s not like writing an OpenAPI document is so hard, particularly compared to writing a WSDL document (been there, done that too 🤣). As another benefit, we understand even better how OpenAPI works, instead of blindly relying on Swashbuckle’s magic (which is awesome by the way).
To create an OpenAPI document, you can of course just write it in your IDE of choice, but it will probably be easier with some tooling to help out. I’ve been using the Swagger Editor available through the Swagger web site, which is a nice tool, even if not very feature rich. You can also look at plugins for editors/IDEs, like this one available for JetBrains Rider and Visual Studio Code. Additionally, Postman and other vendors specializing in API development, probably also have tooling to help out.
Following this approach, I created the following sample OpenAPI document:
When using the Swagger Editor tool mentioned earlier, as we write the contract, we get the traditional Swagger UI on the side, showing things in a more visual way:
Then, all we need to do, is implement an API that fulfills this contract. Using ASP.NET Core minimal APIs, it could look like this:
Notice that given I’m using a contract first approach, I didn’t add any additional metadata required just for the OpenAPI docs, I focused on just the code required for the API to work as we want it to.
Another note, is that although I just went ahead and wrote the whole code myself, as I prefer it this way, there are tools to generate a server stub, that you can then fill in with the logic. A couple of examples are Swagger Codegen and NSwag’s C# controller generator.
Exposing a UI for experiments without OpenAPI document generation
Now, when we use things like Swashbuckle or NSwag, the main thing we probably use them for, is the OpenAPI document they generate for us, but it’s not the only thing they do, as they also give us a nice Swagger UI page we can use to test our API, which is pretty helpful during development. Fortunately, the tools are pretty well developed, so following the best practice of separation of concerns, we can actually still use the Swagger UI feature, even though we don’t use the contract generation features.
As an example with Swashbuckle, we need to add it as a dependency, but don’t need as much, just the
Swashbuckle.AspNetCore.SwaggerUI package is sufficient.
Then, we register it in the request handling pipeline, indicating the location where the OpenAPI document is, and it’ll be able to render the Swagger UI as we’re accustomed to:
There’s just one last thing: we need to put the
openapi.yaml somewhere for the Swagger UI to have access. In ASP.NET Core, the default solution is to put things in
wwwroot (though it is configurable), and enable serving static files in the request handling pipeline.
Now there’s one final issue here: the static files middleware only serves files with extensions it knows, and apparently, it doesn’t know about YAML, which is the type of file I used for the OpenAPI document (it could also be JSON). With this in mind, when setting up the static files middleware, we need to configure it correctly, otherwise the file won’t be returned.
With everything in place, we get our trusted, Swashbuckle powered, Swagger UI.
Before wrapping up, wanted to mention something very important, not just when going with a contract first approach, but in general, though it becomes even more important when the contract isn’t automatically generated from the code: contract testing.
Contract testing is an important technique to use both by the API developers, but also by the developers of the consuming code, to ensure that the code implementing the contract, as well as using it, works as expected. It’s a more effective alternative to end-to-end tests, which are both slower and harder to automate.
As mentioned, because we’re not automatically generating the contract, the code can be completely off, so to ensure that the code actually fulfills the contract, we can do some contract testing. This technique can also be super useful, even with a code first approach, to ensure that we don’t make unexpected breaking changes when working on the API.
Now there’s a very important thing when you’re developing contract tests: don’t reuse the types defined to implement the API. Reusing the types kind of defeats the purpose of contract testing, because you may not be following the contract as you should, if the types were changed but those changes were not reflected on the contract. I feel like the best way to approach this, is to use a tool that generates the client code given an OpenAPI document, just like other teams using our API would do.
There are different options to generate an API client to use in the tests, some more manual, some easier to automate, and we’ll be using one that fits in this later category. Although not necessary, we’ll use the .NET OpenAPI global tool to help us out.
After installing the tool, from the test project folder we can run the following command:
Running this command, the test project’s
csproj file will change a bit, namely adding a couple of package references (at the time of writing,
NSwag.ApiDescription.Client), as well as an
OpenApiReference element, referencing the file we passed as an argument to the command. The reason I mentioned we didn’t need the tool, is that we could just add these elements manually to the
csproj, and it would work the same.
A quick side note, if we want to write tests to ensure compatibility between API versions, for instance, to be sure we didn’t introduce breaking changes between minor versions (e.g. 1.0, 1.1, 1.2, …), it would probably be a good idea to not reference the API project’s OpenAPI file, but copy it and keep the multiple versions.
Another note: if you clicked to see how the tool works, you’ll see that it’s not very well documented. You can check out this article by Steve Collins, who dug deeper into this subject and what can be done with
I customized a bit the
OpenApiReference element, with the code generator I wanted, as we can choose between C# and TypeScript (though C# is the default), the name of the client class and the namespace it’ll be part of:
When we build the project, the code API client code will be generated, and we can write some tests, like this simple one:
That does it for this post. It was more of a chat about contract first vs code first approaches to API development, not much in terms of coding, but if you end up going with a contract first approach, I hope the couple of pointers I wrote about are useful.
In summary, even though a code first approach can be faster, particularly due the awesome tooling available, depending on the context, it might be a better option to go with a contract first approach. Going contract first frees you from thinking about specificities of the tech stack, particularly in a phase of the development process where the focus should be on the API clients’ needs.
- Sample Source Code
- Swagger Editor
- Swagger Codegen
- .NET OpenAPI tool
- Using OpenApiReference To Generate Open API Client Code
Thanks for stopping by, cyaz! 👋