Bulk/batch
since v4.1
The Atomic Operations JSON:API extension defines how to perform multiple write operations in a linear and atomic manner.
Clients can send an array of operations in a single request. JsonApiDotNetCore guarantees that those operations will be processed in order and will either completely succeed or fail together.
On failure, the zero-based index of the failing operation is returned in the error.source.pointer
field of the error response.
Usage
To enable operations, add a controller to your project that inherits from JsonApiOperationsController
or BaseJsonApiOperationsController
:
public sealed class OperationsController : JsonApiOperationsController
{
public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph,
ILoggerFactory loggerFactory, IOperationsProcessor processor,
IJsonApiRequest request, ITargetedFields targetedFields,
IAtomicOperationFilter operationFilter)
: base(options, resourceGraph, loggerFactory, processor, request, targetedFields,
operationFilter)
{
}
}
Important
Since v5.6.0, the set of exposed operations is based on
GenerateControllerEndpoints
usage.
Earlier versions always exposed all operations for all resource types.
If you're using explicit controllers,
register and implement your own
IAtomicOperationFilter
to indicate which operations to expose.
You'll need to send the next Content-Type in a POST request for operations:
application/vnd.api+json; ext="https://jsonapi.org/ext/atomic"
Local IDs
Local IDs (lid) can be used to associate resources that have not yet been assigned an ID. The next example creates two resources and sets a relationship between them:
POST http://localhost/api/operations HTTP/1.1
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
{
"atomic:operations": [
{
"op": "add",
"data": {
"type": "musicTracks",
"lid": "id-for-i-will-survive",
"attributes": {
"title": "I will survive"
}
}
},
{
"op": "add",
"data": {
"type": "performers",
"lid": "id-for-gloria-gaynor",
"attributes": {
"artistName": "Gloria Gaynor"
}
}
},
{
"op": "update",
"ref": {
"type": "musicTracks",
"lid": "id-for-i-will-survive",
"relationship": "performers"
},
"data": [
{
"type": "performers",
"lid": "id-for-gloria-gaynor"
}
]
}
]
}
For example requests, see our suite of tests in JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.
Configuration
The maximum number of operations per request defaults to 10, which you can change at startup:
// Program.cs
builder.Services.AddJsonApi(options => options.MaximumOperationsPerRequest = 250);
Or, if you want to allow unconstrained, set it to null
instead.
Multiple controllers
You can register multiple operations controllers using custom routes, for example:
[DisableRoutingConvention, Route("/operations/musicTracks/create")]
public sealed class CreateMusicTrackOperationsController : JsonApiOperationsController
{
public override async Task<IActionResult> PostOperationsAsync(
IList<OperationContainer> operations, CancellationToken cancellationToken)
{
AssertOnlyCreatingMusicTracks(operations);
return await base.PostOperationsAsync(operations, cancellationToken);
}
}
Limitations
For our atomic:operations implementation, the next limitations apply:
- The
ref.href
field cannot be used. Use type/id or type/lid instead. - You cannot both assign and reference the same local ID in a single operation.
- All repositories used in an operations request must implement
IRepositorySupportsTransaction
and participate in the same transaction. - If you're not using Entity Framework Core, you'll need to implement and register
IOperationsTransactionFactory
yourself.