Versioning REST API of an ASP.NET MVC application - c#

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.

Related

How to write delete REST API that accepts a long list of items to delete?

I'm writing RESTful APIs and am getting used to the recommended protocols for using HTTP verbs for different operations.
However, I'm not sure how those protocols handle the case where you are deleting a potentially long list of items.
It appears that, like GET, the DELETE verb has no body and so is limited to the length of a URL. So how could you support accepting an arbitrarily long list of items to be deleted?
From the top....
HTTP is our standard for self-descriptive messages, which is subject to the uniform interface constraint. That in turn means that everyone on the web understands HTTP requests the same way.
In other words
DELETE /api/users/5b45eda8-067c-42c1-ae1b-e0f82ad736d6
has the same meaning as
DELETE /www/home.html
In both cases, we're asking the server to enact a change to its resource model.
Because everyone understands these requests the same way, we can create general purpose components (ex: caches) that understand the meaning of messages in the transfer of documents over a network domain and can therefore do intelligent things (like invalidating previously cached responses).
And we can do this even though the general purpose components know nothing about the semantics of the resource, and nothing about the underlying domain model hidden behind the resource.
DELETE, in HTTP, always specifies a single target URI; "bulk delete" is not an option here.
(I haven't found any registered HTTP methods that describe a bulk delete to general purpose components. It's possible that one of the WebDAV methods could express those semantics, but the WebDAV standard also has a lot of other baggage - I wouldn't try repurposing those methods for a "normal" API.)
So if you are trying to DELETE three resources in your API, then you are going to need three requests to do it -- just like you would if you were trying to DELETE three pages on your web site.
That said, if deleting a bunch of resources on your web site using a single HTTP request is more important than letting general purpose components understand what is going on: you have the option of using POST
POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.” -- Fielding, 2009
General purpose components will understand that the resource identified by the target URI is changing in some way, but it won't understand what is happening in the payload.
In theory, you could standardize a payload that means "we're deleting all of these resources", and then general purpose components could be implemented to recognize that standard. In practice, good luck.
Now, if instead what you want is a bulk delete of entities in your domain model, you have some options available.
On the web, we would normally use something like a form - perhaps with a check box for each entity. You select the entities that you want to delete, submit the form, and the HTTP request handler parses the message, then forwards the information to your domain model.
You could achieve something similar with a remote authoring idiom - here's a resource whose representation is a list of entities. You PUT to the server a copy of that document with entities removed, and then on the server you make changes to the domain model to match.
It's a very declarative approach: "change the domain model so that the representation of the resource will look like this".
This is analogous to how you would use HTTP to fix a spelling error in a web page: send a PUT request with the new HTML (including the spelling correction) in the request body.
PATCH is very much the same idea: we describe changes to the representation of the resource, and the server passes that information to the domain model. The difference here being that instead of sending the entire representation, we just send a patch document that describes the correction.
If you want an imperative approach - just use POST
POST /Bob
Content-Type: text/plain
Bob,
Please delete domain entities 1, 2, 5, 7
General purpose components won't understand how you are trying to modify the target resource, but they'll at least know that much.
Where things get messy is when there are lots of resources whose representation depends on the same resources. The standards don't offer us much in the way of affordances to announce "here are all the resources that have changed".
Cache invalidation is one of the two hard problems. HTTP has some affordances that work in the simple cases, but trade offs become necessary when things get more complicated.

ASP.NET API version ranges

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.

ASP.NET Core API Versioning - Same controller for all versions

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.

ServiceStack confusion between metadata, OpenAPI, and Swagger

I'm working on documentation for an API server implemented using ServiceStack. I have a few questions. If it makes more sense, I can move these to separate posts.
IgnoreDataMember is mentioned in multiple places as the way to hide DTO properties from metadata, but that's not an option if you publish the message to queue since that works via serialization and it skips those fields...
The Exclude attribute [Exclude(Feature.Metadata)] is supposed to hide DTO's from the meatadata page. While it works at top-level classes, it doesn't filter out properties of a base class. In other words if MyBaseDto is marked to exlude, but MyEndpointDto inherits from MyBaseDto, properties from both classes are shown. Is this a bug?
Removing services from metadata works really well, but I don't see how to remove them from Swagger/OpenAPI. I can implement the OperationFilter callback, but there's no obvious way there to actually remove anything. If I use the ResourceFilterPattern regex property, it works by DTO, not route. I'd love to remove any route that starts with /internal for instance.
The LogoUrl property of the OpenApiFeature and SwaggerFeature doesn't seem to actually affect the page anywhere. Is there another step to take?
On the Swagger UI page, why does every route include the text " XX is not defined!" I'm not sure what's it's supposed to be pull from.
I need help understanding how OpenAPI and Swagger fit together. Why are they two separate features? If I just add the OpenAPI feature, I can still bring up the Swagger UI. What does the Swagger feature bring to the table?
The difference between ServiceStack's Swagger Feature and Open API is that Swagger Feature implements the older Swagger 1.2 spec where as Open API implements the newer Swagger 2.0 spec which has been renamed from Swagger. If you're unsure which to use, use the newer Open API.
Swagger UI is just the UI and ServiceStack bundles a version of the Swagger UI which works with both the older Swagger 1.2 spec as well as the newer Open API 2.0 spec.
[Exclude(Feature.Metadata)] or [ExcludeMetadata] works as intended, annotate it on DTOs you wish to exclude from ServiceStack's metadata services which also removes them being included in the returned Swagger/Open API spec + UI.
You can use the ApiDeclarationFilter to modify the entire OpenApiDeclaration before it's returned, you can remove routes by removing them from the Paths Dictionary and Types definitions from being included by removing them from Definitions Dictionary. You can submit other feature requests for missing features you want implemented.
The LogoUrl has been fixed for Open API and we've also added LogoHref so you can change the url that the logo goes to which by default refreshes the Swagger UI page. This change is available from v4.5.13 that's now available on MyGet.
Open API is the spec Swagger UI is the UI
The purpose of both Open API and Swagger plugins is to implement the Swagger specification which is a generic and machine readable json format to describe Service APIs. Only one of the benefits is to provide a dynamic UI which ServiceStack provides by bundling the Swagger UI that works with their respective API versions. Other purposes of implementing the Open API spec include enabling automated tooling like generating a Azure AutoRest client or importing your Service into Azure's Management API.

Build an API for Multiple

I'm building an API using WebAPI that will be accessed via AJAX calls. However, the API controller will need more than just one POST method. I understand I can specify {action} in my routing, but because I've seen that this is not recommended - am I using the right tool? So 2 questions:
Is Web API the best tool for this, or is there something else I should be using?
Why should I not use more than one POST method in a WebApiController? Is including {action} in my routing a good enough solution to this problem?
1. Is Web API the best tool for this, or is there something else I should be using?
I think WebAPI is a fine choice for you, regardless of whether you have one or many POST calls per controller.
2. Why should I not use more than one POST method in a WebApiController?
To remain RESTFul you'll want a controller per entity type. Without getting too deep into details, a POST against a specific type of entity should be the 'ADD entity' call, and why would you have more than one of those? Having said that, you don't have to be fully RESTFul... if your requirements suite a multi-POST model then go for it, you can always refactor later if necessary.
...Is including {action} in my routing a good enough solution to this problem?
Again, if your goal is to be RESTFul then this isn't a great practice. However, if you have needs that are best achieved using action routings then go for it. REST is not the only model.

Categories

Resources