Our product is a client/server app that has multiple versions of the client out in the field but has only one server that runs the latest version to service all API calls. We have/will have hundreds of API endpoints and I'm trying how best to handle versioning. What I'd like to do is be able to avoid the laborious task of applying attributes to every single method, or copy entire controllers every time we make a minor change.
I might be misinterpreting most documents/practices on this, but it seems like every time you bump your API you have to go through and do all that work, which seems inefficient at best.
Instead what I'd like to do is apply an attribute to each endpoint with the version of when it was written, then the client finds the the closest version that is equal to or less than the client version.
For instance, if an endpoint was written at [ApiVersion("1.0")] then that is the attribute it gets. If we had to modify it, I'd copy the method, rename it, apply a RoutePrefix attribute so it gets properly hit and apply a new attribute with the version of our whole API (in this example I put 1.5).
Here is a simple example:
[HttpGet]
[ApiVersion("1.0")]
[Route("GetHeartBeat")]
public bool GetHeartBeat()
{
return true;
}
[HttpGet]
[ApiVersion("1.5")]
[Route("GetHeartBeat")]
public bool GetHeartBeat2()
{
return false;
}
This works no problem when I use url versioning:
/api/v1.0/GetHeartBeat
or
/api/v1.5/GetHeartBeat
but /api/v1.3/GetHeartBeat does not since that version doesn't exist..
What I want to happen is if I have a client that is running 1.3, then it will find the closest version that is equal to or less than the latest version. So /api/v1.3/GetHeartBeat would get received, since 1.3 doesn't exist, then it'll look at the closest/earlier version, in this case would be 1.0.
I can write a bunch of route logic to accomplish this, but I feel like there has to be a solution out of the box as I can't be the first person to try this. Is there a nuget package that would accomplish this?
You're really asking two questions. How you map things on the server-side is an implementation detail and there are many options. Attributes are not a hard requirement to apply API Versioning metadata. You can use conventions, including your own conventions. API versions must be discrete. That is by design. An API version is much more like a media type. You cannot arbitrarily add a media type, nor an API version, and necessarily expect a client to understand it.
Since you own both sides, you have some great avenues to make things work the way you want. The server should never assume what the client wants and the client should always have to explicitly ask the server what it wants. The easiest way to achieve your goal is to negotiate the API version. Ok, great. How?
I suspect not a lot of people are doing this today, but API Versioning baked in the necessary mechanics to achieve this very early on. There are many use cases, but the most common are for tooling (ex: client code generation) and client version negotiation. The first step is to enable reporting API versions:
services.AddApiVersioning(options => options.ReportApiVersions = true);
You can also apply [ReportApiVersions] on specific controller actions
This will enable reporting the available API versions via the api-supported-versions and api-deprecated-versions HTTP headers. Remember that deprecated doesn't mean that it doesn't exist, it just means that it will be going away at some point; you control the policy. This information can be used by your client to log warnings about out-of-date versions or it can influence your client's decision in selecting an appropriate version.
Part of your challenge is versioning by URL segment. Yes, it's very popular, but it violates the Uniform Interface constraint. v1.api.com is an endpoint. v1.0/GetHeartBeat and v1.5/GetHeartBeat are identifiers. The two identified resources are almost certainly not different resources, but have different representations. Why does that matter? Changing the identifier (e.g. URL) for every version results in a moving target for the client. Every other method of versioning would use always use GetHeartBeat. I'm sure you're far too down the road to make a change, but this leads into the solution.
It doesn't really matter which controller implementation you use, but you essentially need an action that does something like this:
[ApiController]
[Route("api/[controller]")]
public class GetHeartBeatController : ControllerBase
{
[ReportApiVersions] // ← instead of ApiVersioningOptions.ReportApiVersions = true
[ApiVersionNeutral] // ← allow any and all versions, including none at all
[HttpOptions]
public IActionResult Options()
{
// Allow is required by spec; you may need addition information
Response.Headers.Add("Allow", new StringValues(new[]{"GET", "OPTIONS"}));
Response.GetTypedHeaders().CacheControl = new()
{
MaxAge = TimeSpan.FromDays(1d),
};
return Ok();
}
}
Now, if your client sends:
OPTIONS api/getheartbeat HTTP/2
Host: localhost
You'll get back something like:
HTTP/2 200 OK
Cache-Control: max-age=86400
Api-Supported-Versions: 1.0, 1.5
If your client is running 1.3, it now has the knowledge necessary to select 1.0 from the list as the most appropriate API version. The Cache-Control header can be used as a way for the server to tell the client how long it can cache the result (but it doesn't have to). I presume API versions wouldn't change more often then once per day, so this seems like a reasonable approach.
You didn't mention what type of client you have. If it's a browser-based client, you may have to do some additional work with this setup to make it play nice with CORS, if it's even required. Alternatively, you could achieve the same result by using the HEAD method. I'd argue that OPTIONS is more appropriate, but you might not find it worth the work to make it play with CORS should you run into any complications.
Related
I'm upgrading an ASP.NET Core API project from v5 to v6.
Service config in v5:
services.AddSwaggerGen();
Service config in v6:
builder.Services.AddEndpointsApiExplorer(); // what is this?
builder.Services.AddSwaggerGen();
What is AddEndpointsApiExplorer? Everything works as expected whether I add it or not.
I use the "ASP.NET API Versioning" library. Are they related? If so, must I use both, just the library, or is the library unnecessary now?
AddEndpointsApiExplorer is for Minimal APIs whereas AddApiExplorer requires, at least, MVC Core. For API projects, AddControllers calls AddApiExplorer on your behalf.
But Why Does Everything Still Work With AddEndpointsApiExplorer?
With the introduction of Endpoint Routing, everything in the routing system boils down to an Endpoint. ASP.NET Core uses the Application Model, namely ApplicationModel, ControllerModel, and ActionModel, to create Endpoint instances and register them with the routing system. Minimal APIs, however, use a builder to directly create and register individual Endpoint instances.
The default API Explorer implementation provides a IApiDescriptionProvider that builds ApiDescription instances from the Application Model. Minimal APIs do not have an Application Model so there is nothing to build ApiDescription instances from. The API Explorer provides these descriptions, which are commonly used by tools such as OpenAPI generators. Without any descriptions, there would be no support for Minimal APIs and OpenAPI; that would be bad (or, at least, certainly not accepted by developers). To address that, the ASP.NET Core team created a second IApiDescriptionProvider that only considers Endpoint.
If Everything is an Endpoint, Why Not Merge Implementations?
There's two parts to this answer. First, changing the original IApiDescriptionProvider implementation would introduce a public, breaking change. At a minimum, new constructor arguments would be required. Since it was a major version bump, this approach wasn't off the table, but it turns out to be irrelevant. The bigger issue is that the original IApiDescriptionProvider implementation and AddApiExplorer live in and depend on MVC Core. Minimal APIs only require the routing abstractions. There is no way to merge the two without adding unnecessary coupling. To address this problem, AddEndpointsApiExplorer was added which adds an implementation that only requires an IApiDescriptionProvider implementation based on bare bones Endpoint definitions from the routing system.
If AddEndpointsApiExplorer exists and I call it, do I even need AddApiExplorer anymore? Maybe. The metadata exposed and available on Minimal API Endpoint instances is much lighter than the Application Model; after all, they are minimal. Behind the scenes, a IApiDescriptionGroupCollectionProvider implementation takes a sequence of IApiDescriptionProvider instances. If AddEndpointsApiExplorer and AddApiExplorer are called, then both providers will execute. If only AddEndpointsApiExplorer is called, it will work with regular 'ol controllers, but the descriptions' information fidelity might be less than what you are accustomed to. If you are only authoring Minimal APIs, then AddEndpointsApiExplorer is required if you want API Explorer support.
The fidelity between the two methods is improving even more in .NET 7.0. In some future release, it's possible we might see these approaches coalesce into one.
TLDR; .AddEndpointsApiExplorer() was created to support Minimal Api's.
Searching the doc's via google shows a number of pages that include a call to .AddEndpointsApiExplorer(). But no mention of why you need it, or if it is required when migrating from a v5 project. The documentation is definitely lacking.
Working backwards from the source code & git blame, I found the related project. So the answer appears to be related to support for Minimal Api's.
I believe some new services were created to extract return type information from these new minimal api's, in a way that might apply in a more general way when using Endpoint Routing without MVC.
If you are using MVC, perhaps via .AddControllers(), .AddApiExplorer() would be called for you. Providing the services that swagger depends on for describing controller actions. If that's all you need, then this new api call doesn't seem to be required.
While the documentation for using swagger with minimal api's includes a call to .AddEndpointsApiExplorer(). Even that doesn't explain exactly why it is required.
Why does .AddEndpointsApiExplorer() exist at all? Why were the new features excluded from .AddApiExplorer()? Why was this method rename left out of other documentation for v6?
Perhaps we should create an issue on https://github.com/dotnet/aspnetcore/ or https://github.com/dotnet/AspNetCore.Docs/ to ask for clarification so that others don't have to ask these questions.
TL;DR
Only use AddEndpointsApiExplorer if you use v6's "minimal APIs", which look like this:
app.MapGet("/", () => "Hello World!");
I have an ASP.NET Core 3.1 API and I am introducing a new version for one of my controllers.
I am using the Microsoft.AspNetCore.Mvc.Versioning NuGet package and I have set the new version to be the default version.
All my other controllers should work with both the old version (1.0) and the new version (1.1).
For example:
[ApiVersion("1.0", Deprecated = true)]
public class MySampleController
{
}
[ApiVersion("1.1")]
public class MyNewSampleController
{
}
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")]
public class AllOtherController
{
}
Questions:
Do I really have to add all versions to all controllers?
Is there a better/correct way of handling this?
I have tried to use [ApiVersionNeutral] but that does not seem correct and, according to the documentation, should only be used in special cases.
If I do not add the [ApiVersion] attribute, it defaults to the new 1.1 version and 1.0 no longer works.
Since this is my first SO question, I hope itfalls within the guidelines :)
Q: Do I really have to add all versions to all controllers?
A: Yes. API versions are discrete. A client should get exactly what they ask for and an appropriate error response if the server cannot satisfy the request.
Q: Is there a better/correct way of handling this?
A: There are several possible options. A better or more correct way of handling things is highly subjective. There are numerous factors and the final decision may simply come down to preference.
A common misconception is that API Versioning uses attributes. It really doesn't care about attributes, it's just one possibility and one that tends to resonate with developers as metadata. You have the choice to use out-of-the-box attributes, custom attributes, out-of-the-box conventions, or your own custom conventions.
The choice of how to apply the version metadata is typically of preference and practical management. A common scenario is to organize controllers in folders by API version. In several .NET languages, the folder name translates to part or all of the corresponding namespace. This arrangement is common enough that there is an out-of-the-box VersionByNamespaceConvention for it. For details, see the documentation. The By Namespace Sample also demonstrates how to up such a project end-to-end.
API version-neutral means that an API takes any and all versions, including none at all. It can mean you don't care about the API version or that you accept a whole range that you'll deal with yourself. It is really only meant to be used with APIs that never change over time. For example, a HTTP DELETE operation typically does not change over time or across versions.
It's not 100% clear what you mean by:
"If I do not add the [ApiVersion] attribute, it defaults to the new 1.1 version and 1.0 no longer works."
There are no defaults per se. This statement seems to imply that you have set the AssumeDefaultVersionWhenUnspecified option to true. You should not do that unless you have a very good reason to do so. That is probably one of the most abused features of API Versioning. A client must know the version it is asking for. Allowing a client to not specify a version and having things transition from 1.0 to 1.1 can break the client. The server can make no assumption that it won't. This feature was meant to grandfather in existing services that didn't previously have an explicit version defined. That scenario only exists when API Versioning is first enabled. As stated above, all controllers must have one or more discrete API versions, but the original set of APIs didn't have it explicitly defined. If this feature didn't exist, then the baseline set of clients that didn't know about an API version would break.
I'm currently in charge of developing a rather complex REST api with .Net and Web Api.
The interface should later be consumed by various public and private (in house) Clients.
I read a lot about how you should version your api, but opinions seem to be very different.
The application runs behind a load balancer in a multi server environment.
Now my problem is, the load balancer is hard restricted and only supports layer 4 load balancing, so I'm not able to inspect the URL / headers etc. of the incoming requests to route them to the correct version of the application.
We don't want to have versioned api controllers in our code base since we have a lot external dependecies which should be updated frequently, so we could break some functionality.
Currently it seems to be the only solution to use subdomains for versioning, e.g.
ver1.api.domain.com
Is there anything against this approach, do you have other solutions?
The issue with that versioning approach is that there will be duplicate entries for all resources including un-modified ones.
In my opinion, a better approach is to include the version of a resource in the Uri.
Let's have a look at a concrete example. Supposing there is a CarsController like the one below:
public class CarsController : ApiController
{
[Route("cars/{id}")]
public async Task<IHttpActionResult> Get(int id)
{
DoSomething();
return Ok(result);
}
}
After the first release, we introduce the second version of the Get method, we can do something like
[Route("cars/{id}/v2")]
public async Task<IHttpActionResult> GetCarsVersion2(int id)
{
DoSomethingElse();
return Ok(result);
}
So the existing clients still refer to the old Uri /Cars/{id}, whereas new clients can use the new Uri /Cars/{id}/v2.
In addition, if there aren't many differences between the two versions, the original implementation can be refactored to satisfy new requirements. Which, in turns, reduces code duplication.
I'm looking at developing an application in ASP.NET MVC 3 and would like to provide a public API at the same time.
From looking around, there seems to be 2 ways to go about it. Either create an API area and have controllers that return json / xml. Or use action filters and a single front end set of controllers, and have those return json / xml / html depending on the request headers.
I'd like to do the later, but I was wondering how you could go about versioning your api if you went this route?
If you go the first route, you could easily just create a v1 / v2 controller, but if you do the later, how could you version it?
Versioning is a rather complex issue to begin with. Here are ways I looked at before:
URL. In this case https://api.example.com/v1/projects is assumed to be a different resource than http://api.example.com/v2/projects, even though its not the case. Basecamp seems to do this. Following this approach, assume you'll always have to support old APIs.
Headers. The URLs remains the same, however, the client pass an additional HTTP header, say X-MYAPI-VERSION with each request with a value identifying the version of the API to use. The Google Documents List API do this. A potential problem with this approach is that HTTP headers may be stripped by intermediaries between the client and server.
Parameters. To circumvent the problem with option 2, you can pass the API version to use as a parameter (such as https://api.example.com/projects?v=3).
Media types. Here your URLs stay the same, however, users are required to specify the representation of the resources using the accept and content type headers. For example, a "project" can be presented using "application/vnd.mycompany.resource[-version][+format]" giving your representations of "application/vnd.mycompany.project-v1+json" for v1 json or "application/vnd.mycompany.project-v1+xml" for v1 xml. When you need a new version of a project comes along the mime type may look as follows "application/vnd.mycompany.project-v2+xml". Github seems to support that.
Part of payload. In this case the payload of the request contains the version number to use. For example, when XML is passed, you can look at the namespace to determine which version of the API is being used. For JSON, you can use a "$version" or "_version" property to specify the version.
Client Key. When the application is registered, it specifies which version of the API it wants to use. When you authenticate the client, you ensure that you emulate the version it wants to use.
No explicit versioning There is always the option not to version your API and try to handle changes transparently by making all fields optional and handle them appropriately when they are missing. Chances are you will be doing this anyways to make future versions of your API compatible with the version you are developing today.
Many recommend option 4, although its not always practical. Most of these options require additional work to get working with ASP.NET MVC.
You can go one of two routes - you can include the API in the route (instead of http://app.lication/category/1 you would have something like http://app.lication/api/v1/category/1) or you could include a custom HTTP header.
Either will allow you to discriminate which version's being called.
We have a server written in C# (Framework 3.5 SP1). Customers write client applications using our server API. Recently, we created several levels of license schemes like Basic, Intermediate and All. If you have Basic license then you can call few methods on our API. Similarly if you have Intermediate you get some extra methods to call and if you have All then you can call all the methods.
When server starts it gets the license type. Now in each method I have to check the type of license and decide whether to proceed further with the function or return.
For example, a method InterMediateMethod() can only be used by Intermediate License and All license. So I have to something like this.
public void InterMediateMethod()
{
if(licenseType == "Basic")
{
throw new Exception("Access denied");
}
}
It looks like to me that it is very lame approach. Is there any better way to do this? Is there any declarative way to do this by defining some custom attributes? I looked at creating a custom CodeAccessSecurityAttribute but did not get a good success.
Since you are adding the "if" logic in every method (and god knows what else), you might find it easier to use PostSharp (AOP framework) to achieve the same, but personally, I don't like either of the approaches...
I think it would be much cleaner if you'd maintained three different branches (source code) for each license, which may add a little bit of overhead in terms of maintenance (maybe not), but at least keep it clean and simple.
I'm also interested what others have to say about it.
Good post, I like it...
Possibly one easy and clean approach would be to add a proxy API that duplicates all your API methods and exposes them to the client. When called, the proxy would either forward the call to your real method, or return a "not licensed" error. The proxies could be built into three separate (basic, intermediate, all) classes, and your server would create instances of the approprate proxy for your client's licence. This has the advantage of having minimal performance overhead (because you only check the licence once). You may not even need to use a proxy for the "all" level, so it'll get maximum performance. It may be hard to slip this in depending on your existing design though.
Another possibility may be to redesign and break up your APIs into basic/intermediate/all "sections", and put them in separate assemblies, so the entire assembly can be enabled/disabled by the licence, and attempting to call an unlicensed method can just return a "method not found" error (e.g. a TypeLoadException will occur automatically if you simply haven't loaded the needed assembly). This will make it much easier to test and maintain, and again avoids checking at the per-method level.
If you can't do this, at least try to use a more centralised system than an "if" statement hand-written into every method.
Examples (which may or may not be compatible with your existing design) would include:
Add a custom attribute to each method and have the server dispatch code check this attribute using reflection before it passes the call into the method.
Add a custom attribute to mark the method, and use PostSharp to inject a standard bit of code into the method that will read and test the attribute against the licence.
Use PostSharp to add code to test the licence, but put the licence details for each method in a more data driven system (e.g. use an XML file rather than attributes to describe the method permissions). This will allow you to easily change the licensing across the entire server by editing a single file, and allow you to easily add whole new levels or types of licences in future.
Hope that gives you some ideas.
You might really want to consider buying a licensing solution rather than rolling your own. We use Desaware and are pretty happy with it.
Doing licensing at the method level is going to take you into a world of hurt. Maintenance on that would be a nightmare, and it won't scale at all.
You should really look at componentizing your product. Your code should roughly fall into "features", which can be bundled into "components". The trick is to make each component do a license check, and have a licensing solution that knows if a license includes a component.
Components for our products are generally on the assembly level, but for our web products they can get down to the ASP.Net server control level.
I wonder how the people are licensing the SOA services. They can be licensed per service or per end point.
That can be very hard to maintain.
You can try with using strategy pattern.
This can be your starting point.
I agree with the answer from #Ostati that you should keep 3 branches of your code.
What I would further expand on that is then I would expose 3 different services (preferably WCF services) and issue certificates that grant access to the specific service. That way if anyone tried to access the higher level functionality they would just not be able to access the service period.