Check out a free preview of the full Building APIs with C# and ASP.NET Core course
The "Querying Sub-Resources" Lesson is part of the full, Building APIs with C# and ASP.NET Core course featured in this preview video. Here's what you'd learn in this lesson:
Spencer demonstrates strategies for querying subresources. Nested routes are a pattern well-supported by OpenAPI and most API clients. A better option is to create separate queries for the sub-resources. A GetBenefitsForEmployee route is created, and tests are added to ensure the route returns the correct employee benefits.
Transcript from the "Querying Sub-Resources" Lesson
[00:00:00]
>> Spencer Schneidenbach: Okay, let's talk about sub-resources. So up to this point, we've been working with a very one single resource, which is our employee resource up until now. In the real world, we have to work with other resources, that's just a thing that we have to do. So let's talk through some design considerations for that.
[00:00:18]
So one of the most common ways to query sub-resources is to use nested routes, let me show you what that might look like. So let's say we had our employee benefits, and we wanted to be able to get employee benefits. There's a couple of things we can do when we do our GET request, we can simply return it in the response body, right?
[00:00:36]
On each of our employees. Or we could say for employee 1, 2, 3, I wanna get all the benefits and make that a separate API, a couple of different things you can do, right? And it's really gonna depend which of those techniques you implement is really gonna depend on the needs of your system.
[00:00:55]
So that would be an example of using a nested route, right? Employees/ Id/ benefits. It's well supported by ASPNetCore, OpenAPI, and most people expect it, it's very common to see. So let's go ahead and go through the trouble of adding our employee benefits and sort of walk through this problem.
[00:01:15]
So I'm gonna go ahead and stop this. I am going to Open Folder, and I am going to go to we are on querying sub-resources 3-F, so we're gonna go to this lab. And I'm gonna deploy all those, and then I'm just gonna go to my employee file right there.
[00:01:35]
And it's kind of lonely, kind of like a Bob Ross of programming. Let's add ourselves a nice little employee benefits. I mean, I know this looks great, everybody says, when Bob Ross is adding a tree, why has he gotta add that tree? Until he adds it, and then it looks awesome.
[00:01:48]
Let's add ourselves some little employee benefits down there, that looks good, yes, we like it. That's thing one, but employees have benefits. Is that not the case? I certainly hope they do. So I'm gonna go down to my employees class and I'm gonna paste that in, and I'm just gonna basically say, here's my list of benefits, boom.
[00:02:08]
So now, we've created kind of an object hierarchy, right? We have an employee object, and then we have the benefits. There's one employee object and what we defined is basically a very rudimentary, what's called a one-to-many relationship. One employee can have many benefits, zero to n. So we have a couple of different ways we can handle querying these.
[00:02:28]
As I mentioned, we can include them with the original object which is good if you have a small, limited number of objects and you don't expect that list to grow. I don't know, I've not worked on a payroll system, but I would say the benefits are probably going to be, probably no more than 10 to 20 records, depending on how detailed they get.
[00:02:48]
But something like if you are on Amazon, and you're wanting to get all orders for a given account, that list is gonna be a lot bigger. And you're probably not gonna wanna send that list back in a single customer GET. You're not gonna say, here's all the orders.
[00:03:04]
I don't even know how many times I've ordered from Amazon, it's way too many times, more than I can count. That would be a good use case for putting it under a sub-resource or putting it under a different route so that you get those invoices another way. So we're gonna go with the simpler approach for now, which is that we are going to include it in our GetEmployeeResponse.
[00:03:26]
And of course, because we have an entity object and we don't wanna return that entity object to the client, we're gonna go ahead and create a GetEmployeeResponse, EmployeeBenefit class. So we're gonna put that here in our GetEmployeeResponse right there, and we're gonna paste that in. And then we're gonna say, we're gonna create a required property and require that our callers set it, which will create a nice compiler error.
[00:03:53]
Which we'll use to again, go and find it and fix it before we run our project. So we'll go to our GET method right here, you can see if our IDE is working as expected. Let's see, we certainly can go to our employee object, our EmployeeController.cs right here, and you can see yes, now the problems are popping in down there.
[00:04:20]
It says required member must be set in the object initializer, so we're not setting it here. So we'll go ahead and do that. So we'll go benefits and =, and then what do we have here? So we're gonna go ahead and Select the benefit from the core employee object.
[00:04:41]
So we're gonna go and copy that in, paste that here, tab over to make it look pretty. And you're gonna see that we are now getting our employee benefits from our employee record, and we need to do the same thing down here.
>> Spencer Schneidenbach: Cool, got rid of our compiler errors, our compiler is happy now.
[00:05:02]
But let's clean up a little bit of that boilerplate, I mean, that's a lot of duplication, right? So let's just add us a nice little factory method right here. Copy that in and put that right at the bottom of our controller. And now we're just gonna have one common way of creating our GetEmployeeResponses.
[00:05:21]
Do a little bit of cleanup, I like to clean as I go, and this is one of those cases. So now we've removed a little bit of duplication, and we've also had the benefit of actually saving us lines of code. And while I'm not strictly speaking a less lines of code is always better in this case, I'd say it looks a lot better.
[00:05:44]
Go here, create EmployeeResponse, our EmployeeResponse.
>> Spencer Schneidenbach: And then we will open that parent, call that method, employee, and then delete that extra parenthesis. Okay, it's a lot cleaner, that looks a lot nicer. Nice little cleanup there, I like to keep things neat and tidy. All right, so it's okay to get the objects, but it gets more complex when we think about adding and removing sub-resources, right?
[00:06:12]
Now, we wanna be able to mutate those, we wanna add benefits, we wanna remove benefits. Well, do we wanna do that through the parent object? Probably not. There's a lot of reasons why you wouldn't wanna do that, but that essentially means that you could design an API where you have to send every employee field to your API.
[00:06:30]
And then send all of the benefits and then write a bunch of custom nasty logic to add them or remove them, depending on whether or not they're present in that list. Do we create new endpoints that handle the sub-resource data? Probably, it's a little bit of a cleaner way, requires less nasty code.
[00:06:46]
So you have a benefit, a post request that's specifically for created benefits. And then how do we handle updating? Assuming that we're able to just straight update benefits, a benefit is probably an object that in the real world, is gonna require some workflow. But keeping it simple for now, maybe we just have a single update endpoint for benefits as opposed to trying to update a batch of them for a specific employee.
[00:07:09]
I say that let's get to this question a little more once we have a real data source. Without that, without a real data source, cuz our repository, got a little spoiler for you, is not gonna live much longer. Outside of that, we're gonna just throw all that code away anyway, so we'll talk about it when we get there.
[00:07:25]
So we'll talk about instead querying them separately. This is the other common way of dealing with sub-resources, and this is the one that we're gonna go with. And so I'm gonna copy this in to here, I'm gonna copy that into my controller here. Put that in there like this, and we've now created a new GET endpoint for on an employeeId, now we can get that Employee'sBenefits.
[00:07:52]
And it's gonna behave very similar to our single GetEmployeeId, which is to say that we now have an endpoint that we're gonna get the employee by Id out of our repository. And if that employee is null, aka can't be found, we're gonna return not found. Otherwise, we're just going to return a list of the benefits.
[00:08:12]
Pretty straightforward, but of course, we don't wanna return our benefits object directly. So we're gonna do just a little bit more cleanup and just say, create our little factory method to convert our EmployeeBenefits to an GetEmployeeResponseEmployeeBenefit object. So I'm gonna use Select, I'm gonna use a little link.
[00:08:35]
I'm gonna pass this reference to this method, you could do these one of two ways. You could say, b goes into and then GetEmployee or BenefitToBenefitResponse, that's the name of our method, BenefitToBenefitResponse, and give it b. Or we can just skip all of that, you can see that it says, you can just remove this lambda expression directly and just pass it the function directly, which we can do here, which is what we'll do.
[00:09:07]
It's a little bit of link magic, perfect, it's looking good. Okay, now we can use that method. And we're not only, we've created a sub-resource for our benefits, and we've created an object that will represent our response back to the client. We're not using entity objects, so we're looking really good.
[00:09:28]
We can also add a test for it. Now, recall that we just extended our employee object, so we have to do just a little bit of work to make sure that that employee has benefits that we can actually get. So we'll just continue to use John Doe here as our test subject.
[00:09:44]
We'll go to our tests, go to our UnitTest.cs, and we'll go ahead and add John's benefits, which in this case it's BenefitType.Health and it costs $100. Okay, then we can go ahead and write our test as well.
>> Spencer Schneidenbach: Boom, go through that test in a second, but let's do a little manual check.
[00:10:09]
Let's get the satisfaction of seeing our benefits. All right, let's see. So if we get our employees, I don't think, do we, yes, what's our code right now? 5129, or did something did not compile? That's right, tried to run the test project.
>> Spencer Schneidenbach: Okay, we're gonna get all of our employees.
[00:10:32]
Yeah, I was gonna say, I think that array is empty. We'll go ahead and add 1 to our project real quick.
>> Spencer Schneidenbach: Up here, we'll go to our UnitTest.cs.
>> Spencer Schneidenbach: Copy our repo, do a little bit of magic. Where's our employees going? These employees aren't used at all.
[00:10:56]
All right, so we can go to our EmployeeRepository. We'll just new it up, EmployeeRepository, new EmployeeRepository, boom, boom. And then foreach var e in employees, we'll do EmployeeRepository.create e. And then we will go here, and instead of just having it instantiate the type we'll provide it directly, EmployeeRepository, and let's just give John Doe a little bit of benefits.
[00:11:33]
>> Spencer Schneidenbach: Perfect, there we go. Give John Doe a little benefits. And now we've got some data inside of our repo that we can use to get in our GetEmployee call. Now we see that we have data, and you can see that we get our benefits back. What is John Doe's Id?
[00:11:50]
John Doe's ID is probably, I'm gonna guess it's 1, so I'm going to say 1 and benefits. And you're gonna see that we get John's benefits back the BenefitType and the EmployeeId, EmployeeId should be set though. But at any rate, we'll get to that when we get to our entity framework part of the talk.
[00:12:11]
And then, of course, we can go back to our test, we can build our tests, and we can see that we have our benefits for employee's test. We can run it, we specify that John has two benefits up here. So in our test, we assert that that's how many benefits that we get back, and that is the case.
[00:12:31]
That's how many benefits we get back, we get some passing tests and for good measure, let's run the rest of them too. All green, we like it. So that's some considerations that you might make when you're adding sub-resources in for your API. Do you return it in the parent object?
[00:12:48]
Do you return it in a sub-object? Do you create them in the parent object? Probably not, it's best to do it inside of its own resource. So in this case, create a benefit API, and then create and update those benefits from there.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops