Skip to content

Commit 20e862f

Browse files
authored
Merge pull request #6351 from kjac/v14/feature/versioning-management-apis
Add documentation for versioning Management APIs
2 parents 704ec5d + 52da043 commit 20e862f

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

14/umbraco-cms/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@
468468
* [Creating a backoffice API](tutorials/creating-a-backoffice-api/README.md)
469469
* [Documenting your controllers](tutorials/creating-a-backoffice-api/documenting-your-controllers.md)
470470
* [Adding a custom Swagger document](tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document.md)
471+
* [Versioning your API](tutorials/creating-a-backoffice-api/versioning-your-api.md)
471472
* [Polymorphic output in the Management API](tutorials/creating-a-backoffice-api/polymorphic-output-in-the-management-api.md)
472473
* [Umbraco schema and operation IDs](tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids.md)
473474
* [Access policies](tutorials/creating-a-backoffice-api/access-policies.md)
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
---
2+
description: Adding new versions of custom Management APIs
3+
---
4+
5+
# Versioning your API
6+
7+
All APIs register as version 1.0 by default, which means their endpoints are routed under `/umbraco/management/api/v1/`.
8+
9+
As projects evolve, adding new versions of your APIs sometimes becomes necessary. Multiple versions of the same API can co-exist to retain backward compatibility.
10+
11+
APIs are versioned using attribute annotation:
12+
13+
- `[ApiVersion]` attributes on the API controllers.
14+
- `[MapToApiVersion]` attributes on the API controller actions.
15+
16+
{% hint style="info" %}
17+
It is recommended to annotate _all_ API controller actions, as well as the version 1.0 actions.
18+
{% endhint %}
19+
20+
Using the API controller from the [Creating your own API article](./create-your-own-api.md) as an example, we can add version 2.0 implementations of select actions:
21+
22+
{% code title="MyItemApiController.cs" %}
23+
24+
```csharp
25+
[ApiVersion("1.0")]
26+
[ApiVersion("2.0")]
27+
public class MyItemApiController : ManagementApiControllerBase
28+
{
29+
[HttpGet]
30+
[MapToApiVersion("1.0")]
31+
public IActionResult GetAllItems(int skip = 0, int take = 10)
32+
{
33+
// ...
34+
}
35+
36+
[HttpGet("{id:guid}")]
37+
[MapToApiVersion("1.0")]
38+
public IActionResult GetItem(Guid id)
39+
{
40+
// ...
41+
}
42+
43+
[HttpPost]
44+
[MapToApiVersion("1.0")]
45+
public IActionResult CreateItem(string value)
46+
{
47+
// ...
48+
}
49+
50+
[HttpPut("{id:guid}")]
51+
[MapToApiVersion("1.0")]
52+
public IActionResult UpdateItem(Guid id, string value)
53+
{
54+
// ...
55+
}
56+
57+
[HttpDelete("{id:guid}")]
58+
[MapToApiVersion("1.0")]
59+
public IActionResult DeleteItem(Guid id)
60+
{
61+
// ...
62+
}
63+
64+
[HttpGet("{id:guid}")]
65+
[MapToApiVersion("2.0")]
66+
public IActionResult GetItemV2(Guid id)
67+
{
68+
MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);
69+
70+
return item is not null
71+
? Ok(item)
72+
: OperationStatusResult(
73+
MyItemOperationStatus.NotFound,
74+
builder => NotFound(
75+
builder
76+
.WithTitle("The item was not found")
77+
.WithDetail("The item with the given ID did not exist.")
78+
.Build()
79+
)
80+
);
81+
}
82+
83+
[HttpPost]
84+
[MapToApiVersion("2.0")]
85+
public IActionResult CreateItemV2(string value)
86+
{
87+
var newItem = new MyItem(value);
88+
AllItems.Add(newItem);
89+
return CreatedAtId<MyItemApiController>(
90+
ctrl => nameof(ctrl.GetItemV2),
91+
newItem.Id
92+
);
93+
}
94+
}
95+
```
96+
97+
{% endcode %}
98+
99+
Version 2.0 of the "get" and "create" endpoints - `GetItemV2` and `CreateItemV2` respectively, are added with the code above. The rest of the endpoints remain version 1.0 only.
100+
101+
{% hint style="info" %}
102+
The version 2.0 endpoints are routed under `/umbraco/management/api/v2/`.
103+
{% endhint %}
104+
105+
In the example above, the version 2.0 actions are added to the same API controller as their version 1.0 counterparts. If you prefer, they can be added to a new API controller instead. This will leave you with separate API controllers, one for each version of the API. See the examples below:
106+
107+
{% code title="MyItemApiController.cs" %}
108+
109+
```csharp
110+
[ApiVersion("1.0")]
111+
public class MyItemApiController : ManagementApiControllerBase
112+
{
113+
[HttpGet]
114+
[MapToApiVersion("1.0")]
115+
public IActionResult GetAllItems(int skip = 0, int take = 10)
116+
{
117+
// ...
118+
}
119+
120+
[HttpGet("{id:guid}")]
121+
[MapToApiVersion("1.0")]
122+
public IActionResult GetItem(Guid id)
123+
{
124+
// ...
125+
}
126+
127+
[HttpPost]
128+
[MapToApiVersion("1.0")]
129+
public IActionResult CreateItem(string value)
130+
{
131+
// ...
132+
}
133+
134+
[HttpPut("{id:guid}")]
135+
[MapToApiVersion("1.0")]
136+
public IActionResult UpdateItem(Guid id, string value)
137+
{
138+
// ...
139+
}
140+
141+
[HttpDelete("{id:guid}")]
142+
[MapToApiVersion("1.0")]
143+
public IActionResult DeleteItem(Guid id)
144+
{
145+
// ...
146+
}
147+
}
148+
```
149+
150+
{% endcode %}
151+
152+
With the version 1.0 actions added in a controller sampled above, the version 2.0 actions are added in a new controller, as shown below.
153+
154+
{% code title="MyItemApiVersionTwoController.cs" %}
155+
```csharp
156+
[ApiVersion("2.0")]
157+
public class MyItemApiVersionTwoController : ManagementApiControllerBase
158+
{
159+
private static readonly List<MyItem> AllItems = Enumerable.Range(1, 100)
160+
.Select(i => new MyItem($"My V2 Item #{i}"))
161+
.ToList();
162+
163+
[HttpGet("{id:guid}")]
164+
[MapToApiVersion("2.0")]
165+
public IActionResult GetItem(Guid id)
166+
{
167+
MyItem? item = AllItems.FirstOrDefault(item => item.Id == id);
168+
169+
return item is not null
170+
? Ok(item)
171+
: OperationStatusResult(
172+
MyItemOperationStatus.NotFound,
173+
builder => NotFound(
174+
builder
175+
.WithTitle("The item was not found")
176+
.WithDetail("The item with the given ID did not exist.")
177+
.Build()
178+
)
179+
);
180+
}
181+
182+
[HttpPost]
183+
[MapToApiVersion("2.0")]
184+
public IActionResult CreateItem(string value)
185+
{
186+
var newItem = new MyItem(value);
187+
AllItems.Add(newItem);
188+
return CreatedAtId<MyItemApiVersionTwoController>(
189+
ctrl => nameof(ctrl.GetItem),
190+
newItem.Id
191+
);
192+
}
193+
}
194+
```
195+
{% endcode %}
196+
197+
{% hint style="warning" %}
198+
While perhaps tempting, do _not_ name your API controller `V2` - e.g. `MyItemApiVersionV2`. Due to an upstream issue in the API versioning system, this will currently cause routing issues in certain scenarios.
199+
{% endhint %}

0 commit comments

Comments
 (0)