Check out a free preview of the full Building APIs with C# and ASP.NET Core course
The "Refactoring to a PUT Request" 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 refactors the API and adds a PUT request to update an employee. New request and response classes are created as an abstraction between the Employee entity and the data passed to and from the API. This abstraction gives developers more granular control over what's exposed through the API and what is available in the database.
Transcript from the "Refactoring to a PUT Request" Lesson
[00:00:00]
>> Spencer Schneidenbach: The next section is about add a PUT request in this section to actually update our employees. And then we'll do some refactoring to actually take away using our entity directly, which is our employee object. And we're gonna add a little layer of abstraction between the two. So, first things first, I'm gonna go ahead and close this solution file and open up the next one, solution directory.
[00:00:21]
There we go, TheEmployeeAPI. We're gonna go here and we're going to open this project. So this is gonna be our starting point,
>> Spencer Schneidenbach: Right here, good. And when you open up the refactoring to request classes part of the exercise, you can see that we have all of our test project, everything that we add.
[00:00:42]
All of these projects are additive. So everything that we do in one section, the code in the next section will have all that completed code. So you can peek ahead or run it, if you like. So first things first we're gonna do is we're gonna expand our employee object a little bit.
[00:00:57]
So I'm gonna copy and paste these properties in. Let's talk through why I'm doing that. First of all, this is a very common thing, where we have to go in and add something to another thing, to an existing thing. We've got a new feature request, and we need to add some new behavior.
[00:01:14]
So, note that I added a Social Security number seems to fall that an employee should have a Social Security number. And then we have a huge amount of optional fields, including phone number and email. And then we have all of these address fields as well. If we go back to our program file, you're gonna notice that our employee is unhappy.
[00:01:39]
The compiler says, hey, the required member or Social Security number needs to be there. And that's because we put Social Security number in as a required parameter, as a required property. So I'm gonna go ahead and do that. So SocialSecurityNumber = 123-456-3445. I hope that's not somebody's real Social Security number.
[00:02:00]
[LAUGH] And hey, what do you know? 3446, maybe they're twins. Okay, so, the following requirements get handed down from boss or whatever, right? Because we are constantly changing our system, we're constantly growing, we're constantly evolving. We need to be able to always request a first and last name.
[00:02:23]
First and last name needs to be on every single employee object for updating, and we need to be able to update their first name, last name, address, phone number, and email at any single time. So let's talk about PUT requests. So as I mentioned earlier, in RESTful API design, and I put that in quotes because REST is a whole different topic I don't wanna get into.
[00:02:43]
But the colloquial kind of way that people talk about RESTful APIs often talks about, how do you design your JSON APIs? And it's very common to have your GETs, just simply do GETs, and your posts, do some kind of creation or some kind of update, or some kind of action associated to an object.
[00:03:02]
And that PUTs are used for kinda updating objects in place. So that's what we're gonna do. We're gonna have this PUT request come in. So we're gonna say, put requests, we can typically use them to update a resource. So we've got all this code here, and I'm just gonna walk through a little bit of it for you.
[00:03:20]
Paste it in, and you're gonna see that very similar to our map get where we get the ID of an individual employee, we have a put request that allows us to also update stuff about that employee. We have the employee object coming in, if you wanted to, you can prefix with FromBody.
[00:03:45]
>> Spencer Schneidenbach: Import this, there we go. You could say this is from the route. Usually I don't bother with the from route attributes, and then I say, okay, I'm gonna look up the existing employees in my employee repo. I'm gonna say, if it doesn't exist, then I'm gonna return a NotFound, very similar to how we do our GET.
[00:04:02]
And then we're gonna do some very boring property assignment all the way across. We wanna take our employee here and update our existing employee, and then return that to say, hey, yes, we updated this request. So pretty good so far. We, of course, wanna write a test for this behavior.
[00:04:19]
Let's test it manually first. Go here,
>> Spencer Schneidenbach: And we're gonna say, we see now that our documentation includes a PUT request here. And we're gonna go try it out. We are going to say id of 1. This is just doing it in Swagger real fast. And then we're going to say, well, this is the body of the request, it's just giving us kind of a little blueprint.
[00:04:43]
We're gonna go ahead and go with it. Take out the id, I'm gonna say, Execute, and I should get back a 200 response. And you'll see that if we go back to Postman and look at our employee request, oop, it's 5129. And by the way, somebody asked me if your port number differs from mine, is that a problem?
[00:05:08]
And the answer is no. They are gonna differ, but if you put them in, they're just randomly assigned by ASP.NET Core. So just make sure that it's this number here and you're good to go. And you can see that our PUT request did succeed, because we got all that stuff that Swagger gives out of the box, firstName string, lastName string, and all that good stuff.
[00:05:28]
Pretty simple, pretty straightforward. Next, we're gonna actually test our endpoint with a test, or at least we're gonna write something that actually tests it to make sure that if as we change the behavior of this thing, that the behavior doesn't change. So we're gonna go down here, we're gonna go paste in our test, and you're gonna see it looks very similar to the other ones.
[00:05:52]
And we're just gonna assert that our status code came back as successful. You could also write a test to say,
>> Spencer Schneidenbach: UpdateEmployee_ReturnsNotFoundForNonExistentEmployee.
>> Spencer Schneidenbach: Give it 9999, something that we know doesn't exist, and we'll run our tests, dotnet test. It's complaining about something. Let's go here, let's go to the problems and see what we've got.
[00:06:29]
What do you have? What do you got for us? Output, here we go. Must be set in the object, okay, perfect. We'll go ahead and do that, Social Security number 123-456, and then we'll just copy this across. Just to be nice and fast, boom, boom, boom, boom, boom, there we go.
[00:06:54]
Okay, now we'll go back to our tests, run our tests, and they should all be passing. Right, we didn't update this to say, Assert.EqualHttpStatusCode.NotFound, response.StatusCode, perfect. It's the only test that we have failing, so we'll rerun the tests, and we'll see that they all pass now. And then the new requirements are handed down.
[00:07:27]
Boss calls and says that we have a change request. Apparently people are abusing the fact that you can update the first name and last name in the employee. And they also wanna hide the Social Security number from the original GET request, and they wanna require getting that information through a separate workflow of some kind.
[00:07:42]
We'll worry about everything, but except the last part, we won't get the Social Security number. Our GET inputs are officially obsolete now and boss really wants us to do this quickly. So we're gonna go ahead and do it. The first thing that we're gonna do is we're gonna just comment out some of the behavior that allows us to change the first and last name.
[00:08:01]
Okay, well, that seems pretty straightforward. We're gonna go down here and get a little room and say, well, boss said we can't update first and last name, we'll just comment things out, right? It's pretty simple. And then from our GET request, we can just go ahead and spin through each of those employees that we get and just null out the Social Security number.
[00:08:19]
That should do it, right? We'll go ahead and copy that, put that here. We don't wanna return the Social Security number. We're actually gonna not make it a required field now, because now, let's see, we don't want the compiler to complain.
>> Spencer Schneidenbach: Perfect, kinda perfect, right? We're gonna do the same thing down here.
[00:08:44]
We get an individual employee, we wanna say, employee.SocialSecurityNumber, and just null that out. A job well done, you deploy that, right? Well, now we have a problem, which is that we have an inherent mismatch between our storage, which is our employee, and our request objects. And this doesn't seem like a big deal when you're first starting out, but as your API grows in complexity, you're gonna find there's countless examples of where this will happen.
[00:09:12]
Where you need to return data that's in a very different shape than how it's stored. And to be fair, the real world isn't made up of GET, PUT, and POST, and DELETE, at least not completely, of just dumb objects. Usually, you have some additional actions or behaviors. The real world has a lot of different workflows.
[00:09:31]
Then APIs can be modeled to do them in lots of different ways. So having your storage object be also the object that you do requests in is kind of impractical. We may not like unit taskers in the kitchen, as Alton Brown would say, but in programming and in ASP.NET Core, I find them to be extremely valuable because I want this thing to be good at this thing.
[00:09:51]
I want my employee object to be really good at being an employee object. I want my employee request objects to be really good at being employee request objects and just let them be. So we're gonna do a little bit of refactoring here. We're gonna get rid of returning our employees directly.
[00:10:07]
We wanna separate our storage medium from the responses that we get. So we're gonna go to the Employee API and we're gonna add a new folder and we're gonna call it Employees. Then I'm gonna go ahead and add a new file, I'm gonna call it Class and get employee response.cs.
[00:10:26]
And then we're gonna see that I have a lot of things here that I could use, that I could just copy and paste in. So let me kinda walk through what these are and why they are useful. First of all, we have a specifically separate request for creating our employees.
[00:10:46]
We already know that our first and last name are required when we create an employee. I've kinda changed the game a little bit and just said, our Social Security number is no longer required when we create them. And then we have our get employee response. This Instead of returning the employee back for the employee object back, we return back the get employee response.
[00:11:05]
And you notice that we are missing our Social Security number, which is exactly what we want. That's exactly the requirement that we want. And then our update employee request does not allow us to update the first, last name, or Social Security number, exactly as our requirements state. So let's do a little bit of refactoring.
[00:11:22]
I am going to copy this code in to alter our routes,
>> Spencer Schneidenbach: And we'll walk through each of these examples and see all of the changes that we have made. I'm gonna delete these, paste these in. So a couple of things, first of all, it's gonna complain that we need to import some stuff.
[00:11:46]
That works, so we'll import that namespace because we put it in a separate folder, put the classes in a separate namespace, which is what we expect. First thing that we're doing is we are now using a little bit of link. We're actually saying, hey, I want you to project, I want you to select this thing, I want you to create something different from it.
[00:12:02]
So we're giving it our original employee object, and then we're newing up a get employee response, and we're putting it in this shape. And we're sending all of those relevant properties. And you'll notice the get employee response, as we said, does not have a Social Security number anywhere.
[00:12:16]
IntelliSense isn't finding one, which is exactly what we wanted. And then from our single map get, we are doing the same thing where we get employee response here, okay? And our create employee request is used in the same way. Instead of having the employee be the body, the FromBody, put that there for make it super explicit.
[00:12:39]
Instead of our post request taking in that employee object, we're taking in the create employee request. And then we're creating a new employee object and then adding it to our repo. And then we have our update employee request, which again, is different from our create in that we do not want them, in this particular endpoint, to be able to update their first name and last name and Social Security number.
[00:12:59]
So we have all of that, which is exactly what we want. And we can run our tests and see to make sure that we did not break anything, right? We could go through and test this ourselves, or we can just get a pass just a few seconds later just by running our tests.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops