Skip to content

Commit 4262410

Browse files
author
Bart Koelman
committed
Changed filters and formatters to async
1 parent 234cc86 commit 4262410

20 files changed

+204
-207
lines changed

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,18 @@ public void AddResourceGraph(Type dbContextType, Action<ResourceGraphBuilder> co
7979
/// <summary>
8080
/// Configures built-in .NET Core MVC (things like middleware, routing). Most of this configuration can be adjusted for the developers' need.
8181
/// Before calling .AddJsonApi(), a developer can register their own implementation of the following services to customize startup:
82-
/// <see cref="ResourceGraphBuilder"/>, <see cref="ServiceDiscoveryFacade"/>, <see cref="IJsonApiTypeMatchFilter"/>,
83-
/// <see cref="IJsonApiExceptionFilter"/> and <see cref="IJsonApiRoutingConvention"/>.
82+
/// <see cref="ResourceGraphBuilder"/>, <see cref="ServiceDiscoveryFacade"/>, <see cref="IAsyncResourceTypeMatchFilter"/>,
83+
/// <see cref="IAsyncJsonApiExceptionFilter"/> and <see cref="IJsonApiRoutingConvention"/>.
8484
/// </summary>
8585
public void ConfigureMvc()
8686
{
8787
_mvcBuilder.AddMvcOptions(options =>
8888
{
8989
options.EnableEndpointRouting = true;
90-
options.Filters.AddService<IJsonApiExceptionFilter>();
91-
options.Filters.AddService<IJsonApiTypeMatchFilter>();
92-
options.Filters.AddService<IQueryStringActionFilter>();
93-
options.Filters.AddService<IConvertEmptyActionResultFilter>();
90+
options.Filters.AddService<IAsyncJsonApiExceptionFilter>();
91+
options.Filters.AddService<IAsyncResourceTypeMatchFilter>();
92+
options.Filters.AddService<IAsyncQueryStringActionFilter>();
93+
options.Filters.AddService<IAsyncConvertEmptyActionResultFilter>();
9494
ConfigureMvcOptions?.Invoke(options);
9595
});
9696

@@ -154,10 +154,10 @@ private void AddMiddlewareLayer()
154154
_services.AddSingleton<IJsonApiOptions>(_options);
155155
_services.AddSingleton<IJsonApiApplicationBuilder>(this);
156156
_services.TryAddSingleton<IExceptionHandler, ExceptionHandler>();
157-
_services.TryAddScoped<IJsonApiExceptionFilter, JsonApiExceptionFilter>();
158-
_services.TryAddScoped<IJsonApiTypeMatchFilter, JsonApiTypeMatchFilter>();
159-
_services.TryAddScoped<IQueryStringActionFilter, QueryStringActionFilter>();
160-
_services.TryAddScoped<IConvertEmptyActionResultFilter, ConvertEmptyActionResultFilter>();
157+
_services.TryAddScoped<IAsyncJsonApiExceptionFilter, AsyncJsonApiExceptionFilter>();
158+
_services.TryAddScoped<IAsyncResourceTypeMatchFilter, AsyncResourceTypeMatchFilter>();
159+
_services.TryAddScoped<IAsyncQueryStringActionFilter, AsyncQueryStringActionFilter>();
160+
_services.TryAddScoped<IAsyncConvertEmptyActionResultFilter, AsyncConvertEmptyActionResultFilter>();
161161
_services.TryAddSingleton<IJsonApiInputFormatter, JsonApiInputFormatter>();
162162
_services.TryAddSingleton<IJsonApiOutputFormatter, JsonApiOutputFormatter>();
163163
_services.TryAddSingleton<IJsonApiRoutingConvention, JsonApiRoutingConvention>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.AspNetCore.Mvc.Filters;
5+
using Microsoft.AspNetCore.Mvc.Infrastructure;
6+
7+
namespace JsonApiDotNetCore.Middleware
8+
{
9+
/// <inheritdoc />
10+
public sealed class AsyncConvertEmptyActionResultFilter : IAsyncConvertEmptyActionResultFilter
11+
{
12+
/// <inheritdoc />
13+
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
14+
{
15+
if (context == null) throw new ArgumentNullException(nameof(context));
16+
if (next == null) throw new ArgumentNullException(nameof(next));
17+
18+
if (context.HttpContext.IsJsonApiRequest())
19+
{
20+
if (!(context.Result is ObjectResult objectResult) || objectResult.Value == null)
21+
{
22+
if (context.Result is IStatusCodeActionResult statusCodeResult)
23+
{
24+
context.Result = new ObjectResult(null) {StatusCode = statusCodeResult.StatusCode};
25+
}
26+
}
27+
}
28+
29+
await next();
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.AspNetCore.Mvc.Filters;
5+
6+
namespace JsonApiDotNetCore.Middleware
7+
{
8+
/// <inheritdoc />
9+
public class AsyncJsonApiExceptionFilter : IAsyncJsonApiExceptionFilter
10+
{
11+
private readonly IExceptionHandler _exceptionHandler;
12+
13+
public AsyncJsonApiExceptionFilter(IExceptionHandler exceptionHandler)
14+
{
15+
_exceptionHandler = exceptionHandler ?? throw new ArgumentNullException(nameof(exceptionHandler));
16+
}
17+
18+
/// <inheritdoc />
19+
public Task OnExceptionAsync(ExceptionContext context)
20+
{
21+
if (context == null) throw new ArgumentNullException(nameof(context));
22+
23+
if (context.HttpContext.IsJsonApiRequest())
24+
{
25+
var errorDocument = _exceptionHandler.HandleException(context.Exception);
26+
27+
context.Result = new ObjectResult(errorDocument)
28+
{
29+
StatusCode = (int) errorDocument.GetErrorStatusCode()
30+
};
31+
}
32+
33+
return Task.CompletedTask;
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Controllers.Annotations;
5+
using JsonApiDotNetCore.QueryStrings;
6+
using Microsoft.AspNetCore.Mvc.Filters;
7+
8+
namespace JsonApiDotNetCore.Middleware
9+
{
10+
/// <inheritdoc />
11+
public sealed class AsyncQueryStringActionFilter : IAsyncQueryStringActionFilter
12+
{
13+
private readonly IQueryStringReader _queryStringReader;
14+
15+
public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader)
16+
{
17+
_queryStringReader = queryStringReader ?? throw new ArgumentNullException(nameof(queryStringReader));
18+
}
19+
20+
/// <inheritdoc />
21+
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
22+
{
23+
if (context == null) throw new ArgumentNullException(nameof(context));
24+
if (next == null) throw new ArgumentNullException(nameof(next));
25+
26+
if (context.HttpContext.IsJsonApiRequest())
27+
{
28+
var disableQueryStringAttribute = context.Controller.GetType().GetCustomAttribute<DisableQueryStringAttribute>();
29+
_queryStringReader.ReadAll(disableQueryStringAttribute);
30+
}
31+
32+
await next();
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Errors;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Mvc.Filters;
9+
10+
namespace JsonApiDotNetCore.Middleware
11+
{
12+
/// <inheritdoc />
13+
public sealed class AsyncResourceTypeMatchFilter : IAsyncResourceTypeMatchFilter
14+
{
15+
private readonly IResourceContextProvider _provider;
16+
17+
public AsyncResourceTypeMatchFilter(IResourceContextProvider provider)
18+
{
19+
_provider = provider;
20+
}
21+
22+
/// <inheritdoc />
23+
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
24+
{
25+
if (context == null) throw new ArgumentNullException(nameof(context));
26+
if (next == null) throw new ArgumentNullException(nameof(next));
27+
28+
if (context.HttpContext.IsJsonApiRequest() && IsPatchOrPostRequest(context.HttpContext.Request))
29+
{
30+
var deserializedType = context.ActionArguments.FirstOrDefault().Value?.GetType();
31+
var targetType = context.ActionDescriptor.Parameters.FirstOrDefault()?.ParameterType;
32+
33+
if (deserializedType != null && targetType != null && deserializedType != targetType)
34+
{
35+
var resourceFromEndpoint = _provider.GetResourceContext(targetType);
36+
var resourceFromBody = _provider.GetResourceContext(deserializedType);
37+
38+
throw new ResourceTypeMismatchException(new HttpMethod(context.HttpContext.Request.Method), context.HttpContext.Request.Path,
39+
resourceFromEndpoint, resourceFromBody);
40+
}
41+
}
42+
43+
await next();
44+
}
45+
46+
private static bool IsPatchOrPostRequest(HttpRequest request)
47+
{
48+
return request.Method == "PATCH" || request.Method == "POST";
49+
}
50+
}
51+
}

src/JsonApiDotNetCore/Middleware/ConvertEmptyActionResultFilter.cs

-35
This file was deleted.

src/JsonApiDotNetCore/Middleware/IConvertEmptyActionResultFilter.cs renamed to src/JsonApiDotNetCore/Middleware/IAsyncConvertEmptyActionResultFilter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ namespace JsonApiDotNetCore.Middleware
88
/// This ensures our formatter is invoked, where we'll build a json:api compliant response.
99
/// For details, see: https://github.com/dotnet/aspnetcore/issues/16969
1010
/// </summary>
11-
public interface IConvertEmptyActionResultFilter : IAlwaysRunResultFilter { }
11+
public interface IAsyncConvertEmptyActionResultFilter : IAsyncAlwaysRunResultFilter { }
1212
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Mvc.Filters;
2+
3+
namespace JsonApiDotNetCore.Middleware
4+
{
5+
/// <summary>
6+
/// Application-wide exception filter that invokes <see cref="IExceptionHandler"/> for json:api requests.
7+
/// </summary>
8+
public interface IAsyncJsonApiExceptionFilter : IAsyncExceptionFilter { }
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Mvc.Filters;
2+
3+
namespace JsonApiDotNetCore.Middleware
4+
{
5+
/// <summary>
6+
/// Application-wide entry point for processing json:api request query strings.
7+
/// </summary>
8+
public interface IAsyncQueryStringActionFilter : IAsyncActionFilter { }
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Mvc.Filters;
2+
3+
namespace JsonApiDotNetCore.Middleware
4+
{
5+
/// <summary>
6+
/// Verifies the incoming resource type in json:api request body matches the resource type at the current endpoint URL.
7+
/// </summary>
8+
public interface IAsyncResourceTypeMatchFilter : IAsyncActionFilter { }
9+
}

src/JsonApiDotNetCore/Middleware/IJsonApiExceptionFilter.cs

-9
This file was deleted.

src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs

+3
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22

33
namespace JsonApiDotNetCore.Middleware
44
{
5+
/// <summary>
6+
/// Application-wide entry point for reading json:api request bodies.
7+
/// </summary>
58
public interface IJsonApiInputFormatter : IInputFormatter { }
69
}

src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs

+3
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22

33
namespace JsonApiDotNetCore.Middleware
44
{
5+
/// <summary>
6+
/// Application-wide entry point for writing json:api response bodies.
7+
/// </summary>
58
public interface IJsonApiOutputFormatter : IOutputFormatter { }
69
}

src/JsonApiDotNetCore/Middleware/IJsonApiTypeMatchFilter.cs

-9
This file was deleted.

src/JsonApiDotNetCore/Middleware/IQueryStringActionFilter.cs

-9
This file was deleted.

src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs

-39
This file was deleted.

src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66

77
namespace JsonApiDotNetCore.Middleware
88
{
9-
/// <summary>
10-
/// Extensibility point for reading incoming HTTP request.
11-
/// </summary>
9+
/// <inheritdoc />
1210
public sealed class JsonApiInputFormatter : IJsonApiInputFormatter
1311
{
12+
/// <inheritdoc />
1413
public bool CanRead(InputFormatterContext context)
1514
{
1615
if (context == null) throw new ArgumentNullException(nameof(context));
1716

1817
return context.HttpContext.IsJsonApiRequest();
1918
}
2019

20+
/// <inheritdoc />
2121
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
2222
{
2323
if (context == null) throw new ArgumentNullException(nameof(context));

0 commit comments

Comments
 (0)