This post is dedicated to one great book that I was lucky to read: “Building Web APIs with ASP.NET Core” by Valerio De Sanctis.
This book helps to grasp the fundamental principles of building Web applications and contains a lot of exercises, links to primary sources as well as useful tips and tricks that I am going to mention here. Let's start!
We can build Web APIs by using several approaches such as
- REST (Representational State Transfer)
- SOAP (Simple Object Access Protocol)
- GraphQL
- gRPC (Google Remote Procedure Call), etc.
Each of these paradigms has its own advantages and limitations and it does matter to determine data flows between clients and servers and make a reasonable choice.
REST has its drawbacks such as over-fetching and under-fetching that may lead to multiple roundtrips to the server, code bloat and unnecessary traffic. But REST is still very popular for its built-in caching features, scalability, flexibility and portability.
Hopefully you know about the Web's three primary standards: URI, HTTP, and HTML but there are also the six REST constraints defined by Roy Fielding that deserve attention:
The Separation of Concerns principle. The client displays data, and the server computes them.
Statelessness. Each subsequent HTTP request does not know anything about the previous ones. That is why Token-based authentication (Bearer authentication) is preferred as a self-contained authorization mechanism to satisfy this constraint.
Cacheability. All HTTP responses must contain the appropriate caching or non caching info within their headers.
Layered system. Using some intermediary HTTP services such as forwarders, CDNs, reverse proxies, and load balancers in order to improve the overall security posture and increase the performance of your web app.
Code on Demand (COD). The server can delegate some jobs like performing some checks to its clients.
Uniform Interface that includes:
a) Resource Identification. Each resource must be identified through its dedicated and unique URI and represented using a standard format (such as JSON).
b) Manipulation through representations. Clients interact with resources by sending and receiving data in formats like JSON or XML, which represent the current state of those resources.
c) Self-descriptive messages. Ensures that each message sent between the client and server contains enough information for the recipient to understand how to process it.
d) HATEOAS (Hypermedia As The Engine of Application State). Allows clients to interact with a server through dynamically provided hypermedia links.
Thus, based on the foregoing, let's recap some of the guidelines applied to REST services.
Implement the idempotent semantic by using GET, PUT, DELETE (HEAD, OPTIONS, TRACE) methods when you know that identical subsequent requests will return the same result as the first one.
Follow the corresponding RFC standards, for example, the RFC 7807 that describes machine-readable details of errors in HTTP responses for HTTP APIs or the RFC 7231.
Cache at the highest level by using several caching mechanisms provided by the framework:
- HTTP Response cache (client-side and server-side)
- in-memory cache
- distributed cache.
Prevent caching wherever it is needed providing The Cache-Control header with the 'no-cache, no-store' value (the [ResponseCache] attribute) according to the caching specifications (RFC 7234). That also may be achieved through adding a default caching directive that can kick in every time in case of the missing [ResponseCache] attribute.
Implement secure CORS Configuration. Limit allowed origins: always specify exact domains in the Access-Control-Allow-Origin header. Avoid using *.
Define a default policy and named policies. You can store your policy configuration parameters (AllowedHosts, AllowedOrigins) in the appsettings.json.
Provide proper and up-to date documentation to your web app's endpoints that is crucial for demonstrating and testing how your app works.
The documentation must tell your users about:
- what the endpoints do
- what are their input parameters (types, allowed values, hints)
- what are their responses and status codes.
It is good to supply request and response samples, authorization requirements and exclude some reserved endpoints from public use.
Thanks to OpenAPI/Swagger integrated in ASP.NET Core, you can enjoy the auto generated description file (swagger.json) and the API client (SwaggerUI). Moreover, you can customize your documentation content by using the special filters, attributes, services and middlewares. You can incorporate your code-level comments as API-documentation or override them where needed.
Hide Swagger in staging and production environments for security reasons: generate a static API documentation that can be hosted on a website with limited access.
The author of the book encourages developers to adopt the Security by Design approach. This is apparent in the following aspects:
Applying the edge-origin architectural pattern where a proxy or CDN service (the edge) publicly serves the content by taking it from a source web server (the origin) adding multiple layers of security controls.
Set up the HTTP Security Headers mentioned in the OWASP HTTP Security Response Headers Cheat Sheet.
Store your secrets securely by using environment variables, the ASP.NET Core Secret Manager tool for development environments and Azure Key Vault for production environments.
Review the appsettings.json file(s) and Program.cs file before deploying your app: set up different middlewares and appsettings.json files for each available runtime environment (Development, Staging, and Production).
Protect from SQL Injection and think about techniques for handling Large Data Queries (pagination, indexing, etc).
The author also mentions some other useful programming principles such as:
Aspect-Oriented Programming (AOP) that is implemented through The Data Annotation attributes.
Strongly typed approach that is achieved by using the constant values instead of literal strings.
Task-based Asynchronous Pattern (TAP) to run I/O-bound tasks in a non-blocking way and resume the execution upon their completion.
DRY principle that is meant to avoid redundancy.
Dependency Injection (DI) pattern to achieve Inversion of Control (IoC).
And now let's list some basic things that are worth remembering too.
The Program.cs can be divided into several sections where our app is constructed:
Create the builder (WebApplication.CreateBuilder(..))
Add services to the container (builder.Services.AddSomeService(..))
Build the app (builder.Build())
Configure the HTTP request pipeline (app.UseSomeMiddleware(..), app.MapSomeMiddleware(..))
Run the app (app.Run(..) - a terminal middleware)
Care about lifetime of your services, their correct disposal and thread-safety.
Take care of the order in which you add middlewares: each middleware can either pass the HTTP request to the next component in the pipeline or provide a HTTP response, thus short-circuiting the pipeline itself.
When creating Web APIs, we don't use Views (HTML), since we need to return JSON or XML data, so that inherit from the ControllerBase class and decorate your controllers with the [ApiController] attribute providing us with an automated error handling.
The [ApiController] attribute works with Routing Middleware that invokes the model binding system performing Model binding (raw strings to .NET objects) and Model validation (ModelState.IsValid).
Use Data Transfer Objects (DTOs, POCO classes) to decouple the response data from the data returned by the Data Access Layer.
Minimal APIs may be useful for implementing one-liners.
Thank you for reading. Any feedback from you would be appreciated. Take care!
Top comments (0)