Building APIs with C# and ASP.NET Core

JSON API Routes

Spencer Schneidenbach

Spencer Schneidenbach

Aviron Software, Microsoft MVP
Building APIs with C# and ASP.NET Core

Check out a free preview of the full Building APIs with C# and ASP.NET Core course

The "JSON API Routes" 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 continues building the employee API. Routes are created to get an employee by ID and create an employee. A route group is added to the application to simplify the shared URL pattern with the three routes.

Preview
Close

Transcript from the "JSON API Routes" Lesson

[00:00:00]
>> Spencer Schneidenbach: We need to keep going, build out this API. So we're gonna start with the most basic possible in-memory database. We're gonna go back and reach back to our list. So this is definitely, we will be taking this application as we go and refactoring it to use something real.

[00:00:15]
This is not something that I would write for a production app, but we wanna take this one step. We're gonna take this and just show you the API as it should be built for now, and just kind of forget about the data storage. So I'm gonna say, new list of employee.

[00:00:30]
Boom, okay. So this is gonna be our data store for all of our employees. We'll go ahead and hard code in a couple of employees. Let me go down here, just copy paste these in, use array initializer syntax, just go there with little squigglies, or actually, we'll just call employees.add, which is what this does.

[00:00:49]
It will tell you, collection initialization can be simplified and give you that nice little light bulb. So we'll go ahead and do that and make it look all pretty. All right, now let's finally write our first employee end point. So I'm gonna say app,
>> Spencer Schneidenbach: Dot MapGet, and we're gonna say /employees.

[00:01:09]
Now, I mentioned that, we could use request delegate or we could use delegate. In this case, we're just gonna use delegate. And like I said, we're gonna use delegate pretty much for the remainder of this course. So, it can be any old function. It could even be a function that doesn't return anything if you so choose.

[00:01:24]
But in this case, we are gonna return something, which is we're gonna return our employees list. Now, this is something that we can do, and if we play this application,
>> Spencer Schneidenbach: And run it, go back here, 5230, and then we're gonna say employees, and you're gonna see that, yeah, we get back our data.

[00:01:49]
But this is one of those spicy tech moments, which is that, you can choose to return just the employee list as it is. But in pretty much every case, I'm gonna choose to just return it as the status code that it was intended to be. I never return anything that isn't tied directly to an HTTP status code.

[00:02:09]
And the way that you get those status codes is you can go and cut that, and say, results.okay. I do this for explicitness, I do this because I build a lot of APIs, and I want to be very explicit about the status codes that I'm returning to the client.

[00:02:29]
We'll also get a single employee endpoint. So, if we go down here, paste that in, and you can see that we are calling single or default. So, let me break this down a little bit, calling single or default to get that single person. But wait, how does it know what ID it is?

[00:02:46]
I mean, we're just passing it a function, right? Well, we can define what's called a route parameter. And inside of our route here, we can say, hey, you can expect that you will get an ID as part of this endpoint. And after you execute that, which we'll show you in a second, we just run our code to say var employee, employee single or default.

[00:03:07]
We're looking for an employee with that. And we're expecting that there's either gonna be 1 or 0 of this employee with that unique ID. If that employee is null, we're gonna say return results not found. Otherwise, we're gonna say return results employee, and you can see why, if you just tried to return the employee straight up, you can do that except for that the compiler is not gonna complain at you.

[00:03:32]
Because it actually thinks that there's a separate function signature that should be there. It really wants you to have one return type. But you can see how just trying to return like an employee's object, that constrains you to only having one status code. So, use the results class for sure.

[00:03:49]
Okay, now that we've got that, we can also constrain this to say that this should only be an inch. So, we can basically say that the route constraint that we've said is that anything that happens after employees/, it must match as an integer in order for this endpoint to be hit.

[00:04:12]
All right, let's take a look. Let's run these. We're gonna run, let's see, 5230, perfect. And then I'm gonna use my employees get, so you can see that we're returning the results.okay. But we get the exact thing that we expect. And then I am going to duplicate this and we're gonna say employee/1.

[00:04:34]
And you're gonna see that we get back our John Doe object because that's our employee with ID1. We give it something that doesn't exist, it's gonna give us a 404, not found. So far, so good, we got exactly the behavior that we want. Now, we're gonna add something to create the employee.

[00:04:50]
So, we're gonna do a little bit of cheat code. Normally in our database system, it would automatically pick the ID for us, but we're gonna go ahead and just say, we're just gonna take the next employee ID from the list, and just add 1 to it. We're gonna take the max and add 1, so this will be 3, and then it'll be 4, and so on.

[00:05:09]
And you notice that we did a map post, we're gonna do some mutation here. We're gonna actually go in and post our employee, so we're gonna use a post request, and we're gonna give something in the body of the request to actually get a to execute, so let's go ahead and do that.

[00:05:28]
>> Spencer Schneidenbach: Give it that, and we're gonna go to Postman. We're gonna duplicate this, kill that, and we're gonna match our endpoint precisely, and we're gonna send it and see what happens. And then it's gonna come Complain. It's gonna complain because it says, implicit body inferred for parameter employee, but no body was provided.

