Skip to content

Commit f6c9fe0

Browse files
committed
Check if Model State is valid and throw a 400
1 parent 9013643 commit f6c9fe0

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Threading.Tasks;
3+
using JsonApiDotNetCore.Extensions;
34
using JsonApiDotNetCore.Internal;
45
using JsonApiDotNetCore.Models;
56
using JsonApiDotNetCore.Services;
@@ -152,6 +153,7 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
152153

153154
if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId))
154155
return Forbidden();
156+
if (!ModelState.IsValid) return BadRequest(ModelState.ConvertToErrorCollection());
155157

156158
entity = await _create.CreateAsync(entity);
157159

@@ -164,6 +166,7 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
164166

165167
if (entity == null)
166168
return UnprocessableEntity();
169+
if (!ModelState.IsValid) return BadRequest(ModelState.ConvertToErrorCollection());
167170

168171
var updatedEntity = await _update.UpdateAsync(id, entity);
169172

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using JsonApiDotNetCore.Internal;
2+
using Microsoft.AspNetCore.Mvc.ModelBinding;
3+
using Microsoft.EntityFrameworkCore.Internal;
4+
5+
namespace JsonApiDotNetCore.Extensions
6+
{
7+
public static class ModelStateExtensions
8+
{
9+
public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary modelState)
10+
{
11+
ErrorCollection errors = new ErrorCollection();
12+
foreach (var entry in modelState)
13+
{
14+
if (!entry.Value.Errors.Any()) continue;
15+
foreach (var modelError in entry.Value.Errors)
16+
{
17+
errors.Errors.Add(new Error(400, entry.Key, modelError.ErrorMessage, modelError.Exception != null ? ErrorMeta.FromException(modelError.Exception) : null));
18+
}
19+
}
20+
return errors;
21+
}
22+
}
23+
}

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>2.3.1</VersionPrefix>
3+
<VersionPrefix>2.3.2</VersionPrefix>
44
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>

test/UnitTests/Controllers/BaseJsonApiController_Tests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
using Moq;
66
using Xunit;
77
using System.Threading.Tasks;
8+
using JsonApiDotNetCore.Configuration;
89
using JsonApiDotNetCore.Internal;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.AspNetCore.Mvc;
912

1013
namespace UnitTests
1114
{
@@ -153,6 +156,25 @@ public async Task PatchAsync_Calls_Service()
153156
VerifyApplyContext();
154157
}
155158

159+
[Fact]
160+
public async Task PatchAsync_ModelStateInvalid()
161+
{
162+
// arrange
163+
const int id = 0;
164+
var resource = new Resource();
165+
var serviceMock = new Mock<IUpdateService<Resource>>();
166+
var controller = new BaseJsonApiController<Resource>(_jsonApiContextMock.Object, update: serviceMock.Object);
167+
controller.ModelState.AddModelError("Id", "Failed Validation");
168+
169+
// act
170+
var response = await controller.PatchAsync(id, resource);
171+
172+
// assert
173+
serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny<Resource>()), Times.Never);
174+
Assert.IsType<BadRequestObjectResult>(response);
175+
Assert.IsType<ErrorCollection>(((BadRequestObjectResult) response).Value);
176+
}
177+
156178
[Fact]
157179
public async Task PatchAsync_Throws_405_If_No_Service()
158180
{
@@ -168,6 +190,46 @@ public async Task PatchAsync_Throws_405_If_No_Service()
168190
Assert.Equal(405, exception.GetStatusCode());
169191
}
170192

193+
[Fact]
194+
public async Task PostAsync_Calls_Service()
195+
{
196+
// arrange
197+
var resource = new Resource();
198+
var serviceMock = new Mock<ICreateService<Resource>>();
199+
_jsonApiContextMock.Setup(a => a.ApplyContext<Resource>(It.IsAny<BaseJsonApiController<Resource>>())).Returns(_jsonApiContextMock.Object);
200+
_jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions());
201+
var controller = new BaseJsonApiController<Resource>(_jsonApiContextMock.Object, create: serviceMock.Object);
202+
serviceMock.Setup(m => m.CreateAsync(It.IsAny<Resource>())).ReturnsAsync(resource);
203+
controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()};
204+
205+
// act
206+
await controller.PostAsync(resource);
207+
208+
// assert
209+
serviceMock.Verify(m => m.CreateAsync(It.IsAny<Resource>()), Times.Once);
210+
VerifyApplyContext();
211+
}
212+
213+
[Fact]
214+
public async Task PostAsync_ModelStateInvalid()
215+
{
216+
// arrange
217+
var resource = new Resource();
218+
var serviceMock = new Mock<ICreateService<Resource>>();
219+
_jsonApiContextMock.Setup(a => a.ApplyContext<Resource>(It.IsAny<BaseJsonApiController<Resource>>())).Returns(_jsonApiContextMock.Object);
220+
_jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions());
221+
var controller = new BaseJsonApiController<Resource>(_jsonApiContextMock.Object, create: serviceMock.Object);
222+
controller.ModelState.AddModelError("Id", "Failed Validation");
223+
224+
// act
225+
var response = await controller.PostAsync(resource);
226+
227+
// assert
228+
serviceMock.Verify(m => m.CreateAsync(It.IsAny<Resource>()), Times.Never);
229+
Assert.IsType<BadRequestObjectResult>(response);
230+
Assert.IsType<ErrorCollection>(((BadRequestObjectResult)response).Value);
231+
}
232+
171233
[Fact]
172234
public async Task PatchRelationshipsAsync_Calls_Service()
173235
{

0 commit comments

Comments
 (0)