From 60260fb2ed07680e3fca4f26c15a15f60982699b Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 17 Nov 2020 11:49:58 +0100 Subject: [PATCH 1/2] Removed unused methods --- .../Repositories/PlaceholderEntityCollector.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/JsonApiDotNetCore/Repositories/PlaceholderEntityCollector.cs b/src/JsonApiDotNetCore/Repositories/PlaceholderEntityCollector.cs index e2bb41a6cd..dfdcf02d91 100644 --- a/src/JsonApiDotNetCore/Repositories/PlaceholderEntityCollector.cs +++ b/src/JsonApiDotNetCore/Repositories/PlaceholderEntityCollector.cs @@ -22,16 +22,6 @@ public PlaceholderResourceCollector(IResourceFactory resourceFactory, DbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } - /// - /// Creates a new placeholder resource (without adding it to the change tracker) and registers it for detachment. - /// - public IIdentifiable CreateUntracked(Type resourceType) - { - var placeholderResource = _resourceFactory.CreateInstance(resourceType); - _resources.Add(placeholderResource); - return placeholderResource; - } - /// /// Creates a new placeholder resource, assigns the specified ID, adds it to the change tracker /// in state and registers it for detachment. @@ -61,14 +51,6 @@ public TResource CaptureExisting(TResource placeholderResource) return resourceTracked; } - /// - /// Registers the specified resources for detachment. - /// - public void Register(IEnumerable resources) - { - _resources.AddRange(resources); - } - /// /// Detaches the collected placeholder resources from the change tracker. /// From 2175155bf8d15ea295f0bf829b9a356b50df93c2 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 17 Nov 2020 11:52:52 +0100 Subject: [PATCH 2/2] Added request cancellation support --- .../Controllers/TodoCollectionsController.cs | 8 +- .../Controllers/TodoItemsCustomController.cs | 33 ++++--- .../Controllers/TodoItemsTestController.cs | 47 ++++++--- .../Services/CustomArticleService.cs | 5 +- .../Services/WorkItemService.cs | 31 +++--- .../Controllers/ReportsController.cs | 6 +- .../ReportsExample/Services/ReportService.cs | 3 +- .../Controllers/BaseJsonApiController.cs | 44 +++++---- .../Controllers/JsonApiCommandController.cs | 36 ++++--- .../Controllers/JsonApiController.cs | 56 +++++++---- .../Controllers/JsonApiQueryController.cs | 23 +++-- .../Internal/Execution/HookExecutorHelper.cs | 3 +- .../Internal/Execution/ResourcePipeline.cs | 3 +- .../Hooks/Internal/IReadHookExecutor.cs | 3 +- .../Middleware/ExceptionHandler.cs | 21 +++- .../EntityFrameworkCoreRepository.cs | 57 +++++------ .../Repositories/IResourceReadRepository.cs | 5 +- .../IResourceRepositoryAccessor.cs | 21 ++-- .../Repositories/IResourceWriteRepository.cs | 15 +-- .../ResourceRepositoryAccessor.cs | 41 ++++---- .../Services/AsyncCollectionExtensions.cs | 9 +- .../Services/IAddToRelationshipService.cs | 4 +- .../Services/ICreateService.cs | 3 +- .../Services/IDeleteService.cs | 3 +- .../Services/IGetAllService.cs | 3 +- .../Services/IGetByIdService.cs | 3 +- .../Services/IGetRelationshipService.cs | 3 +- .../Services/IGetSecondaryService.cs | 3 +- .../IRemoveFromRelationshipService.cs | 4 +- .../Services/ISetRelationshipService.cs | 4 +- .../Services/IUpdateService.cs | 3 +- .../Services/JsonApiResourceService.cs | 98 ++++++++++--------- .../ObfuscatedIdentifiableController.cs | 41 ++++---- .../ResultCapturingRepository.cs | 5 +- .../BaseJsonApiController_Tests.cs | 49 +++++----- .../EntityFrameworkCoreRepositoryTests.cs | 5 +- .../IServiceCollectionExtensionsTests.cs | 77 ++++++++------- 37 files changed, 448 insertions(+), 330 deletions(-) diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index a6c130940b..1745269b38 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; @@ -27,15 +28,16 @@ public TodoCollectionsController( } [HttpPatch("{id}")] - public override async Task PatchAsync(Guid id, [FromBody] TodoItemCollection resource) + public override async Task PatchAsync(Guid id, [FromBody] TodoItemCollection resource, CancellationToken cancellationToken) { if (resource.Name == "PRE-ATTACH-TEST") { var targetTodoId = resource.TodoItems.First().Id; var todoItemContext = _dbResolver.GetContext().Set(); - await todoItemContext.Where(ti => ti.Id == targetTodoId).FirstOrDefaultAsync(); + await todoItemContext.Where(ti => ti.Id == targetTodoId).FirstOrDefaultAsync(cancellationToken); } - return await base.PatchAsync(id, resource); + + return await base.PatchAsync(id, resource, cancellationToken); } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index 16fd697688..721f126648 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers.Annotations; @@ -58,18 +59,18 @@ public CustomJsonApiController( } [HttpGet] - public async Task GetAsync() + public async Task GetAsync(CancellationToken cancellationToken) { - var resources = await _resourceService.GetAsync(); + var resources = await _resourceService.GetAsync(cancellationToken); return Ok(resources); } [HttpGet("{id}")] - public async Task GetAsync(TId id) + public async Task GetAsync(TId id, CancellationToken cancellationToken) { try { - var resource = await _resourceService.GetAsync(id); + var resource = await _resourceService.GetAsync(id, cancellationToken); return Ok(resource); } catch (ResourceNotFoundException) @@ -79,11 +80,11 @@ public async Task GetAsync(TId id) } [HttpGet("{id}/relationships/{relationshipName}")] - public async Task GetRelationshipsAsync(TId id, string relationshipName) + public async Task GetRelationshipsAsync(TId id, string relationshipName, CancellationToken cancellationToken) { try { - var relationship = await _resourceService.GetRelationshipAsync(id, relationshipName); + var relationship = await _resourceService.GetRelationshipAsync(id, relationshipName, cancellationToken); return Ok(relationship); } catch (ResourceNotFoundException) @@ -93,14 +94,14 @@ public async Task GetRelationshipsAsync(TId id, string relationsh } [HttpGet("{id}/{relationshipName}")] - public async Task GetRelationshipAsync(TId id, string relationshipName) + public async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) { - var relationship = await _resourceService.GetSecondaryAsync(id, relationshipName); + var relationship = await _resourceService.GetSecondaryAsync(id, relationshipName, cancellationToken); return Ok(relationship); } [HttpPost] - public async Task PostAsync([FromBody] T resource) + public async Task PostAsync([FromBody] T resource, CancellationToken cancellationToken) { if (resource == null) return UnprocessableEntity(); @@ -108,20 +109,20 @@ public async Task PostAsync([FromBody] T resource) if (_options.AllowClientGeneratedIds && !string.IsNullOrEmpty(resource.StringId)) return Forbidden(); - resource = await _resourceService.CreateAsync(resource); + resource = await _resourceService.CreateAsync(resource, cancellationToken); return Created($"{HttpContext.Request.Path}/{resource.Id}", resource); } [HttpPatch("{id}")] - public async Task PatchAsync(TId id, [FromBody] T resource) + public async Task PatchAsync(TId id, [FromBody] T resource, CancellationToken cancellationToken) { if (resource == null) return UnprocessableEntity(); try { - var updated = await _resourceService.UpdateAsync(id, resource); + var updated = await _resourceService.UpdateAsync(id, resource, cancellationToken); return Ok(updated); } catch (ResourceNotFoundException) @@ -131,17 +132,17 @@ public async Task PatchAsync(TId id, [FromBody] T resource) } [HttpPatch("{id}/relationships/{relationshipName}")] - public async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds) + public async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) { - await _resourceService.SetRelationshipAsync(id, relationshipName, secondaryResourceIds); + await _resourceService.SetRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); return Ok(); } [HttpDelete("{id}")] - public async Task DeleteAsync(TId id) + public async Task DeleteAsync(TId id, CancellationToken cancellationToken) { - await _resourceService.DeleteAsync(id); + await _resourceService.DeleteAsync(id, cancellationToken); return NoContent(); } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index c6d945372e..b4d0f9fe2c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Net; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; @@ -36,21 +37,31 @@ public TodoItemsTestController( { } [HttpGet] - public override async Task GetAsync() => await base.GetAsync(); + public override async Task GetAsync(CancellationToken cancellationToken) + { + return await base.GetAsync(cancellationToken); + } [HttpGet("{id}")] - public override async Task GetAsync(int id) => await base.GetAsync(id); + public override async Task GetAsync(int id, CancellationToken cancellationToken) + { + return await base.GetAsync(id, cancellationToken); + } [HttpGet("{id}/{relationshipName}")] - public override async Task GetSecondaryAsync(int id, string relationshipName) - => await base.GetSecondaryAsync(id, relationshipName); + public override async Task GetSecondaryAsync(int id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetSecondaryAsync(id, relationshipName, cancellationToken); + } [HttpGet("{id}/relationships/{relationshipName}")] - public override async Task GetRelationshipAsync(int id, string relationshipName) - => await base.GetRelationshipAsync(id, relationshipName); + public override async Task GetRelationshipAsync(int id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetRelationshipAsync(id, relationshipName, cancellationToken); + } [HttpPost] - public override async Task PostAsync([FromBody] TodoItem resource) + public override async Task PostAsync([FromBody] TodoItem resource, CancellationToken cancellationToken) { await Task.Yield(); @@ -62,11 +73,13 @@ public override async Task PostAsync([FromBody] TodoItem resource [HttpPost("{id}/relationships/{relationshipName}")] public override async Task PostRelationshipAsync( - int id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds); + int id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } [HttpPatch("{id}")] - public override async Task PatchAsync(int id, [FromBody] TodoItem resource) + public override async Task PatchAsync(int id, [FromBody] TodoItem resource, CancellationToken cancellationToken) { await Task.Yield(); @@ -75,11 +88,13 @@ public override async Task PatchAsync(int id, [FromBody] TodoItem [HttpPatch("{id}/relationships/{relationshipName}")] public override async Task PatchRelationshipAsync( - int id, string relationshipName, [FromBody] object secondaryResourceIds) - => await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds); + int id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } [HttpDelete("{id}")] - public override async Task DeleteAsync(int id) + public override async Task DeleteAsync(int id, CancellationToken cancellationToken) { await Task.Yield(); @@ -87,7 +102,9 @@ public override async Task DeleteAsync(int id) } [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(int id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds); + public override async Task DeleteRelationshipAsync(int id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 2e42b0a413..0680e22a4a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Hooks.Internal; @@ -27,9 +28,9 @@ public CustomArticleService( resourceChangeTracker, resourceFactory, hookExecutor) { } - public override async Task
GetAsync(int id) + public override async Task
GetAsync(int id, CancellationToken cancellationToken) { - var resource = await base.GetAsync(id); + var resource = await base.GetAsync(id, cancellationToken); resource.Caption = "None for you Glen Coco"; return resource; } diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index a5c34e0966..c787096182 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Dapper; using JsonApiDotNetCore.Resources; @@ -22,31 +23,31 @@ public WorkItemService(IConfiguration configuration) _connectionString = configuration["Data:DefaultConnection"].Replace("###", postgresPassword); } - public async Task> GetAsync() + public async Task> GetAsync(CancellationToken cancellationToken) { return (await QueryAsync(async connection => - await connection.QueryAsync(@"select * from ""WorkItems"""))).ToList(); + await connection.QueryAsync(new CommandDefinition(@"select * from ""WorkItems""", cancellationToken: cancellationToken)))).ToList(); } - public async Task GetAsync(int id) + public async Task GetAsync(int id, CancellationToken cancellationToken) { var query = await QueryAsync(async connection => - await connection.QueryAsync(@"select * from ""WorkItems"" where ""Id""=@id", new {id})); + await connection.QueryAsync(new CommandDefinition(@"select * from ""WorkItems"" where ""Id""=@id", new {id}, cancellationToken: cancellationToken))); return query.Single(); } - public Task GetSecondaryAsync(int id, string relationshipName) + public Task GetSecondaryAsync(int id, string relationshipName, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task GetRelationshipAsync(int id, string relationshipName) + public Task GetRelationshipAsync(int id, string relationshipName, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public async Task CreateAsync(WorkItem resource) + public async Task CreateAsync(WorkItem resource, CancellationToken cancellationToken) { return (await QueryAsync(async connection => { @@ -54,35 +55,35 @@ public async Task CreateAsync(WorkItem resource) @"insert into ""WorkItems"" (""Title"", ""IsBlocked"", ""DurationInHours"", ""ProjectId"") values " + @"(@description, @isLocked, @ordinal, @uniqueId) returning ""Id"", ""Title"", ""IsBlocked"", ""DurationInHours"", ""ProjectId"""; - return await connection.QueryAsync(query, new + return await connection.QueryAsync(new CommandDefinition(query, new { description = resource.Title, ordinal = resource.DurationInHours, uniqueId = resource.ProjectId, isLocked = resource.IsBlocked - }); + }, cancellationToken: cancellationToken)); })).SingleOrDefault(); } - public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds) + public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task UpdateAsync(int id, WorkItem resource) + public Task UpdateAsync(int id, WorkItem resource, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task SetRelationshipAsync(int primaryId, string relationshipName, object secondaryResourceIds) + public Task SetRelationshipAsync(int primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public async Task DeleteAsync(int id) + public async Task DeleteAsync(int id, CancellationToken cancellationToken) { await QueryAsync(async connection => - await connection.QueryAsync(@"delete from ""WorkItems"" where ""Id""=@id", new {id})); + await connection.QueryAsync(new CommandDefinition(@"delete from ""WorkItems"" where ""Id""=@id", new {id}, cancellationToken: cancellationToken))); } - public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds) + public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 74bb2301a5..c9820f8a22 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; @@ -19,6 +20,9 @@ public ReportsController( { } [HttpGet] - public override async Task GetAsync() => await base.GetAsync(); + public override async Task GetAsync(CancellationToken cancellationToken) + { + return await base.GetAsync(cancellationToken); + } } } diff --git a/src/Examples/ReportsExample/Services/ReportService.cs b/src/Examples/ReportsExample/Services/ReportService.cs index 61a75d3886..6b13e1f91d 100644 --- a/src/Examples/ReportsExample/Services/ReportService.cs +++ b/src/Examples/ReportsExample/Services/ReportService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Services; using Microsoft.Extensions.Logging; @@ -16,7 +17,7 @@ public ReportService(ILoggerFactory loggerFactory) _logger = loggerFactory.CreateLogger(); } - public Task> GetAsync() + public Task> GetAsync(CancellationToken cancellationToken) { _logger.LogInformation("GetAsync"); diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 14338fd5da..c3c966b4ce 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -91,12 +92,12 @@ protected BaseJsonApiController( /// Gets a collection of top-level (non-nested) resources. /// Example: GET /articles HTTP/1.1 /// - public virtual async Task GetAsync() + public virtual async Task GetAsync(CancellationToken cancellationToken) { _traceWriter.LogMethodStart(); if (_getAll == null) throw new RequestMethodNotAllowedException(HttpMethod.Get); - var resources = await _getAll.GetAsync(); + var resources = await _getAll.GetAsync(cancellationToken); return Ok(resources); } @@ -105,12 +106,12 @@ public virtual async Task GetAsync() /// Gets a single top-level (non-nested) resource by ID. /// Example: /articles/1 /// - public virtual async Task GetAsync(TId id) + public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id}); if (_getById == null) throw new RequestMethodNotAllowedException(HttpMethod.Get); - var resource = await _getById.GetAsync(id); + var resource = await _getById.GetAsync(id, cancellationToken); return Ok(resource); } @@ -121,13 +122,13 @@ public virtual async Task GetAsync(TId id) /// GET /articles/1/author HTTP/1.1 /// GET /articles/1/revisions HTTP/1.1 /// - public virtual async Task GetSecondaryAsync(TId id, string relationshipName) + public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); if (_getSecondary == null) throw new RequestMethodNotAllowedException(HttpMethod.Get); - var relationship = await _getSecondary.GetSecondaryAsync(id, relationshipName); + var relationship = await _getSecondary.GetSecondaryAsync(id, relationshipName, cancellationToken); return Ok(relationship); } @@ -137,13 +138,13 @@ public virtual async Task GetSecondaryAsync(TId id, string relati /// Example: GET /articles/1/relationships/author HTTP/1.1 /// Example: GET /articles/1/relationships/revisions HTTP/1.1 /// - public virtual async Task GetRelationshipAsync(TId id, string relationshipName) + public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); if (_getRelationship == null) throw new RequestMethodNotAllowedException(HttpMethod.Get); - var rightResources = await _getRelationship.GetRelationshipAsync(id, relationshipName); + var rightResources = await _getRelationship.GetRelationshipAsync(id, relationshipName, cancellationToken); return Ok(rightResources); } @@ -152,7 +153,7 @@ public virtual async Task GetRelationshipAsync(TId id, string rel /// Creates a new resource with attributes, relationships or both. /// Example: POST /articles HTTP/1.1 /// - public virtual async Task PostAsync([FromBody] TResource resource) + public virtual async Task PostAsync([FromBody] TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {resource}); if (resource == null) throw new ArgumentNullException(nameof(resource)); @@ -169,7 +170,7 @@ public virtual async Task PostAsync([FromBody] TResource resource throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, namingStrategy); } - var newResource = await _create.CreateAsync(resource); + var newResource = await _create.CreateAsync(resource, cancellationToken); var resourceId = (newResource ?? resource).StringId; string locationUrl = $"{HttpContext.Request.Path}/{resourceId}"; @@ -190,14 +191,15 @@ public virtual async Task PostAsync([FromBody] TResource resource /// The identifier of the primary resource. /// The relationship to add resources to. /// The set of resources to add to the relationship. - public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) + /// Propagates notification that request handling should be canceled. + public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); if (secondaryResourceIds == null) throw new ArgumentNullException(nameof(secondaryResourceIds)); if (_addToRelationship == null) throw new RequestMethodNotAllowedException(HttpMethod.Post); - await _addToRelationship.AddToToManyRelationshipAsync(id, relationshipName, secondaryResourceIds); + await _addToRelationship.AddToToManyRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); return NoContent(); } @@ -207,7 +209,7 @@ public virtual async Task PostRelationshipAsync(TId id, string re /// Only the values of sent attributes are replaced. And only the values of sent relationships are replaced. /// Example: PATCH /articles/1 HTTP/1.1 /// - public virtual async Task PatchAsync(TId id, [FromBody] TResource resource) + public virtual async Task PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, resource}); if (resource == null) throw new ArgumentNullException(nameof(resource)); @@ -220,7 +222,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource throw new InvalidModelStateException(ModelState, typeof(TResource), _options.IncludeExceptionStackTraceInErrors, namingStrategy); } - var updated = await _update.UpdateAsync(id, resource); + var updated = await _update.UpdateAsync(id, resource, cancellationToken); return updated == null ? (IActionResult) NoContent() : Ok(updated); } @@ -232,13 +234,14 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource /// The identifier of the primary resource. /// The relationship for which to perform a complete replacement. /// The resource or set of resources to assign to the relationship. - public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds) + /// Propagates notification that request handling should be canceled. + public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); if (_setRelationship == null) throw new RequestMethodNotAllowedException(HttpMethod.Patch); - await _setRelationship.SetRelationshipAsync(id, relationshipName, secondaryResourceIds); + await _setRelationship.SetRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); return NoContent(); } @@ -247,12 +250,12 @@ public virtual async Task PatchRelationshipAsync(TId id, string r /// Deletes an existing resource. /// Example: DELETE /articles/1 HTTP/1.1 /// - public virtual async Task DeleteAsync(TId id) + public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id}); if (_delete == null) throw new RequestMethodNotAllowedException(HttpMethod.Delete); - await _delete.DeleteAsync(id); + await _delete.DeleteAsync(id, cancellationToken); return NoContent(); } @@ -264,14 +267,15 @@ public virtual async Task DeleteAsync(TId id) /// The identifier of the primary resource. /// The relationship to remove resources from. /// The set of resources to remove from the relationship. - public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) + /// Propagates notification that request handling should be canceled. + public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); if (secondaryResourceIds == null) throw new ArgumentNullException(nameof(secondaryResourceIds)); if (_removeFromRelationship == null) throw new RequestMethodNotAllowedException(HttpMethod.Delete); - await _removeFromRelationship.RemoveFromToManyRelationshipAsync(id, relationshipName, secondaryResourceIds); + await _removeFromRelationship.RemoveFromToManyRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); return NoContent(); } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs index ea00374638..276efd28ff 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; @@ -27,34 +28,47 @@ protected JsonApiCommandController( /// [HttpPost] - public override async Task PostAsync([FromBody] TResource resource) - => await base.PostAsync(resource); + public override async Task PostAsync([FromBody] TResource resource, CancellationToken cancellationToken) + { + return await base.PostAsync(resource, cancellationToken); + } /// [HttpPost("{id}/relationships/{relationshipName}")] public override async Task PostRelationshipAsync( - TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds); + TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } /// [HttpPatch("{id}")] - public override async Task PatchAsync(TId id, [FromBody] TResource resource) - => await base.PatchAsync(id, resource); + public override async Task PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken) + { + return await base.PatchAsync(id, resource, cancellationToken); + } /// [HttpPatch("{id}/relationships/{relationshipName}")] public override async Task PatchRelationshipAsync( - TId id, string relationshipName, [FromBody] object secondaryResourceIds) - => await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds); + TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } /// [HttpDelete("{id}")] - public override async Task DeleteAsync(TId id) => await base.DeleteAsync(id); + public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) + { + return await base.DeleteAsync(id, cancellationToken); + } /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds); + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } } /// diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 5d56ceeace..b403b72dbd 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; @@ -45,54 +46,75 @@ public JsonApiController( /// [HttpGet] - public override async Task GetAsync() => await base.GetAsync(); + public override async Task GetAsync(CancellationToken cancellationToken) + { + return await base.GetAsync(cancellationToken); + } /// [HttpGet("{id}")] - public override async Task GetAsync(TId id) => await base.GetAsync(id); + public override async Task GetAsync(TId id, CancellationToken cancellationToken) + { + return await base.GetAsync(id, cancellationToken); + } /// [HttpGet("{id}/{relationshipName}")] - public override async Task GetSecondaryAsync(TId id, string relationshipName) - => await base.GetSecondaryAsync(id, relationshipName); + public override async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetSecondaryAsync(id, relationshipName, cancellationToken); + } /// [HttpGet("{id}/relationships/{relationshipName}")] - public override async Task GetRelationshipAsync(TId id, string relationshipName) - => await base.GetRelationshipAsync(id, relationshipName); + public override async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetRelationshipAsync(id, relationshipName, cancellationToken); + } /// [HttpPost] - public override async Task PostAsync([FromBody] TResource resource) - => await base.PostAsync(resource); + public override async Task PostAsync([FromBody] TResource resource, CancellationToken cancellationToken) + { + return await base.PostAsync(resource, cancellationToken); + } /// [HttpPost("{id}/relationships/{relationshipName}")] public override async Task PostRelationshipAsync( - TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds); + TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } /// [HttpPatch("{id}")] - public override async Task PatchAsync(TId id, [FromBody] TResource resource) + public override async Task PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken) { - return await base.PatchAsync(id, resource); + return await base.PatchAsync(id, resource, cancellationToken); } /// [HttpPatch("{id}/relationships/{relationshipName}")] public override async Task PatchRelationshipAsync( - TId id, string relationshipName, [FromBody] object secondaryResourceIds) - => await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds); + TId id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } /// [HttpDelete("{id}")] - public override async Task DeleteAsync(TId id) => await base.DeleteAsync(id); + public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) + { + return await base.DeleteAsync(id, cancellationToken); + } /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds) - => await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds); + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) + { + return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } } /// diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index 4ca4e8d361..17ed6725db 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; @@ -26,21 +27,31 @@ protected JsonApiQueryController( /// [HttpGet] - public override async Task GetAsync() => await base.GetAsync(); + public override async Task GetAsync(CancellationToken cancellationToken) + { + return await base.GetAsync(cancellationToken); + } /// [HttpGet("{id}")] - public override async Task GetAsync(TId id) => await base.GetAsync(id); + public override async Task GetAsync(TId id, CancellationToken cancellationToken) + { + return await base.GetAsync(id, cancellationToken); + } /// [HttpGet("{id}/{relationshipName}")] - public override async Task GetSecondaryAsync(TId id, string relationshipName) - => await base.GetSecondaryAsync(id, relationshipName); + public override async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetSecondaryAsync(id, relationshipName, cancellationToken); + } /// [HttpGet("{id}/relationships/{relationshipName}")] - public override async Task GetRelationshipAsync(TId id, string relationshipName) - => await base.GetRelationshipAsync(id, relationshipName); + public override async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) + { + return await base.GetRelationshipAsync(id, relationshipName, cancellationToken); + } } /// diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs index f3c00d5c47..486146ff6f 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Hooks.Internal.Discovery; using JsonApiDotNetCore.Queries; @@ -152,7 +153,7 @@ private IEnumerable GetWhereAndInclude(IReadOnlyColle } var repository = GetRepository(); - return repository.GetAsync(queryLayer).Result; + return repository.GetAsync(queryLayer, CancellationToken.None).Result; } private static FilterExpression CreateFilterByIds(IReadOnlyCollection ids, ResourceContext resourceContext) diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs index ffeb61a07f..9d8c906106 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourcePipeline.cs @@ -1,10 +1,11 @@ +using System.Threading; using JsonApiDotNetCore.Services; namespace JsonApiDotNetCore.Hooks.Internal.Execution { /// /// An enum that represents the initiator of a resource hook. Eg, when BeforeCreate() - /// is called from , it will be called + /// is called from , it will be called /// with parameter pipeline = ResourceAction.GetSingle. /// public enum ResourcePipeline diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs index 920341045d..3ac67c0a93 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IReadHookExecutor.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using JsonApiDotNetCore.Hooks.Internal.Execution; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Services; @@ -18,7 +19,7 @@ public interface IReadHookExecutor /// /// An enum indicating from where the hook was triggered. /// StringId of the requested resource in the case of - /// . + /// . /// The type of the request resource void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TResource : class, IIdentifiable; /// diff --git a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs index 1207efbb95..451af73e63 100644 --- a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs +++ b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Net; +using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Serialization.Objects; @@ -45,6 +46,11 @@ protected virtual LogLevel GetLogLevel(Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); + if (exception is OperationCanceledException) + { + return LogLevel.None; + } + if (exception is JsonApiException || exception is InvalidModelStateException) { return LogLevel.Information; @@ -73,11 +79,16 @@ protected virtual ErrorDocument CreateErrorDocument(Exception exception) Error error = exception is JsonApiException jsonApiException ? jsonApiException.Error - : new Error(HttpStatusCode.InternalServerError) - { - Title = "An unhandled error occurred while processing this request.", - Detail = exception.Message - }; + : exception is TaskCanceledException + ? new Error((HttpStatusCode) 499) + { + Title = "Request execution was canceled." + } + : new Error(HttpStatusCode.InternalServerError) + { + Title = "An unhandled error occurred while processing this request.", + Detail = exception.Message + }; ApplyOptions(error, exception); diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index f9b73a13a2..f2c63998be 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -52,17 +53,17 @@ public EntityFrameworkCoreRepository( } /// - public virtual async Task> GetAsync(QueryLayer layer) + public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {layer}); if (layer == null) throw new ArgumentNullException(nameof(layer)); IQueryable query = ApplyQueryLayer(layer); - return await query.ToListAsync(); + return await query.ToListAsync(cancellationToken); } /// - public virtual async Task CountAsync(FilterExpression topFilter) + public virtual async Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {topFilter}); @@ -73,7 +74,7 @@ public virtual async Task CountAsync(FilterExpression topFilter) }; IQueryable query = ApplyQueryLayer(layer); - return await query.CountAsync(); + return await query.CountAsync(cancellationToken); } protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) @@ -114,7 +115,7 @@ protected virtual IQueryable GetAll() } /// - public virtual async Task CreateAsync(TResource resource) + public virtual async Task CreateAsync(TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {resource}); if (resource == null) throw new ArgumentNullException(nameof(resource)); @@ -124,17 +125,17 @@ public virtual async Task CreateAsync(TResource resource) foreach (var relationship in _targetedFields.Relationships) { var rightValue = relationship.GetValue(resource); - await UpdateRelationshipAsync(relationship, resource, rightValue, collector); + await UpdateRelationshipAsync(relationship, resource, rightValue, collector, cancellationToken); } var dbSet = _dbContext.Set(); dbSet.Add(resource); - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } /// - public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds) + public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryId, secondaryResourceIds}); if (secondaryResourceIds == null) throw new ArgumentNullException(nameof(secondaryResourceIds)); @@ -146,14 +147,14 @@ public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet(primaryId); - await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector); + await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken); - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } } /// - public virtual async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds) + public virtual async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryResource, secondaryResourceIds}); @@ -162,13 +163,13 @@ public virtual async Task SetRelationshipAsync(TResource primaryResource, object AssertIsNotClearingRequiredRelationship(relationship, primaryResource, secondaryResourceIds); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector); + await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken); - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } /// - public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase) + public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {resourceFromRequest, resourceFromDatabase}); if (resourceFromRequest == null) throw new ArgumentNullException(nameof(resourceFromRequest)); @@ -182,7 +183,7 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightResources); - await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResources, collector); + await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResources, collector, cancellationToken); } foreach (var attribute in _targetedFields.Attributes) @@ -190,11 +191,11 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest)); } - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } /// - public virtual async Task DeleteAsync(TId id) + public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id}); @@ -208,13 +209,13 @@ public virtual async Task DeleteAsync(TId id) if (RequiresLoadOfRelationshipForDeletion(relationship)) { var navigation = GetNavigationEntry(resource, relationship); - await navigation.LoadAsync(); + await navigation.LoadAsync(cancellationToken); } } _dbContext.Remove(resource); - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } private NavigationEntry GetNavigationEntry(TResource resource, RelationshipAttribute relationship) @@ -255,7 +256,7 @@ private INavigation TryGetNavigation(RelationshipAttribute relationship) } /// - public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds) + public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryResource, secondaryResourceIds}); if (secondaryResourceIds == null) throw new ArgumentNullException(nameof(secondaryResourceIds)); @@ -270,9 +271,9 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryRes AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - await UpdateRelationshipAsync(relationship, primaryResource, rightResourceIds, collector); + await UpdateRelationshipAsync(relationship, primaryResource, rightResourceIds, collector, cancellationToken); - await SaveChangesAsync(); + await SaveChangesAsync(cancellationToken); } protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute relationship, TResource leftResource, object rightValue) @@ -309,17 +310,17 @@ private static bool IsRequiredToManyRelationshipBeingCleared(RelationshipAttribu } /// - public virtual async Task GetForUpdateAsync(QueryLayer queryLayer) + public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) { - var resources = await GetAsync(queryLayer); + var resources = await GetAsync(queryLayer, cancellationToken); return resources.FirstOrDefault(); } - protected virtual async Task SaveChangesAsync() + protected virtual async Task SaveChangesAsync(CancellationToken cancellationToken) { try { - await _dbContext.SaveChangesAsync(); + await _dbContext.SaveChangesAsync(cancellationToken); } catch (DbUpdateException exception) { @@ -328,7 +329,7 @@ protected virtual async Task SaveChangesAsync() } protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, TResource leftResource, - object valueToAssign, PlaceholderResourceCollector collector) + object valueToAssign, PlaceholderResourceCollector collector, CancellationToken cancellationToken) { var trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked(valueToAssign, relationship.Property.PropertyType, collector); @@ -337,7 +338,7 @@ protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, var entityEntry = _dbContext.Entry(trackedValueToAssign); var inversePropertyName = relationship.InverseNavigationProperty.Name; - await entityEntry.Reference(inversePropertyName).LoadAsync(); + await entityEntry.Reference(inversePropertyName).LoadAsync(cancellationToken); } relationship.SetValue(leftResource, trackedValueToAssign); diff --git a/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs index 9c04f4bcc5..907792c8b7 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceReadRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; @@ -23,11 +24,11 @@ public interface IResourceReadRepository /// /// Executes a read query using the specified constraints and returns the collection of matching resources. /// - Task> GetAsync(QueryLayer layer); + Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken); /// /// Executes a read query using the specified top-level filter and returns the top-level count of matching resources. /// - Task CountAsync(FilterExpression topFilter); + Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs index e27fed924f..ca37d9dcd0 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; @@ -15,60 +16,60 @@ public interface IResourceRepositoryAccessor /// /// Invokes . /// - Task> GetAsync(QueryLayer layer) + Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource type. /// - Task> GetAsync(Type resourceType, QueryLayer layer); + Task> GetAsync(Type resourceType, QueryLayer layer, CancellationToken cancellationToken); /// /// Invokes for the specified resource type. /// - Task CountAsync(FilterExpression topFilter) + Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task CreateAsync(TResource resource) + Task CreateAsync(TResource resource, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource type. /// - Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds) + Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase) + Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds) + Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource type. /// - Task DeleteAsync(TId id) + Task DeleteAsync(TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds) + Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task GetForUpdateAsync(QueryLayer queryLayer) + Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs index 04ca3bc305..929631d3b7 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Resources; @@ -22,36 +23,36 @@ public interface IResourceWriteRepository /// /// Creates a new resource in the underlying data store. /// - Task CreateAsync(TResource resource); + Task CreateAsync(TResource resource, CancellationToken cancellationToken); /// /// Adds resources to a to-many relationship in the underlying data store. /// - Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds); + Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken); /// /// Updates the attributes and relationships of an existing resource in the underlying data store. /// - Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase); + Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken); /// /// Performs a complete replacement of the relationship in the underlying data store. /// - Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds); + Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken); /// /// Deletes an existing resource from the underlying data store. /// - Task DeleteAsync(TId id); + Task DeleteAsync(TId id, CancellationToken cancellationToken); /// /// Removes resources from a to-many relationship in the underlying data store. /// - Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds); + Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken); /// /// Retrieves a resource with all of its attributes, including the set of targeted relationships, in preparation for update. /// - Task GetForUpdateAsync(QueryLayer queryLayer); + Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 0a65507ac0..a4cb63d1a9 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries; @@ -22,84 +23,84 @@ public ResourceRepositoryAccessor(IServiceProvider serviceProvider, IResourceCon } /// - public async Task> GetAsync(QueryLayer layer) + public async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetReadRepository(typeof(TResource)); - return (IReadOnlyCollection) await repository.GetAsync(layer); + return (IReadOnlyCollection) await repository.GetAsync(layer, cancellationToken); } /// - public async Task> GetAsync(Type resourceType, QueryLayer layer) + public async Task> GetAsync(Type resourceType, QueryLayer layer, CancellationToken cancellationToken) { if (resourceType == null) throw new ArgumentNullException(nameof(resourceType)); dynamic repository = GetReadRepository(resourceType); - return (IReadOnlyCollection) await repository.GetAsync(layer); + return (IReadOnlyCollection) await repository.GetAsync(layer, cancellationToken); } /// - public async Task CountAsync(FilterExpression topFilter) + public async Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetReadRepository(typeof(TResource)); - return (int) await repository.CountAsync(topFilter); + return (int) await repository.CountAsync(topFilter, cancellationToken); } /// - public async Task CreateAsync(TResource resource) + public async Task CreateAsync(TResource resource, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.CreateAsync(resource); + await repository.CreateAsync(resource, cancellationToken); } /// - public async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds) + public async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds); + await repository.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds, cancellationToken); } /// - public async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase) + public async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.UpdateAsync(resourceFromRequest, resourceFromDatabase); + await repository.UpdateAsync(resourceFromRequest, resourceFromDatabase, cancellationToken); } /// - public async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds) + public async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.SetRelationshipAsync(primaryResource, secondaryResourceIds); + await repository.SetRelationshipAsync(primaryResource, secondaryResourceIds, cancellationToken); } /// - public async Task DeleteAsync(TId id) + public async Task DeleteAsync(TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.DeleteAsync(id); + await repository.DeleteAsync(id, cancellationToken); } /// - public async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds) + public async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.RemoveFromToManyRelationshipAsync(primaryResource, secondaryResourceIds); + await repository.RemoveFromToManyRelationshipAsync(primaryResource, secondaryResourceIds, cancellationToken); } /// - public async Task GetForUpdateAsync(QueryLayer queryLayer) + public async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - return await repository.GetForUpdateAsync(queryLayer); + return await repository.GetForUpdateAsync(queryLayer, cancellationToken); } protected object GetReadRepository(Type resourceType) diff --git a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs index 0a400e5512..70268bb416 100644 --- a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs @@ -1,23 +1,24 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace JsonApiDotNetCore.Services { public static class AsyncCollectionExtensions { - public static async Task AddRangeAsync(this ICollection source, IAsyncEnumerable elementsToAdd) + public static async Task AddRangeAsync(this ICollection source, IAsyncEnumerable elementsToAdd, CancellationToken cancellationToken = default) { - await foreach (var missingResource in elementsToAdd) + await foreach (var missingResource in elementsToAdd.WithCancellation(cancellationToken)) { source.Add(missingResource); } } - public static async Task> ToListAsync(this IAsyncEnumerable source) + public static async Task> ToListAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) { var list = new List(); - await foreach (var element in source) + await foreach (var element in source.WithCancellation(cancellationToken)) { list.Add(element); } diff --git a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs index 01a37e726e..a3e1b9fa76 100644 --- a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -19,6 +20,7 @@ public interface IAddToRelationshipService /// The identifier of the primary resource. /// The relationship to add resources to. /// The set of resources to add to the relationship. - Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds); + /// Propagates notification that request handling should be canceled. + Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/ICreateService.cs b/src/JsonApiDotNetCore/Services/ICreateService.cs index 9cbb6d18df..72752b4c56 100644 --- a/src/JsonApiDotNetCore/Services/ICreateService.cs +++ b/src/JsonApiDotNetCore/Services/ICreateService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -15,6 +16,6 @@ public interface ICreateService /// /// Handles a json:api request to create a new resource with attributes, relationships or both. /// - Task CreateAsync(TResource resource); + Task CreateAsync(TResource resource, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IDeleteService.cs b/src/JsonApiDotNetCore/Services/IDeleteService.cs index 92b5979b14..56817bd49a 100644 --- a/src/JsonApiDotNetCore/Services/IDeleteService.cs +++ b/src/JsonApiDotNetCore/Services/IDeleteService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -15,6 +16,6 @@ public interface IDeleteService /// /// Handles a json:api request to delete an existing resource. /// - Task DeleteAsync(TId id); + Task DeleteAsync(TId id, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IGetAllService.cs b/src/JsonApiDotNetCore/Services/IGetAllService.cs index e13ad53dce..ff190c3954 100644 --- a/src/JsonApiDotNetCore/Services/IGetAllService.cs +++ b/src/JsonApiDotNetCore/Services/IGetAllService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -16,6 +17,6 @@ public interface IGetAllService /// /// Handles a json:api request to retrieve a collection of resources for a primary endpoint. /// - Task> GetAsync(); + Task> GetAsync(CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IGetByIdService.cs b/src/JsonApiDotNetCore/Services/IGetByIdService.cs index 768b5a6d79..8a47976be4 100644 --- a/src/JsonApiDotNetCore/Services/IGetByIdService.cs +++ b/src/JsonApiDotNetCore/Services/IGetByIdService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -15,6 +16,6 @@ public interface IGetByIdService /// /// Handles a json:api request to retrieve a single resource for a primary endpoint. /// - Task GetAsync(TId id); + Task GetAsync(TId id, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs index 444bba4ad5..fb54980786 100644 --- a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -15,6 +16,6 @@ public interface IGetRelationshipService /// /// Handles a json:api request to retrieve a single relationship. /// - Task GetRelationshipAsync(TId id, string relationshipName); + Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs index dbe52cf6f9..e7765e0367 100644 --- a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs +++ b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -15,6 +16,6 @@ public interface IGetSecondaryService /// /// Handles a json:api request to retrieve a single resource or a collection of resources for a secondary endpoint, such as /articles/1/author or /articles/1/revisions. /// - Task GetSecondaryAsync(TId id, string relationshipName); + Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs index 34c39608e6..a34eca036c 100644 --- a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -19,6 +20,7 @@ public interface IRemoveFromRelationshipService /// The identifier of the primary resource. /// The relationship to remove resources from. /// The set of resources to remove from the relationship. - Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds); + /// Propagates notification that request handling should be canceled. + Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs index 75bfc2722e..72906ccccb 100644 --- a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -18,6 +19,7 @@ public interface ISetRelationshipService /// The identifier of the primary resource. /// The relationship for which to perform a complete replacement. /// The resource or set of resources to assign to the relationship. - Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds); + /// Propagates notification that request handling should be canceled. + Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IUpdateService.cs b/src/JsonApiDotNetCore/Services/IUpdateService.cs index 1e2b60832f..3db2807c96 100644 --- a/src/JsonApiDotNetCore/Services/IUpdateService.cs +++ b/src/JsonApiDotNetCore/Services/IUpdateService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Resources; @@ -16,6 +17,6 @@ public interface IUpdateService /// Handles a json:api request to update the attributes and/or relationships of an existing resource. /// Only the values of sent attributes are replaced. And only the values of sent relationships are replaced. /// - Task UpdateAsync(TId id, TResource resource); + Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index b13929560d..c880c147f7 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -56,7 +58,7 @@ public JsonApiResourceService( } /// - public virtual async Task> GetAsync() + public virtual async Task> GetAsync(CancellationToken cancellationToken) { _traceWriter.LogMethodStart(); @@ -65,7 +67,7 @@ public virtual async Task> GetAsync() if (_options.IncludeTotalResourceCount) { var topFilter = _queryLayerComposer.GetTopFilterFromConstraints(); - _paginationContext.TotalResourceCount = await _repositoryAccessor.CountAsync(topFilter); + _paginationContext.TotalResourceCount = await _repositoryAccessor.CountAsync(topFilter, cancellationToken); if (_paginationContext.TotalResourceCount == 0) { @@ -74,7 +76,7 @@ public virtual async Task> GetAsync() } var queryLayer = _queryLayerComposer.ComposeFromConstraints(_request.PrimaryResource); - var resources = await _repositoryAccessor.GetAsync(queryLayer); + var resources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); if (queryLayer.Pagination?.PageSize != null && queryLayer.Pagination.PageSize.Value == resources.Count) { @@ -86,13 +88,13 @@ public virtual async Task> GetAsync() } /// - public virtual async Task GetAsync(TId id) + public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id}); _hookExecutor.BeforeReadSingle(id, ResourcePipeline.GetSingle); - var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.PreserveExisting); + var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.PreserveExisting, cancellationToken); AssertPrimaryResourceExists(primaryResource); _hookExecutor.AfterReadSingle(primaryResource, ResourcePipeline.GetSingle); @@ -102,7 +104,7 @@ public virtual async Task GetAsync(TId id) } /// - public virtual async Task GetSecondaryAsync(TId id, string relationshipName) + public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName}); AssertHasRelationship(_request.Relationship, relationshipName); @@ -120,7 +122,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relationshipN // And we should call BlogResourceDefinition.OnApplyFilter to filter out soft-deleted blogs and translate from equals('IsDeleted','false') to equals('Blog.IsDeleted','false') } - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer); + var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); var primaryResource = primaryResources.SingleOrDefault(); AssertPrimaryResourceExists(primaryResource); @@ -139,7 +141,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relationshipN } /// - public virtual async Task GetRelationshipAsync(TId id, string relationshipName) + public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, relationshipName}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); @@ -151,7 +153,7 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh var secondaryLayer = _queryLayerComposer.ComposeSecondaryLayerForRelationship(_request.SecondaryResource); var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship); - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer); + var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); var primaryResource = primaryResources.SingleOrDefault(); AssertPrimaryResourceExists(primaryResource); @@ -164,7 +166,7 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh } /// - public virtual async Task CreateAsync(TResource resource) + public virtual async Task CreateAsync(TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {resource}); if (resource == null) throw new ArgumentNullException(nameof(resource)); @@ -180,21 +182,21 @@ public virtual async Task CreateAsync(TResource resource) try { - await _repositoryAccessor.CreateAsync(resource); + await _repositoryAccessor.CreateAsync(resource, cancellationToken); } catch (DataStoreUpdateException) { - var existingResource = await TryGetPrimaryResourceByIdAsync(resource.Id, TopFieldSelection.OnlyIdAttribute); + var existingResource = await TryGetPrimaryResourceByIdAsync(resource.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken); if (existingResource != null) { throw new ResourceAlreadyExistsException(resource.StringId, _request.PrimaryResource.PublicName); } - await AssertResourcesToAssignInRelationshipsExistAsync(resource); + await AssertResourcesToAssignInRelationshipsExistAsync(resource, cancellationToken); throw; } - var resourceFromDatabase = await TryGetPrimaryResourceByIdAsync(resource.Id, TopFieldSelection.WithAllAttributes); + var resourceFromDatabase = await TryGetPrimaryResourceByIdAsync(resource.Id, TopFieldSelection.WithAllAttributes, cancellationToken); AssertPrimaryResourceExists(resourceFromDatabase); _hookExecutor.AfterCreate(resourceFromDatabase); @@ -211,7 +213,7 @@ public virtual async Task CreateAsync(TResource resource) return resourceFromDatabase; } - private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource resource) + private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource resource, CancellationToken cancellationToken) { var missingResources = new List(); @@ -220,8 +222,8 @@ private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource re object rightValue = relationship.GetValue(resource); ICollection rightResourceIds = TypeHelper.ExtractResources(rightValue); - var missingResourcesInRelationship = GetMissingRightResourcesAsync(queryLayer, relationship, rightResourceIds); - await missingResources.AddRangeAsync(missingResourcesInRelationship); + var missingResourcesInRelationship = GetMissingRightResourcesAsync(queryLayer, relationship, rightResourceIds, cancellationToken); + await missingResources.AddRangeAsync(missingResourcesInRelationship, cancellationToken); } if (missingResources.Any()) @@ -232,10 +234,10 @@ private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource re private async IAsyncEnumerable GetMissingRightResourcesAsync( QueryLayer existingRightResourceIdsQueryLayer, RelationshipAttribute relationship, - ICollection rightResourceIds) + ICollection rightResourceIds, [EnumeratorCancellation] CancellationToken cancellationToken) { var existingResources = await _repositoryAccessor.GetAsync( - existingRightResourceIdsQueryLayer.ResourceContext.ResourceType, existingRightResourceIdsQueryLayer); + existingRightResourceIdsQueryLayer.ResourceContext.ResourceType, existingRightResourceIdsQueryLayer, cancellationToken); var existingResourceIds = existingResources.Select(resource => resource.StringId).ToArray(); @@ -250,7 +252,7 @@ private async IAsyncEnumerable GetMissingRightRes } /// - public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds) + public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryId, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); @@ -264,29 +266,29 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshi { // In the case of a many-to-many relationship, creating a duplicate entry in the join table results in a // unique constraint violation. We avoid that by excluding already-existing entries from the set in advance. - await RemoveExistingIdsFromSecondarySet(primaryId, secondaryResourceIds, hasManyThrough); + await RemoveExistingIdsFromSecondarySet(primaryId, secondaryResourceIds, hasManyThrough, cancellationToken); } try { - await _repositoryAccessor.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds); + await _repositoryAccessor.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds, cancellationToken); } catch (DataStoreUpdateException) { - var primaryResource = await TryGetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute); + var primaryResource = await TryGetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); AssertPrimaryResourceExists(primaryResource); - await AssertResourcesExistAsync(secondaryResourceIds); + await AssertResourcesExistAsync(secondaryResourceIds, cancellationToken); throw; } } } private async Task RemoveExistingIdsFromSecondarySet(TId primaryId, ISet secondaryResourceIds, - HasManyThroughAttribute hasManyThrough) + HasManyThroughAttribute hasManyThrough, CancellationToken cancellationToken) { var queryLayer = _queryLayerComposer.ComposeForHasManyThrough(hasManyThrough, primaryId, secondaryResourceIds); - var primaryResources = await _repositoryAccessor.GetAsync(queryLayer); + var primaryResources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); var primaryResource = primaryResources.FirstOrDefault(); AssertPrimaryResourceExists(primaryResource); @@ -297,11 +299,11 @@ private async Task RemoveExistingIdsFromSecondarySet(TId primaryId, ISet secondaryResourceIds) + private async Task AssertResourcesExistAsync(ICollection secondaryResourceIds, CancellationToken cancellationToken) { var queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, secondaryResourceIds); - var missingResources = await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, secondaryResourceIds).ToListAsync(); + var missingResources = await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, secondaryResourceIds, cancellationToken).ToListAsync(cancellationToken); if (missingResources.Any()) { throw new ResourcesInRelationshipsNotFoundException(missingResources); @@ -309,7 +311,7 @@ private async Task AssertResourcesExistAsync(ICollection secondar } /// - public virtual async Task UpdateAsync(TId id, TResource resource) + public virtual async Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id, resource}); if (resource == null) throw new ArgumentNullException(nameof(resource)); @@ -319,21 +321,21 @@ public virtual async Task UpdateAsync(TId id, TResource resource) _hookExecutor.BeforeUpdateResource(resourceFromRequest); - TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(id); + TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(id, cancellationToken); _resourceChangeTracker.SetInitiallyStoredAttributeValues(resourceFromDatabase); try { - await _repositoryAccessor.UpdateAsync(resourceFromRequest, resourceFromDatabase); + await _repositoryAccessor.UpdateAsync(resourceFromRequest, resourceFromDatabase, cancellationToken); } catch (DataStoreUpdateException) { - await AssertResourcesToAssignInRelationshipsExistAsync(resourceFromRequest); + await AssertResourcesToAssignInRelationshipsExistAsync(resourceFromRequest, cancellationToken); throw; } - TResource afterResourceFromDatabase = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.WithAllAttributes); + TResource afterResourceFromDatabase = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.WithAllAttributes, cancellationToken); AssertPrimaryResourceExists(afterResourceFromDatabase); _hookExecutor.AfterUpdateResource(afterResourceFromDatabase); @@ -351,24 +353,24 @@ public virtual async Task UpdateAsync(TId id, TResource resource) } /// - public virtual async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds) + public virtual async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryId, relationshipName, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); AssertHasRelationship(_request.Relationship, relationshipName); - TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId); + TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId, cancellationToken); _hookExecutor.BeforeUpdateRelationship(resourceFromDatabase); try { - await _repositoryAccessor.SetRelationshipAsync(resourceFromDatabase, secondaryResourceIds); + await _repositoryAccessor.SetRelationshipAsync(resourceFromDatabase, secondaryResourceIds, cancellationToken); } catch (DataStoreUpdateException) { - await AssertResourcesExistAsync(TypeHelper.ExtractResources(secondaryResourceIds)); + await AssertResourcesExistAsync(TypeHelper.ExtractResources(secondaryResourceIds), cancellationToken); throw; } @@ -376,7 +378,7 @@ public virtual async Task SetRelationshipAsync(TId primaryId, string relationshi } /// - public virtual async Task DeleteAsync(TId id) + public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {id}); @@ -384,11 +386,11 @@ public virtual async Task DeleteAsync(TId id) try { - await _repositoryAccessor.DeleteAsync(id); + await _repositoryAccessor.DeleteAsync(id, cancellationToken); } catch (DataStoreUpdateException) { - var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.OnlyIdAttribute); + var primaryResource = await TryGetPrimaryResourceByIdAsync(id, TopFieldSelection.OnlyIdAttribute, cancellationToken); AssertPrimaryResourceExists(primaryResource); throw; } @@ -397,7 +399,7 @@ public virtual async Task DeleteAsync(TId id) } /// - public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds) + public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new {primaryId, relationshipName, secondaryResourceIds}); if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName)); @@ -405,27 +407,27 @@ public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relati AssertHasRelationship(_request.Relationship, relationshipName); - TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId); - await AssertResourcesExistAsync(secondaryResourceIds); + TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId, cancellationToken); + await AssertResourcesExistAsync(secondaryResourceIds, cancellationToken); if (secondaryResourceIds.Any()) { - await _repositoryAccessor.RemoveFromToManyRelationshipAsync(resourceFromDatabase, secondaryResourceIds); + await _repositoryAccessor.RemoveFromToManyRelationshipAsync(resourceFromDatabase, secondaryResourceIds, cancellationToken); } } - private async Task TryGetPrimaryResourceByIdAsync(TId id, TopFieldSelection fieldSelection) + private async Task TryGetPrimaryResourceByIdAsync(TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) { var primaryLayer = _queryLayerComposer.ComposeForGetById(id, _request.PrimaryResource, fieldSelection); - var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer); + var primaryResources = await _repositoryAccessor.GetAsync(primaryLayer, cancellationToken); return primaryResources.SingleOrDefault(); } - private async Task GetPrimaryResourceForUpdateAsync(TId id) + private async Task GetPrimaryResourceForUpdateAsync(TId id, CancellationToken cancellationToken) { var queryLayer = _queryLayerComposer.ComposeForUpdate(id, _request.PrimaryResource); - var resource = await _repositoryAccessor.GetForUpdateAsync(queryLayer); + var resource = await _repositoryAccessor.GetForUpdateAsync(queryLayer, cancellationToken); AssertPrimaryResourceExists(resource); return resource; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index 5ce3179878..c762873886 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; @@ -19,74 +20,74 @@ protected ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFacto } [HttpGet] - public override Task GetAsync() + public override Task GetAsync(CancellationToken cancellationToken) { - return base.GetAsync(); + return base.GetAsync(cancellationToken); } [HttpGet("{id}")] - public Task GetAsync(string id) + public Task GetAsync(string id, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.GetAsync(idValue); + return base.GetAsync(idValue, cancellationToken); } [HttpGet("{id}/{relationshipName}")] - public Task GetSecondaryAsync(string id, string relationshipName) + public Task GetSecondaryAsync(string id, string relationshipName, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.GetSecondaryAsync(idValue, relationshipName); + return base.GetSecondaryAsync(idValue, relationshipName, cancellationToken); } [HttpGet("{id}/relationships/{relationshipName}")] - public Task GetRelationshipAsync(string id, string relationshipName) + public Task GetRelationshipAsync(string id, string relationshipName, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.GetRelationshipAsync(idValue, relationshipName); + return base.GetRelationshipAsync(idValue, relationshipName, cancellationToken); } [HttpPost] - public override Task PostAsync([FromBody] TResource resource) + public override Task PostAsync([FromBody] TResource resource, CancellationToken cancellationToken) { - return base.PostAsync(resource); + return base.PostAsync(resource, cancellationToken); } [HttpPost("{id}/relationships/{relationshipName}")] public Task PostRelationshipAsync(string id, string relationshipName, - [FromBody] ISet secondaryResourceIds) + [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.PostRelationshipAsync(idValue, relationshipName, secondaryResourceIds); + return base.PostRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } [HttpPatch("{id}")] - public Task PatchAsync(string id, [FromBody] TResource resource) + public Task PatchAsync(string id, [FromBody] TResource resource, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.PatchAsync(idValue, resource); + return base.PatchAsync(idValue, resource, cancellationToken); } [HttpPatch("{id}/relationships/{relationshipName}")] public Task PatchRelationshipAsync(string id, string relationshipName, - [FromBody] object secondaryResourceIds) + [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.PatchRelationshipAsync(idValue, relationshipName, secondaryResourceIds); + return base.PatchRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } [HttpDelete("{id}")] - public Task DeleteAsync(string id) + public Task DeleteAsync(string id, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.DeleteAsync(idValue); + return base.DeleteAsync(idValue, cancellationToken); } [HttpDelete("{id}/relationships/{relationshipName}")] public Task DeleteRelationshipAsync(string id, string relationshipName, - [FromBody] ISet secondaryResourceIds) + [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { int idValue = HexadecimalCodec.Decode(id); - return base.DeleteRelationshipAsync(idValue, relationshipName, secondaryResourceIds); + return base.DeleteRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SparseFieldSets/ResultCapturingRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SparseFieldSets/ResultCapturingRepository.cs index 9d6a522ae1..4387296f9f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SparseFieldSets/ResultCapturingRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SparseFieldSets/ResultCapturingRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries; @@ -29,9 +30,9 @@ public ResultCapturingRepository( _captureStore = captureStore; } - public override async Task> GetAsync(QueryLayer layer) + public override async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) { - var resources = await base.GetAsync(layer); + var resources = await base.GetAsync(layer, cancellationToken); _captureStore.Add(resources); diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 57e41f7e96..5d6fddc6a0 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; @@ -59,10 +60,10 @@ public async Task GetAsync_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, getAll: serviceMock.Object); // Act - await controller.GetAsync(); + await controller.GetAsync(CancellationToken.None); // Assert - serviceMock.Verify(m => m.GetAsync(), Times.Once); + serviceMock.Verify(m => m.GetAsync(CancellationToken.None), Times.Once); } [Fact] @@ -72,7 +73,7 @@ public async Task GetAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, null); // Act - var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); + var exception = await Assert.ThrowsAsync(() => controller.GetAsync(CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -88,10 +89,10 @@ public async Task GetAsyncById_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, getById: serviceMock.Object); // Act - await controller.GetAsync(id); + await controller.GetAsync(id, CancellationToken.None); // Assert - serviceMock.Verify(m => m.GetAsync(id), Times.Once); + serviceMock.Verify(m => m.GetAsync(id, CancellationToken.None), Times.Once); } [Fact] @@ -102,7 +103,7 @@ public async Task GetAsyncById_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); + var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -118,10 +119,10 @@ public async Task GetRelationshipsAsync_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, getRelationship: serviceMock.Object); // Act - await controller.GetRelationshipAsync(id, string.Empty); + await controller.GetRelationshipAsync(id, string.Empty, CancellationToken.None); // Assert - serviceMock.Verify(m => m.GetRelationshipAsync(id, string.Empty), Times.Once); + serviceMock.Verify(m => m.GetRelationshipAsync(id, string.Empty, CancellationToken.None), Times.Once); } [Fact] @@ -132,7 +133,7 @@ public async Task GetRelationshipsAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); + var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -148,10 +149,10 @@ public async Task GetRelationshipAsync_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, getSecondary: serviceMock.Object); // Act - await controller.GetSecondaryAsync(id, string.Empty); + await controller.GetSecondaryAsync(id, string.Empty, CancellationToken.None); // Assert - serviceMock.Verify(m => m.GetSecondaryAsync(id, string.Empty), Times.Once); + serviceMock.Verify(m => m.GetSecondaryAsync(id, string.Empty, CancellationToken.None), Times.Once); } [Fact] @@ -162,7 +163,7 @@ public async Task GetRelationshipAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.GetSecondaryAsync(id, string.Empty)); + var exception = await Assert.ThrowsAsync(() => controller.GetSecondaryAsync(id, string.Empty, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -180,10 +181,10 @@ public async Task PatchAsync_Calls_Service() var controller = new ResourceController(new JsonApiOptions(), NullLoggerFactory.Instance, update: serviceMock.Object); // Act - await controller.PatchAsync(id, resource); + await controller.PatchAsync(id, resource, CancellationToken.None); // Assert - serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Once); + serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny(), CancellationToken.None), Times.Once); } [Fact] @@ -195,7 +196,7 @@ public async Task PatchAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, resource)); + var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, resource, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -210,14 +211,14 @@ public async Task PostAsync_Calls_Service() var serviceMock = new Mock>(); var controller = new ResourceController(new JsonApiOptions(), NullLoggerFactory.Instance, create: serviceMock.Object); - serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); + serviceMock.Setup(m => m.CreateAsync(It.IsAny(), It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }; // Act - await controller.PostAsync(resource); + await controller.PostAsync(resource, CancellationToken.None); // Assert - serviceMock.Verify(m => m.CreateAsync(It.IsAny()), Times.Once); + serviceMock.Verify(m => m.CreateAsync(It.IsAny(), CancellationToken.None), Times.Once); } [Fact] @@ -229,10 +230,10 @@ public async Task PatchRelationshipsAsync_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, setRelationship: serviceMock.Object); // Act - await controller.PatchRelationshipAsync(id, string.Empty, null); + await controller.PatchRelationshipAsync(id, string.Empty, null, CancellationToken.None); // Assert - serviceMock.Verify(m => m.SetRelationshipAsync(id, string.Empty, null), Times.Once); + serviceMock.Verify(m => m.SetRelationshipAsync(id, string.Empty, null, CancellationToken.None), Times.Once); } [Fact] @@ -243,7 +244,7 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipAsync(id, string.Empty, null)); + var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipAsync(id, string.Empty, null, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); @@ -259,10 +260,10 @@ public async Task DeleteAsync_Calls_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance, delete: serviceMock.Object); // Act - await controller.DeleteAsync(id); + await controller.DeleteAsync(id, CancellationToken.None); // Assert - serviceMock.Verify(m => m.DeleteAsync(id), Times.Once); + serviceMock.Verify(m => m.DeleteAsync(id, CancellationToken.None), Times.Once); } [Fact] @@ -273,7 +274,7 @@ public async Task DeleteAsync_Throws_405_If_No_Service() var controller = new ResourceController(new Mock().Object, NullLoggerFactory.Instance); // Act - var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); + var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id, CancellationToken.None)); // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, exception.Error.StatusCode); diff --git a/test/UnitTests/Data/EntityFrameworkCoreRepositoryTests.cs b/test/UnitTests/Data/EntityFrameworkCoreRepositoryTests.cs index 09732a0144..a80e5c076d 100644 --- a/test/UnitTests/Data/EntityFrameworkCoreRepositoryTests.cs +++ b/test/UnitTests/Data/EntityFrameworkCoreRepositoryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries; @@ -54,7 +55,7 @@ public async Task UpdateAsync_AttributesUpdated_ShouldHaveSpecificallyThoseAttri targetedFields.Setup(m => m.Relationships).Returns(new HashSet()); // Act - await repository.UpdateAsync(todoItemUpdates, databaseResource); + await repository.UpdateAsync(todoItemUpdates, databaseResource, CancellationToken.None); } // Assert - in different context @@ -70,7 +71,7 @@ public async Task UpdateAsync_AttributesUpdated_ShouldHaveSpecificallyThoseAttri Filter = new ComparisonExpression(ComparisonOperator.Equals, new ResourceFieldChainExpression(idAttribute), new LiteralConstantExpression(itemId.ToString())) - }); + }, CancellationToken.None); var fetchedTodo = resources.First(); Assert.NotNull(fetchedTodo); diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 2e68031414..faca3e6f78 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -193,56 +194,56 @@ public class GuidResource : Identifiable { } private sealed class IntResourceService : IResourceService { - public Task> GetAsync() => throw new NotImplementedException(); - public Task GetAsync(int id) => throw new NotImplementedException(); - public Task GetSecondaryAsync(int id, string relationshipName) => throw new NotImplementedException(); - public Task GetRelationshipAsync(int id, string relationshipName) => throw new NotImplementedException(); - public Task CreateAsync(IntResource resource) => throw new NotImplementedException(); - public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task UpdateAsync(int id, IntResource resource) => throw new NotImplementedException(); - public Task SetRelationshipAsync(int primaryId, string relationshipName, object secondaryResourceIds) => throw new NotImplementedException(); - public Task DeleteAsync(int id) => throw new NotImplementedException(); - public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds) => throw new NotImplementedException(); + public Task> GetAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetAsync(int id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetSecondaryAsync(int id, string relationshipName, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetRelationshipAsync(int id, string relationshipName, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CreateAsync(IntResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task UpdateAsync(int id, IntResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task SetRelationshipAsync(int primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task DeleteAsync(int id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); } private sealed class GuidResourceService : IResourceService { - public Task> GetAsync() => throw new NotImplementedException(); - public Task GetAsync(Guid id) => throw new NotImplementedException(); - public Task GetSecondaryAsync(Guid id, string relationshipName) => throw new NotImplementedException(); - public Task GetRelationshipAsync(Guid id, string relationshipName) => throw new NotImplementedException(); - public Task CreateAsync(GuidResource resource) => throw new NotImplementedException(); - public Task AddToToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task UpdateAsync(Guid id, GuidResource resource) => throw new NotImplementedException(); - public Task SetRelationshipAsync(Guid primaryId, string relationshipName, object secondaryResourceIds) => throw new NotImplementedException(); - public Task DeleteAsync(Guid id) => throw new NotImplementedException(); - public Task RemoveFromToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds) => throw new NotImplementedException(); + public Task> GetAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetAsync(Guid id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetSecondaryAsync(Guid id, string relationshipName, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetRelationshipAsync(Guid id, string relationshipName, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CreateAsync(GuidResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task AddToToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task UpdateAsync(Guid id, GuidResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task SetRelationshipAsync(Guid primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task DeleteAsync(Guid id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task RemoveFromToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); } private sealed class IntResourceRepository : IResourceRepository { - public Task> GetAsync(QueryLayer layer) => throw new NotImplementedException(); - public Task CountAsync(FilterExpression topFilter) => throw new NotImplementedException(); - public Task CreateAsync(IntResource resource) => throw new NotImplementedException(); - public Task AddToToManyRelationshipAsync(int primaryId, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task UpdateAsync(IntResource resourceFromRequest, IntResource resourceFromDatabase) => throw new NotImplementedException(); - public Task SetRelationshipAsync(IntResource primaryResource, object secondaryResourceIds) => throw new NotImplementedException(); - public Task DeleteAsync(int id) => throw new NotImplementedException(); - public Task RemoveFromToManyRelationshipAsync(IntResource primaryResource, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task GetForUpdateAsync(QueryLayer queryLayer) => throw new NotImplementedException(); + public Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CreateAsync(IntResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task AddToToManyRelationshipAsync(int primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task UpdateAsync(IntResource resourceFromRequest, IntResource resourceFromDatabase, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task SetRelationshipAsync(IntResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task DeleteAsync(int id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task RemoveFromToManyRelationshipAsync(IntResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) => throw new NotImplementedException(); } private sealed class GuidResourceRepository : IResourceRepository { - public Task> GetAsync(QueryLayer layer) => throw new NotImplementedException(); - public Task CountAsync(FilterExpression topFilter) => throw new NotImplementedException(); - public Task CreateAsync(GuidResource resource) => throw new NotImplementedException(); - public Task AddToToManyRelationshipAsync(Guid primaryId, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task UpdateAsync(GuidResource resourceFromRequest, GuidResource resourceFromDatabase) => throw new NotImplementedException(); - public Task SetRelationshipAsync(GuidResource primaryResource, object secondaryResourceIds) => throw new NotImplementedException(); - public Task DeleteAsync(Guid id) => throw new NotImplementedException(); - public Task RemoveFromToManyRelationshipAsync(GuidResource primaryResource, ISet secondaryResourceIds) => throw new NotImplementedException(); - public Task GetForUpdateAsync(QueryLayer queryLayer) => throw new NotImplementedException(); + public Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task CreateAsync(GuidResource resource, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task AddToToManyRelationshipAsync(Guid primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task UpdateAsync(GuidResource resourceFromRequest, GuidResource resourceFromDatabase, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task SetRelationshipAsync(GuidResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task DeleteAsync(Guid id, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task RemoveFromToManyRelationshipAsync(GuidResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) => throw new NotImplementedException(); } public class TestContext : DbContext