[00:05:46]
Okay, well, give it a body of a request. So in Postman, we know that we're Marshall and JSON back and forth. So we're going to give it a raw body, and we're gonna select JSON from this list, so that it knows to send JSON. And we're just gonna send an empty JSON object, and again, we'll see what happens.

[00:06:05]
And then it says, again, failed to read employee from this, it says, JSON deserialization for type employee was missing required properties, first name and last name. Well, okay, we can provide those, absolutely, so let's do that. We'll say, Mary and last name, Sue, hit Send. Okay, and it doesn't complain at us.

[00:06:28]
Now, it's returning our full object, and you can see that indeed increment the ID and go back to our Get Request here. And you can see, same thing here. So that's pretty good. One thing that I will say that I don't like is the fact that the messaging that we get back is literally just a giant stack trace.

[00:06:47]
So it's literally an exception that's being thrown in the pipeline. It is correctly returning bad request, but it's not really returning it in a very pretty format. We'll address that a little bit later as we go down the course. So I am gonna do one last refactor of our application.

[00:07:03]
I am gonna hit Stop on here. We don't have to map everything to this employees. We don't have to continue to specify employees every single time. We can use what's called a route group. And a route group, essentially, just allows us to group together a related series of APIs.

[00:07:20]
So let me show you what that looks like. So we're gonna go employee route. We're gonna say, app.MapGroup("employees"). And now down here, as opposed to calling app.MapGet, we're gonna say employeeRoute.MapGet. And we are actually going to take this out entirely, and have empty string, I prefer string.Empty, and we can do the same thing here.

[00:07:50]
Delete that, give it employeeRoute, and that'll basically group all these APIs into the same route. Functionally, equivalent to what we've done already, but gives you a little bit more, it's a little bit neater in terms of organization. We'll give it string.empty there. And you can see that, if we go here, all of our endpoints still work as expected.

[00:08:16]
Yes, Mark.
>> Mark: Do you have to restart the server every time you change stuff?
>> Spencer Schneidenbach: Do you have to restart that? Yes, you do. And in this case, well, you can actually turn on hot reload. I don't find hot reload works very consistently, and I'm just used to stopping and starting.

[00:08:29]
Hot reload has some limitations to it that don't make it very easy to use. So, when you're making the code change, yeah, you stop and restart, but it's pretty quick, the feedback loop is pretty quick. And we're gonna make it quicker here in the next couple of sections.

[00:08:45]
So now we have our blueprint for our app. We've got it nicely organized, so we've got everything under the employee route. No duplication of that employee string, that looks pretty good. I do wanna call back to this thing here, as I mentioned, you can in your MapPost or your MapGet, you can pass it any old delegate, any old function that you want.

[00:09:08]
The way that ASP NET Core is going to interpret these parameters is going to depend on what the parameter actually is. So I do wanna talk a little bit about parameter binding, cuz I do think it's really important to understand. Because this feels like magic right now, but there actually is a kind of a reason for why this works.

[00:09:28]
And the reason is, is these parameters are looked at by the ASP.NET Core, and then, it will assign what it thinks is the correct parameter type based on the function that you provide. So, this is the order in which it does that. So this is the order in which it interprets the arguments of that function.

[00:09:48]
First, it looks at parameters using bind or from attributes, which we'll go into in a second. It has special types like HTP context and cancellation token that are available. So you can, if you wanted to and I have done this sometimes pass in an HTTP context. And ASP.NET Core will interpret that and say, okay, well, we already know what the HTTP context is, so we're gonna bind it specifically that special type.

[00:10:14]
We're gonna bind it specifically to that thing. Then it's gonna do route parameters or query string, followed by any services that are required from the dependency injection container. We'll get into that a little bit later. And then finally, it will try to map from the body of the request.

[00:10:32]
The Bind/From attributes are really useful, in my opinion, I use them a lot. And the reason that I use them is because I like to be very explicit about what my API is doing. I don't like magic, as I mentioned earlier, so I like to take some of the magic away by instructing ASP.NET Core, hey, this is what you should expect this parameter will be used for.

[00:10:51]
For instance, I can tell it explicitly from body and it will bypass every other little check that it does and just say, he intends for this to be from the body of the request, so I'm going to interpret that as such. I like to do that because, again, I like to be explicit, I like to not rely necessarily on the magic of the framework.

[00:11:12]
I don't necessarily do them very often, I do them as as much as I can on body type of requests, but I don't do them as much on routes, but you absolutely can. So if you wanted to do from route, you could do that here. And then you could say, and then it explicitly instructs ASP.NET Core, like, hey, you should expect this from the route.

[00:11:34]
There's other ones like FromQuery. So this would be a value that you could get out of the query string, okay, so another binding attribute. You can specify from services to say, hey, I expect this thing should come from the dependency injection container. So, they're not recommended, strictly speaking, but they are very useful.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now