Learn how to tie the model classes and the data from the database together in a controller to return data to the client.
- [Instructor] Now that we have a model that represents a room in the database, the room resource will return to the client and test data in our database. Let's wire this all up in a controller. I'm going to open up the rooms controller and I need to inject the context that we added earlier. So I'll declare a private read only HotelApiContext in the controller. And in the controller's constructor, I'll inject HotelApiContext.
Now we need to create a new route on the room's controller to get a single room by its Id. So I'll create a new route that will be slash rooms slash roomId by declaring a new method. This time I'll return a task of IActionResult because we'll use an Async method in here. We'll call this GetRoomByIdAsync. And it'll take a Guid which is the roomId and it'll also take a CancellationToken called ct. I need to import a couple of namespaces.
Accepting a cancellation token for Async methods is a good pattern because ASP.NET Core will automatically send a cancellation message if the browser or client cancels the request unexpectedly. Now let's add an HttpGet attribute. We'll need to pass some route templating information to this attribute. We want this route to accept a parameter called roomId. And that'll be bound to this roomId parameter that gets put into the method. I'll name this route after the name of the method, GetRoomByIdAsync.
And now we can write the body of the method. The first thing we want to do is try to pull out an entity from the database that matches this Id. So we can say var entity equals await _context.Rooms.SingleOrDefaultAsync which is a method we'll have to add a using for. Now we can pass a lambda that selects the room Id. The room.Id must match the roomId that we got from the method. And we'll also pass the cancellation token into the Async call.
Now Visual Studio will complain that we can't use await unless the method is Async, so we'll add the Async keyword. And single or default will return null if there is no matching room with that roomId. So we can check here. If the entity is null, we'll simply return NotFound which will return a 404 error back to the client. If the entity was found, we need to map the entity properties into the room resource. So we'll create a new resource instance of Room which we need to import. And we'll populate these properties.
The Href will be provided by Url.Link with the route name of this route, GetRoomByIdAsync. We also need to pass an object that will contain the route parameters. In this case the route parameter needs to be the roomId which comes from entity.Id. And the other two properties are pretty straightforward. Name comes from entity.Name and the rate, which is a decimal on the room resource, comes from entity.rate divided by 100, to convert from cents into dollars.
Once we've constructed this resource, we can return Ok with the resource data. Now directly referencing the db context inside of the controller isn't a great pattern. But we can refactor this out later. Let's test out what we have so far. I'm going to set a break point in this method so we can see execution enter the method when we make a request. And I'll start it up. I'll switch over to Postman and make a request. First, I'll make a request to slash rooms slash foo which I know doesn't exist.
So we can see execution fall into this method correctly. The roomId is null because that's not a valid Guid and ASP.NET Core wasn't able to map it. Since that roomId is invalid we should see the lookup to the database fail. And the entity should be null exactly. So it'll return not found to the client, which results in ASP.NET Core sending back 404, exactly what you'd expect. Now let's look up an Id that does exist. I've copied the Id of one of the rooms and I'll paste it here into Postman.
This time the lookup should succeed and we should see a room entity come back. This will be mapped onto a room resource which will be returned to the client. I'll let this continue. So now we have a resource response with a full Href including the roomId and the rest of the properties that we mapped over. One problem that you might notice is that the rate does not have a decimal point. But it should. There's a reason for that. If I switch back over to the code, we're dividing rate, which is an int, by another int, which means that any decimal part will be thrown away.
What we need to do is divide by 100.0 and intentionally cast it to a decimal which is the data type of the Room resource rate property. If we try that request again, we should see 101.19 which was the value saved in the database. Now that's looking right. Okay, so we've successfully retrieved data from our database and returned it as an ION resource to the client.
- 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