diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 0beb0516c1..c09cbefef3 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -68,21 +68,20 @@ public Documents Build(IEnumerable entities) private Dictionary GetMeta(IIdentifiable entity) { - if (entity == null) return null; - var builder = _jsonApiContext.MetaBuilder; - - if (entity is IHasMeta metaEntity) - builder.Add(metaEntity.GetMeta(_jsonApiContext)); - - if (_jsonApiContext.Options.IncludeTotalRecordCount) + if (_jsonApiContext.Options.IncludeTotalRecordCount && _jsonApiContext.PageManager.TotalRecords != null) builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); if (_requestMeta != null) builder.Add(_requestMeta.GetMeta()); + if (entity != null && entity is IHasMeta metaEntity) + builder.Add(metaEntity.GetMeta(_jsonApiContext)); + var meta = builder.Build(); - if (meta.Count > 0) return meta; + if (meta.Count > 0) + return meta; + return null; } diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index 17da00333a..d27fc158fd 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -6,12 +6,12 @@ namespace JsonApiDotNetCore.Internal { public class PageManager { - public int TotalRecords { get; set; } + public int? TotalRecords { get; set; } public int PageSize { get; set; } public int DefaultPageSize { get; set; } public int CurrentPage { get; set; } public bool IsPaginated => PageSize > 0; - public int TotalPages => (TotalRecords == 0) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords, PageSize)); + public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); public RootLinks GetPageLinks(LinkBuilder linkBuilder) { diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index e9d5eea415..7019a8e6cc 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,6 +1,6 @@  - 2.3.0 + 2.3.1 $(NetStandardVersion) JsonApiDotNetCore JsonApiDotNetCore diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 0643d494d6..21c4b56736 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -86,20 +86,20 @@ internal static bool PathIsRelationship(string requestPath) const char pathSegmentDelimiter = '/'; var span = requestPath.AsSpan(); - + // we need to iterate over the string, from the end, // checking whether or not the 2nd to last path segment // is "relationships" // -2 is chosen in case the path ends with '/' - for(var i = requestPath.Length - 2; i >= 0; i--) + for (var i = requestPath.Length - 2; i >= 0; i--) { // if there are not enough characters left in the path to // contain "relationships" - if(i < relationships.Length) + if (i < relationships.Length) return false; // we have found the first instance of '/' - if(span[i] == pathSegmentDelimiter) + if (span[i] == pathSegmentDelimiter) { // in the case of a "relationships" route, the next // path segment will be "relationships" @@ -112,7 +112,7 @@ internal static bool PathIsRelationship(string requestPath) return false; } - + private PageManager GetPageManager() { if (Options.DefaultPageSize == 0 && (QuerySet == null || QuerySet.PageQuery.PageSize == 0)) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs index bb055e9935..2b6b1e251b 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using JsonApiDotNetCore.Models; using JsonApiDotNetCoreExample; @@ -47,13 +48,123 @@ public async Task Total_Record_Count_Included() var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); var documents = JsonConvert.DeserializeObject(responseBody); - + // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(documents.Meta); Assert.Equal((long)expectedCount, (long)documents.Meta["total-records"]); } + [Fact] + public async Task Total_Record_Count_Included_When_None() + { + // arrange + _context.TodoItems.RemoveRange(_context.TodoItems); + _context.SaveChanges(); + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("GET"); + var route = $"/api/v1/todo-items"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + + // act + var response = await client.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + var documents = JsonConvert.DeserializeObject(responseBody); + + // assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(documents.Meta); + Assert.Equal(0, (long)documents.Meta["total-records"]); + } + + [Fact] + public async Task Total_Record_Count_Not_Included_In_POST_Response() + { + // arrange + _context.TodoItems.RemoveRange(_context.TodoItems); + _context.SaveChanges(); + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("POST"); + var route = $"/api/v1/todo-items"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + var content = new + { + data = new + { + type = "todo-items", + attributes = new + { + description = "New Description", + } + } + }; + + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + + // act + var response = await client.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + var documents = JsonConvert.DeserializeObject(responseBody); + + // assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.False(documents.Meta.ContainsKey("total-records")); + } + + [Fact] + public async Task Total_Record_Count_Not_Included_In_PATCH_Response() + { + // arrange + _context.TodoItems.RemoveRange(_context.TodoItems); + TodoItem todoItem = new TodoItem(); + _context.TodoItems.Add(todoItem); + _context.SaveChanges(); + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("PATCH"); + var route = $"/api/v1/todo-items/{todoItem.Id}"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + var content = new + { + data = new + { + type = "todo-items", + id = todoItem.Id, + attributes = new + { + description = "New Description", + } + } + }; + + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + + // act + var response = await client.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + var documents = JsonConvert.DeserializeObject(responseBody); + + // assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.False(documents.Meta.ContainsKey("total-records")); + } + [Fact] public async Task EntityThatImplements_IHasMeta_Contains_MetaData() { @@ -73,26 +184,26 @@ public async Task EntityThatImplements_IHasMeta_Contains_MetaData() // act var response = await client.SendAsync(request); var documents = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - + // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(documents.Meta); Assert.NotNull(expectedMeta); Assert.NotEmpty(expectedMeta); - - foreach(var hash in expectedMeta) + + foreach (var hash in expectedMeta) { - if(hash.Value is IList) + if (hash.Value is IList) { var listValue = (IList)hash.Value; - for(var i=0; i < listValue.Count; i++) + for (var i = 0; i < listValue.Count; i++) Assert.Equal(listValue[i].ToString(), ((IList)documents.Meta[hash.Key])[i].ToString()); } else { Assert.Equal(hash.Value, documents.Meta[hash.Key]); } - } + } } } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs index 8573d0b560..9c8d5f8214 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs @@ -53,8 +53,10 @@ public async Task Request_ForEmptyCollection_Returns_EmptyDataCollection() var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(httpMethod, route); - var expectedBody = JsonConvert.SerializeObject(new { - data = new List() + var expectedBody = JsonConvert.SerializeObject(new + { + data = new List(), + meta = new Dictionary { { "total-records", 0 } } }); // act