To keep controllers as light as possible, data access should be delegated to a service. Nate explains how to refactor the controller to use a service class for data access.
- Right now the controller is directly interacting with the data context. It's a better idea to wrap this data access code in a service class and keep the controller as thin as possible by relying on a service to interact with the DB context, the data access concerns are separated from the controller. This also has the benefit of making the controller easier to test because you can inject a mock service instead of having to mock the entire database context. To refactor this into a service class, first, I'll create a services folder and then I'll create an interface called IRoomService.
I'll make it public and for now it will only have one method, which returns a task of a room-- we need import that namespace. We'll call this method GetRoomAsync. The method will take Guid id and a CancellationToken called ct. Next we'll create a class that will implement the IRoomService. I'll call this class DefaultRoomService. I like calling my service class implementations DefaultService that way it can be easily distinguished from another service type such as a mock service.
This service will implement IRoomService and it'll need to implement that one interface method. Just like the controller before we'll inject the HotelApiContext_context using a constructor injection pattern. We can copy some of this code directly from the controller. We'll cut all of the data access code and move it over into the service and we'll need to mark the service method as Async as well; import a few namespaces.
Here we don't have access to the NotFound method because that's part of the controller based class. Instead we can simply return null. We don't have access to the URL.Link method either because URL exists on the controller based class as well. For now, we'll set this to null but we'll come back to this and fix it in a moment. At the end of the method, we need to return the resource that we built up. I also need to fix an error here because this ID parameter changed names. Now I can switch back to the controller and finish refactoring.
Instead of injecting a HotelApiContext now I can inject an IRoomService. I need to include this namespace and I can call this roomService and I can update the constructor as well. In the GetRoomByIdAsync method, I just need to call the service. I'll call GetRoomAsync, pass the ID, and the cancellation token and then like before I'll check if the result came back as null, in which case I'll simply return NotFound.
If it is found, I'll just return the room resource straight to the client. There's one more step. We need to add the service class to the ASP.net core dependency injection container so that it knows to give us a default room service when we ask for an IRoomService in the controller. We can do this in the start up class. Somewhere in the configure services method we'll add a new line and say services.AddScoped and pair IRoomService with DefaultRoomService. Adding this as a scoped service means that a new instance of DefaultRoomService will be created for every incoming request.
This is different than a singleton service, which would only be created once for the life of the application. The entity framework core objects like the DB context have a scoped lifetime and so any service that interacts with the DB context should be scoped as well. We can build and run the project to test it out with Postman. I'll send the same request before which includes a room ID and this time the response has all the data for the room except for the H-rev and that's expected because we don't have access to URL.Link in that service. We'll fix that in a moment but first let's reduce the boiler plate code needed to copy the entity properties over the resource properties.
- REST vs. RPC
- Using HTTP methods (aka verbs)
- Returning JSON
- Creating a new API project
- Building a root controller
- Routing to controllers with templates
- Requiring HTTPS for security
- Creating resources and data models
- Returning data and resources from a controller
- Representing links (HREFs)
- Representing collections
- Sorting and searching collections
- Creating forms
- Caching and compression
- Authentication and authorization for RESTful APIs