Skip to content

Commit a9c7ff9

Browse files
authored
Merge pull request #375 from json-api-dotnet/fix/#313
fix(#313): Do not return 409 for generic InvalidCastException
2 parents 8005d1a + ee7d069 commit a9c7ff9

File tree

3 files changed

+61
-20
lines changed

3 files changed

+61
-20
lines changed

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using JsonApiDotNetCore.Services.Operations.Processors;
1313
using Microsoft.AspNetCore.Http;
1414
using Microsoft.AspNetCore.Mvc;
15+
using Microsoft.AspNetCore.Mvc.Filters;
1516
using Microsoft.EntityFrameworkCore;
1617
using Microsoft.Extensions.DependencyInjection;
1718

@@ -44,12 +45,7 @@ public static IServiceCollection AddJsonApi<TContext>(this IServiceCollection se
4445

4546
config.BuildContextGraph(builder => builder.AddDbContext<TContext>());
4647

47-
mvcBuilder
48-
.AddMvcOptions(opt =>
49-
{
50-
opt.Filters.Add(typeof(JsonApiExceptionFilter));
51-
opt.SerializeAsJsonApi(config);
52-
});
48+
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config));
5349

5450
AddJsonApiInternals<TContext>(services, config);
5551
return services;
@@ -63,17 +59,19 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services,
6359

6460
options(config);
6561

66-
mvcBuilder
67-
.AddMvcOptions(opt =>
68-
{
69-
opt.Filters.Add(typeof(JsonApiExceptionFilter));
70-
opt.SerializeAsJsonApi(config);
71-
});
62+
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config));
7263

7364
AddJsonApiInternals(services, config);
7465
return services;
7566
}
7667

68+
private static void AddMvcOptions(MvcOptions options, JsonApiOptions config)
69+
{
70+
options.Filters.Add(typeof(JsonApiExceptionFilter));
71+
options.Filters.Add(typeof(TypeMatchFilter));
72+
options.SerializeAsJsonApi(config);
73+
}
74+
7775
public static void AddJsonApiInternals<TContext>(
7876
this IServiceCollection services,
7977
JsonApiOptions jsonApiOptions) where TContext : DbContext
@@ -141,6 +139,8 @@ public static void AddJsonApiInternals(
141139
services.AddScoped<IQueryParser, QueryParser>();
142140
services.AddScoped<IControllerContext, Services.ControllerContext>();
143141
services.AddScoped<IDocumentBuilderOptionsProvider, DocumentBuilderOptionsProvider>();
142+
143+
// services.AddScoped<IActionFilter, TypeMatchFilter>();
144144
}
145145

146146
private static void AddOperationServices(IServiceCollection services)

src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs

-8
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ public static JsonApiException GetException(Exception exception)
1111
if (exceptionType == typeof(JsonApiException))
1212
return (JsonApiException)exception;
1313

14-
// TODO: this is for mismatching type requests (e.g. posting an author to articles endpoint)
15-
// however, we can't actually guarantee that this is the source of this exception
16-
// we should probably use an action filter or when we improve the ContextGraph
17-
// we might be able to skip most of deserialization entirely by checking the JToken
18-
// directly
19-
if (exceptionType == typeof(InvalidCastException))
20-
return new JsonApiException(409, exception.Message, exception);
21-
2214
return new JsonApiException(500, exceptionType.Name, exception);
2315
}
2416
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Linq;
3+
using JsonApiDotNetCore.Internal;
4+
using JsonApiDotNetCore.Services;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Mvc.Filters;
7+
8+
namespace JsonApiDotNetCore.Middleware
9+
{
10+
public class TypeMatchFilter : IActionFilter
11+
{
12+
private readonly IJsonApiContext _jsonApiContext;
13+
14+
public TypeMatchFilter(IJsonApiContext jsonApiContext)
15+
{
16+
_jsonApiContext = jsonApiContext;
17+
}
18+
19+
/// <summary>
20+
/// Used to verify the incoming type matches the target type, else return a 409
21+
/// </summary>
22+
public void OnActionExecuting(ActionExecutingContext context)
23+
{
24+
var request = context.HttpContext.Request;
25+
if (IsJsonApiRequest(request) && request.Method == "PATCH" || request.Method == "POST")
26+
{
27+
var deserializedType = context.ActionArguments.FirstOrDefault().Value?.GetType();
28+
var targetType = context.ActionDescriptor.Parameters.FirstOrDefault()?.ParameterType;
29+
30+
if (deserializedType != null && targetType != null && deserializedType != targetType)
31+
{
32+
var expectedJsonApiResource = _jsonApiContext.ContextGraph.GetContextEntity(targetType);
33+
34+
throw new JsonApiException(409,
35+
$"Cannot '{context.HttpContext.Request.Method}' type '{_jsonApiContext.RequestEntity.EntityName}' "
36+
+ $"to '{expectedJsonApiResource?.EntityName}' endpoint.",
37+
detail: "Check that the request payload type matches the type expected by this endpoint.");
38+
}
39+
}
40+
}
41+
42+
private bool IsJsonApiRequest(HttpRequest request)
43+
{
44+
return (request.ContentType?.Equals(Constants.ContentType, StringComparison.OrdinalIgnoreCase) == true);
45+
}
46+
47+
public void OnActionExecuted(ActionExecutedContext context) { /* noop */ }
48+
}
49+
}

0 commit comments

Comments
 (0)