How to Avoid Duplicate 404 Code in .NET Core APIs - Part 3

How to Avoid Duplicate 404 Code in .NET Core APIs - Part 3

Part 1

Part 2

The Current Problem

It's great that we can now have all of our 404 Entity Not Found code be generic so that it can be used for all top-level resources. The issue is, we now are making a call to the database in the EntityNotFound attribute as well as in our actual controller. This may not seem like a big deal, but what if we had a very nested entity that looked like this?

/api/library/1/area/2/shelf/3/book/4

We would have to ensure that all of these entities existed, and therefore we would be making duplicate calls to the database for each of these. We made everything generic, but now we have slowed our requests down a bit because of how many database calls each request will make. Let's look at how we can solve that problem, and only make one call to the database per entity.

Scoped Services

Let me first give a brief overview of the different dependency injection lifetimes that exist in dotnet core:

  1. Transient - this will create a new service each time it is requested (not the same as an api request). For instance if we have a database service configured as transient, each time we make a call to the database it will use a new instance of the database service; even if we make multiple calls to the database in the same API request.
  2. Scoped - this will create one service for the entirety of a given client request. For instance if you make a call to an API which calls multiple functions that all use the same service, it will only use one version of that service if it is defined as Scoped. You might have already guessed it, but this is what we will use for this ScopedService.
  3. Singleton - this will create one service which will either be initiated the first time it is requested, or if the developer provides an implementation for it.

With that out of the way let's take a quick look at the generic ScopedService we will create:

scoped_service.png

Pretty easy right? Looks just like a regular property on a class using a getter and setter.

NOTE: To set this up using dependency injection you'd need to add one instance of ScopedService per type. Something like:

services.AddScoped<IScopedService<Book>, ScopedService<Book>>();

Bringing it all Together

Now that we have our ScopedService we can update our EntityNotFound and Controller to use the ScopedService so that we can avoid making multiple calls to the database. First let's look at the EntityNotFound code with this new ScopedService:

enf_new.png

It is essentially the same as what we had before, except now we are able to set the entity within the ScopedService so that it will be there for the entirety of a client request. Now we can inject the ScopedService into our controller and grab the entity directly from the ScopedService, as opposed to going and getting it from the database again:

bc_new.png

This keeps our controller code extremely clean, and also allows us to not make multiple calls to the database!

Conclusion

Duplicate code can get very tedious especially when you have to change something that relates to that duplicate code. Patterns like this allow us to keep our controllers and code super clean, and accelerate much faster since we have less tests and code to write each time we had some new endpoints. If anyone is interested in seeing a follow-up to this for unit testing the EntityNotFound attribute feel free to let me know. Otherwise this will conclude the series on preventing duplicate 404 code in your .NET Core APIs.