Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a9c7ff9

Browse files
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

Lines changed: 12 additions & 12 deletions
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

Lines changed: 0 additions & 8 deletions
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
}
Lines changed: 49 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /