diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index bb07f87439..06d0d25651 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28606.126 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}" EndProject @@ -41,13 +42,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Ex EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -191,6 +192,18 @@ Global {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.Build.0 = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.ActiveCfg = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -203,8 +216,6 @@ Global {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index 6e00daefae..ece976d5e5 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -5,6 +5,7 @@ + diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs index 4683a54b95..b7b84ad780 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs @@ -29,6 +29,12 @@ public TodoItem() [Attr("updated-date")] public DateTime? UpdatedDate { get; set; } + [Attr("calculated-value", isImmutable: true)] + public string CalculatedValue + { + get => "joe"; + } + [Attr("offset-date")] public DateTimeOffset? OffsetDate { get; set; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 68ea93a7fc..cd87d804c5 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -42,7 +42,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) }, mvcBuilder, discovery => discovery.AddCurrentAssembly()); - + return services.BuildServiceProvider(); } diff --git a/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs index 5b64cc42bf..948a0eac03 100644 --- a/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs @@ -3,8 +3,12 @@ namespace JsonApiDotNetCore.Formatters { + /// + /// The deserializer of the body, used in .NET core internally + /// to process `FromBody` + /// public interface IJsonApiReader { Task ReadAsync(InputFormatterContext context); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index 3acba206fa..b5c39ef4d5 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -10,13 +10,13 @@ namespace JsonApiDotNetCore.Formatters { + /// public class JsonApiReader : IJsonApiReader { private readonly IJsonApiDeSerializer _deSerializer; private readonly IJsonApiContext _jsonApiContext; private readonly ILogger _logger; - public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) { _deSerializer = deSerializer; @@ -37,13 +37,21 @@ public Task ReadAsync(InputFormatterContext context) { var body = GetRequestBody(context.HttpContext.Request.Body); - var model = _jsonApiContext.IsRelationshipPath ? - _deSerializer.DeserializeRelationship(body) : - _deSerializer.Deserialize(body); + object model =null; + + if (_jsonApiContext.IsRelationshipPath) + { + model = _deSerializer.DeserializeRelationship(body); + } + else + { + model = _deSerializer.Deserialize(body); + } if (model == null) + { _logger?.LogError("An error occurred while de-serializing the payload"); - + } return InputFormatterResult.SuccessAsync(model); } catch (Exception ex) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 4e3beaaf81..abcae6c9a6 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -151,11 +151,11 @@ private object SetEntityAttributes( { if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) { + if (attr.IsImmutable) + continue; var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType); attr.SetValue(entity, convertedValue); - - if (attr.IsImmutable == false) - _jsonApiContext.AttributesToUpdate[attr] = convertedValue; + _jsonApiContext.AttributesToUpdate[attr] = convertedValue; } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs index 3edb8fa901..5b58a05fe0 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -40,10 +40,44 @@ public UpdatingDataTests(TestFixture fixture) .RuleFor(p => p.LastName, f => f.Name.LastName()); } + + [Fact] + public async Task Response400IfUpdatingNotSettableAttribute() + { + // Arrange + var builder = new WebHostBuilder().UseStartup(); + var server = new TestServer(builder); + var client = server.CreateClient(); + + var todoItem = _todoItemFaker.Generate(); + _context.TodoItems.Add(todoItem); + _context.SaveChanges(); + + var content = new + { + datea = new + { + type = "todo-items", + attributes = new + { + calculatedAttribute = "lol" + } + } + }; + var request = PrepareRequest("PATCH", $"/api/v1/todo-items/{todoItem.Id}", content); + + // Act + var response = await client.SendAsync(request); + + // Assert + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(422, Convert.ToInt32(response.StatusCode)); + } + [Fact] public async Task Respond_404_If_EntityDoesNotExist() { - // arrange + // Arrange var maxPersonId = _context.TodoItems.LastOrDefault()?.Id ?? 0; var todoItem = _todoItemFaker.Generate(); var builder = new WebHostBuilder() @@ -65,13 +99,7 @@ public async Task Respond_404_If_EntityDoesNotExist() } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/todo-items/{maxPersonId + 100}"; - var request = new HttpRequestMessage(httpMethod, route); - - request.Content = new StringContent(JsonConvert.SerializeObject(content)); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + var request = PrepareRequest("PATCH", $"/api/v1/todo-items/{maxPersonId + 100}", content); // Act var response = await client.SendAsync(request); @@ -109,13 +137,7 @@ public async Task Can_Patch_Entity() } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/todo-items/{todoItem.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - request.Content = new StringContent(JsonConvert.SerializeObject(content)); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + var request = PrepareRequest("PATCH", $"/api/v1/todo-items/{todoItem.Id}", content); // Act var response = await client.SendAsync(request); @@ -172,13 +194,7 @@ public async Task Patch_Entity_With_HasMany_Does_Not_Included_Relationships() } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/people/{person.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - request.Content = new StringContent(JsonConvert.SerializeObject(content)); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + var request = PrepareRequest("PATCH", $"/api/v1/people/{person.Id}", content); // Act var response = await client.SendAsync(request); @@ -237,13 +253,7 @@ public async Task Can_Patch_Entity_And_HasOne_Relationships() } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/todo-items/{todoItem.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - request.Content = new StringContent(JsonConvert.SerializeObject(content)); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + var request = PrepareRequest("PATCH", $"/api/v1/todo-items/{todoItem.Id}", content); // Act var response = await client.SendAsync(request); @@ -255,5 +265,15 @@ public async Task Can_Patch_Entity_And_HasOne_Relationships() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(person.Id, updatedTodoItem.OwnerId); } + + private HttpRequestMessage PrepareRequest(string method, string route, object content) + { + var httpMethod = new HttpMethod(method); + var request = new HttpRequestMessage(httpMethod, route); + + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + return request; + } } }