Main

WebAPI Versioning

You've already got clients accessing your RESTful API and now you want to provide new features. You need versioning. Source code available at: https://github.com/JasperKent/API-Versioning Topics include: - ASP.NET Versioning - Swagger - Adding, removing and modifying API features - Reusing existing features in a new C# controller - Reporting available APIs to the client - Defaulting to the existing API

Coding Tutorials

11 months ago

We've been talking quite a lot recently about  Web API, and so I thought this week we'd look at the idea of Web API versioning. So if you've  already published your API, you've got customers who are using it, how we can then modify that not  by making any changes to the existing API, but by producing a new version so that those clients who  want to use the new version can use it, those who want to stick with the older version can stick with  it as well. So let's have a look at what we've got for
that. And what we're going to do here is use  the application that we were using over the last few videos, when we're demonstrating Swagger  and things like that. So it's pretty simple. What we've got is a SQLite database, we've got  an entity called BookReview that just has an Id, a Title and a Rating. We've got a load of  those reviews already in the database. We've got Entity Framework and a DbContext connecting  it all up. So just look at the earlier videos for the details of that. But then
we've got our  BookReviewsController. And so this is where we've got a number of endpoints for manipulating  this data. So we can get all the reviews. We can get an individual review by Id. And we can do, for  example, have a summary of the average rating for each one of the titles across those reviews. And  we've got posts and puts and things like that. And if we just run that up, we can see that it's all  working. And so we get Swagger showing us what's going on, we can do something like look
at all the  reviews. So if we just try that out, then we get some JSON back showing us all the reviews we've  got. So more than one review for each title. And then that's what feeds into the Summary down the  bottom here. So if we try that one out, it will run through them all, and then just give us the  average rating for all of the reviews for that particular book. So we can see that Dr No has an  average of 3.5. So that's our current API. And so then we may make a decision that we want to mo
dify  that. Now there's a number of ways you can modify it; the simplest one is just to be adding a new  piece of functionality. But you might also have the idea that you're going to remove functionality  - say we're no longer able to post reviews on this interface, something like that. You might also  want to modify existing functionality. So add a new parameter to one of those endpoints. And  indeed, you might want to make a big change, like changing the entire model. So our model  that we've
got here, our BookRreview is defined with Id, Title and Rating - if you added a new  property in there, that would mean that pretty much all of the endpoints were going to change.  And so that would be quite a big modification to your API. And we want to do that in accordance  with things like the Open Closed Principle and the Interface Segregation Principle. So we  don't want to be modifying the existing API; we want to leave that for those people who want  to use it and then add a new one, so
that people who do want to move forward with the new features  can do so. And the new feature we're going to add is simply - rather like the summary we've got  here, which gives us the average rating for all of the titles - we're going to have a summary that  takes the title as a parameter and just gives us back the average one of those. So it's going to  be an additional new endpoint. You could argue, well, we could just add that into the existing API  - it's not going to break anything, becaus
e people aren't going to be using it unless they know about  it. But that actually would still be a breach of the Interface Segregation Principle, in that it  would mean - if you remember the definition of Interface Segregation Principle - that interfaces  should not have methods that clients don't use. And that would happen in this case. Much better  if we produce a new interface with a new feature, and then those clients that want it can  migrate to it, those that don't can stick where they ar
e. So that's what we're going to  do. So let's go in there and let's do this in a very simple way to start with. Let's just add  a new controller, and we're going to call this ‘BookReviewsV2’ - version two – ‘Controller’.  And then I'm going to make a slight change, because I've adopted here the convention we're  not going to have ‘api/’ on there. So if we look at our original one, we've just got Route to  the [controller]. So the same sort of thing here. Then what we need to do is we need to ge
t  hold of a couple of things. We need to get hold of the repository, so we'll have a ‘private  readonly IReviewRepository’ so that we can get all the data. We'll call it ‘_ReviewRepository’.  But then additionally, we also need to get hold of the other controller, because we don't  want to be repeating ourselves in terms of the functionality that's already there. So we're going  to have a ‘BooksReviewController’, call it that, then we're going to have a constructor that will  inject the reposit
ory. So we'll pop that in there and then just move that back up to where it was.  Then in the constructor, as well as having the repository injected, we're also going to create  the other BookReviewsController and pass it the repository there. So that means we can make use  of that for anything which is the same in both the old controller and the new one. And then  for the most part we're just going to delegate over. So actually, let's go in here and let's  grab all of those endpoints and just p
aste them into the new controller. But we're not going to  use those implementations, because that would be code duplication. So what we then want to do  is simply delegate each one of those to the existing controller. So in here, I'm just going  to say ‘return _BookReviewsController.Get()’. And then we don't need to worry about anything  else. And similarly, we'll do that in the get by Id. So return and then ‘_BookReviewsController.’ And it  works out for us. Similarly, in the summary. And all
the way through, we can  just put those in there. So for the puts and for the delete. So that's got all the  same functionality now in our second controller. Then we want to add some new functionality. So  remember, we're going to do this as a summary for a particular title. So let's base it on what  we've got there. So this is now going to be called ‘summaryfortitle’ - change the name of the method.  So that's also ‘SummaryForTitle’. And that's going to take as a parameter the title, so we'll 
just put that there on the route and also in the method. And then what we're going to do is we're  going to say ‘var reviews =’ and then this one, we directly access the repository because we  can't get this from the other controller. And we'll say ‘.AllReviews.Where’ and then it's worked out  for us where ‘review => review.Title ...’. So that's going to give us all the reviews matching  this particular title. We've then got to worry about the fact that actually there could  be nothing for that
title, if it's an invalid title. So we're then going to say ‘if’ and it's  given us ‘reviews.Any()’, but we actually want ‘!reviews.Any()’. And if there aren't any,  we're simply going to return a ‘NotFound’. And that means, of course, we've  got to remember, in our metadata, we've got to announce that we're returning  a NotFound. So we can put that in there and pick the NotFound. And then if we have  got some reviews, then we're going to say ‘var summary =’ and we're going to have a ‘new  BookR
eview’ and that's going to have a ‘Title’ which is simply ‘title’ - the parameter that’s  come in. But then it's going to have a ‘Rating’, which is all of those ‘reviews’ for this title,  and then ‘Average’ and then ‘r => r.Rating’. So the rating is the average of all those ratings. And  then we can simply return on ‘Ok’ of ‘summary’. A couple of other things we have to do, though.  Because we're now just returning a single item, we're going to change the return type,  and also change the type s
pecified in the ‘ProducesResponseType’. And so that should  be that all working. So if we now run that up, we can see that Swagger is now giving us two  completely separate controllers. So we've got two different API's. We've got the original one  that just has the original methods. And then we've got the new one that has all of the original  methods, plus the additional ‘summaryfortitle’ that we gave it there. So if we just check  that out, we can see that the get all works in exactly the same
way, so that just delegated  through to the original controller. Whereas the summaryfortitle - that's the new one. So if we  try that one out, and we go for something like ‘Goldfinger’ and execute that, then you can see  we're just getting the average for Goldfinger. And indeed, you don't have to use Swagger, obviously.  If we just take that URL and paste that in there directly, will still get the same result done  directly. So we've kind of done it. But we haven't really done actual versioning,
because all we've  got here is two separate APIs that happen to have similar names. And we've effectively used just  a naming convention to indicate that we've got BookReviewsV2 and the original one - that we might  have changed the name to V1 or just stick with BookReviews. But we can do better than that in  terms of having a much clearer definition of the versioning. So that's what we're going to do here.  So the first thing we're going to do is we're actually going to make it so that both of
these  controllers have the same name - they're both just called ‘BookReviewsController’ - we don't put the  version in the name. Now obviously, that's going to cause a problem because you can't have two  classes with the same name in the same namespace. But we're going to fix that. We're going to put  in separate folders for each of these. So let's put in a new folder that we’ll call ‘V1’ and  then another new folder that we’ll call 'V2'. And then let's drag those controllers in there.  And sa
y OK for that. Notice the nice thing it does: ‘Adjust namespaces for moved files?’.  So remember, that's now going to be in the namespace ‘Controllers.V1’. So it put that  one in for us. Same sort of thing with V2, drag that in there. Click OK on both of those. And  so that one is now in the V2 namespace. Next thing we're going to do though, is we're going to change  the name here. So let's rename that to just being ‘BookReviewsController’ exactly the same  name, but that is now valid because th
ey're in different namespaces – V1 and V2.  But now we've got a bit of a problem, because that's now going to be recursive. So this  one - the one that we're delegating to - we have to call ‘V1.BookReviewsController’, and the same  down there. So that's now what we're delegating to. So that's now given them the same name, put  them in separate folders, and it should still be working. If we run that one up however, then  we can see we're getting a problem. And it's not just a problem with Swagger
- this is not going  to work at all. So if we now go to a different tab and paste in that URL we had before, get rid  of that V2, because it's now just going to be ‘BookReviews’. So ‘/summaryfortitle/Goldfinger’  - that one actually worked. So that's not a problem. The problem is, if we just went  for something like ‘BookReviews/Summary’, that gives us a problem because it can't tell  which one we're supposed to be working with. So the routing failed because there are two things  with exactly t
he same route. So now we need to make some changes to that as well to get that  to work. So what we now need to do is actually introduce a NuGet package that is going to help  us out with it. So if we go in here, and just do a search on ‘MVC.Versioning’, that gives us the  package to give us the versioning information. So let's install that and then the next thing  we need to do is configure that. So we'll go into our Program and in here we're going to say  ‘builder.Services.’ and then ‘AddApiVe
rsioning’. So that's been introduced with that new  package. And that takes some ‘options’. So we’ll put in those options, and the options  we can go for - we'll talk about the details of these, maybe, in a later video - but the  simple ones we're going to go for, we're going to say ‘options.DefaultApiVersion’ and then we're  going to say ‘= new ApiVersion’ and let's get rid of the namespace there and just put it in as a  using. And then this takes two parameters, which are the major version and
the minor version. So  that's the degree of control we’ve got: major and minor. So it's going to be version ‘1.0’. So we're  defaulting it back to the original, because that means that anybody who was working on the original  will not have their functionality changed, they'll stick with what they've got. Let's also do a few  other things. So we're going to say ‘options.’ and then ‘AssumeDefaultedVersionWhenUnspecified’.  So again, that's kind of related to same thing, that it means that anybody
who is unaware of the  change and doesn't include a version - they're just going to get the one that they want. And  then we're also going to say ‘options.’ and then ‘ReportApiVersion’ and set it ‘true’. And that  means that when a request is made to this API, we're going to get sent back in the response  header information to say what versions are actually available, so that anybody using  it can see that there may be a newer version for them. Then we need to make some changes to  the controll
ers themselves. So we'll go to the original one - the version one - and on there I'm  going to firstly add the version information. So we're going to put another attribute which is  going to be ‘[ApiVersion]’. And this is going to be version ‘1.0’, and so that's specifying  the version there. And then also, we're going to have to specify what the route is going to be  including the version. So I'm going to take that existing route and then we're going to change  that. So we're going to say ‘v’ f
or version, and then we're going to have curly braces and  we're going to say ‘{version:apiVersion}’. So that means that it's going to automatically  insert the version number after the ‘v’ there. So this is actually going to be ‘v1/BookReviews’. We  could have hard coded that, we could have used a different convention other than just having a ‘v’  there, but that's the simplest way to do it. So it'll take basically the ‘1.0’ from there and it  will put it in there. So that's where we're going t
o go. You'll notice though, we leave the original  route in there. So both of these routes will take us to this version one controller, and that,  again, is for backwards compatibility. So anybody who was accessing it by the old route doesn't have  to upgrade, that will just stay the same. We're then going to take that and we're going to put it  into the version two with some slight changes. So we're going to change that obviously to ‘2.0’.  Leave that exactly the same, that's fine, But then we'
re going to remove that one because  we're not defaulting to 2.0, we're defaulting to 1.0. And so those are the changes that we  need to make that. And if we run that one up, we're still failing on the Swagger, but the rest  of it should be working okay. So if we go to ‘localhost:…/BookReviews’ then that, remember,  is doing the get, and it's doing the get on the version one controller, because that was the  default. We can demonstrate that if we just go into Visual Studio, go to the version  on
e controller and go to the get for that. And then just run that again, we can  see that’s what we're hitting there. Similarly, if I were to change that to  ‘/V1/BookReviews’ then we hit the same one again, because although we could go with the default,  we can also go with the name. But if I were to put in another breakpoint on the get for V2  – which, remember, is just delegating to V1, but it's going through our new controller -  then in that case, if I change that to V2, then we hit the V2 co
ntroller, which then  delegates through to the V1 controller and that then gives us a result in exactly the same way.  You could also actually put in ‘V2.0’ there and it will still do the same thing. So if you've got  ‘0’ as your minor version then you don't have to put it in, but you can. So we can do that ‘V2.0’.  But what we can also do as well is we can now, on this one, do ‘/summaryfortitle/Goldfinger’, and  that's working as well. So our version two has the ‘summaryfortitle’, which obvious
ly our version one  is not going to have. And that's the whole point, that we've separated these two out. So we have  now got our two different versions - version one, version two of this particular API - with the  addition or whatever changes we wanted to make. And while we're here, we can just take a  quick look using the DevTools at what was coming in there. So if I refresh that, there we  can see Goldfinger. If I click on that and look at the headers and just scroll down a little  there, you
can see in the Response headers, we've got that API supported versions ‘1.0’ and  ‘2.0’, which is what I configured it to do in the Program when we have that ‘ReportApiVersions’.  So we've got all of that working, all of that information being reported, but what we haven't  got - and we're going to see this in the next video - we haven't got Swagger working with this,  because there's a fair bit more work to put that together. So next time, we're going to look at how  to make Swagger work, and
also look at a few other details of what we can do with the versioning. So  I hope you found that helpful. If you did, do like, do subscribe, and I'll see you next time to  look at how we can make that work with Swagger.

Comments

@CodingTutorialsAreGo

Do your API vary much, or once published are they stable? Let me know. Source code available at: https://github.com/JasperKent/API-Versioning Remember to subscribe at http://www.youtube.com/channel/UCqWQzlUDdllnLmtgfSgYTCA?sub_confirmation=1 And if you liked the video, click the 👍.

@syafiighazali

Clearly explained and engaging, thanks so much!

@zaharivaklinov

Nicely done and well explained! (:

@10Totti

Another best tutorial thanks!

@syafiighazali

If v2 of a controller action takes in a model with extra/modified properties, do I create a new model? And should I create a new repository class to handle v2 controller actions?

@VindicatorMorty

Interested in the next parts because those are the different ones. I am just curious about the decision to go straight to 2.0. Basically the summaryfortitle endpoint is a new Feature. That would mean for every Feature you raise the major Version. What if you Just add a property to an existing Model. That could be handled tolerant (by ignoring it) within the api consuming Client. Imo that would be a raise of the minor Version of the model itself.