Skip to content

Commit baa77ee

Browse files
committed
feat: remove GetEntityFromControllerName from ResourceGraph
1 parent e2c5be0 commit baa77ee

File tree

8 files changed

+72
-54
lines changed

8 files changed

+72
-54
lines changed

src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs

Lines changed: 13 additions & 6 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.Configuration;
34
using JsonApiDotNetCore.Controllers;
45
using JsonApiDotNetCore.Models;
56
using JsonApiDotNetCore.Services;
@@ -13,26 +14,30 @@ namespace JsonApiDotNetCoreExample.Controllers
1314
public class TodoItemsCustomController : CustomJsonApiController<TodoItem>
1415
{
1516
public TodoItemsCustomController(
17+
IJsonApiOptions options,
1618
IResourceService<TodoItem> resourceService,
1719
ILoggerFactory loggerFactory)
18-
: base(resourceService, loggerFactory)
20+
: base(options, resourceService, loggerFactory)
1921
{ }
2022
}
2123

2224
public class CustomJsonApiController<T>
2325
: CustomJsonApiController<T, int> where T : class, IIdentifiable<int>
2426
{
2527
public CustomJsonApiController(
28+
IJsonApiOptions options,
2629
IResourceService<T, int> resourceService,
2730
ILoggerFactory loggerFactory)
28-
: base(resourceService, loggerFactory)
29-
{ }
31+
: base(options, resourceService, loggerFactory)
32+
{
33+
}
3034
}
3135

3236
public class CustomJsonApiController<T, TId>
3337
: ControllerBase where T : class, IIdentifiable<TId>
3438
{
3539
private readonly ILogger _logger;
40+
private readonly IJsonApiOptions _options;
3641
private readonly IResourceService<T, TId> _resourceService;
3742

3843
protected IActionResult Forbidden()
@@ -41,11 +46,13 @@ protected IActionResult Forbidden()
4146
}
4247

4348
public CustomJsonApiController(
49+
IJsonApiOptions options,
4450
IResourceService<T, TId> resourceService,
4551
ILoggerFactory loggerFactory)
4652
{
53+
_options = options;
4754
_resourceService = resourceService;
48-
_logger = loggerFactory.CreateLogger<JsonApiDotNetCore.Controllers.JsonApiController<T, TId>>();
55+
_logger = loggerFactory.CreateLogger<JsonApiController<T, TId>>();
4956
}
5057

5158
public CustomJsonApiController(
@@ -95,8 +102,8 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
95102
if (entity == null)
96103
return UnprocessableEntity();
97104

98-
//if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId))
99-
// return Forbidden();
105+
if (_options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId))
106+
return Forbidden();
100107

101108
entity = await _resourceService.CreateAsync(entity);
102109

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ private static void ConfigureMvc(IServiceCollection services, IMvcCoreBuilder mv
121121
// register services that allow user to override behaviour that is configured on startup, like routing conventions
122122
AddStartupConfigurationServices(services, options);
123123
var intermediateProvider = services.BuildServiceProvider();
124-
mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, intermediateProvider.GetRequiredService<IJsonApiRoutingConvention>()));
124+
var routingConvention = intermediateProvider.GetRequiredService<IJsonApiRoutingConvention>();
125+
mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, routingConvention));
126+
services.AddSingleton(routingConvention);
125127
}
126128

127129
private static void AddMvcOptions(MvcOptions options, JsonApiOptions config)

src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,5 @@ public interface IResourceGraph : IContextEntityProvider
1616
/// Was built against an EntityFrameworkCore DbContext ?
1717
/// </summary>
1818
bool UsesDbContext { get; }
19-
20-
ContextEntity GetEntityFromControllerName(string pathParsed);
2119
}
2220
}

src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
using System.Reflection;
77
using JsonApiDotNetCore.Configuration;
88
using JsonApiDotNetCore.Controllers;
9+
using JsonApiDotNetCore.Extensions;
910
using JsonApiDotNetCore.Graph;
11+
using JsonApiDotNetCore.Models;
12+
using Microsoft.AspNetCore.Mvc;
1013
using Microsoft.AspNetCore.Mvc.ApplicationModels;
1114

1215
namespace JsonApiDotNetCore.Internal
@@ -37,17 +40,29 @@ public class DefaultRoutingConvention : IJsonApiRoutingConvention
3740
private readonly string _namespace;
3841
private readonly IResourceNameFormatter _formatter;
3942
private readonly HashSet<string> _registeredTemplates = new HashSet<string>();
43+
private readonly Dictionary<string, Type> _registeredResources = new Dictionary<string, Type>();
4044
public DefaultRoutingConvention(IJsonApiOptions options, IResourceNameFormatter formatter)
4145
{
4246
_namespace = options.Namespace;
4347
_formatter = formatter;
4448
}
4549

50+
/// <inheritdoc/>
51+
public Type GetAssociatedResource(string controllerName)
52+
{
53+
_registeredResources.TryGetValue(controllerName, out Type type);
54+
return type;
55+
}
56+
4657
/// <inheritdoc/>
4758
public void Apply(ApplicationModel application)
4859
{
4960
foreach (var controller in application.Controllers)
5061
{
62+
var resourceType = GetResourceTypeFromController(controller.ControllerType);
63+
if (resourceType != null)
64+
_registeredResources.Add(controller.ControllerName, resourceType);
65+
5166
if (RoutingConventionDisabled(controller) == false)
5267
continue;
5368

@@ -74,12 +89,12 @@ private bool RoutingConventionDisabled(ControllerModel controller)
7489
/// </summary>
7590
private string TemplateFromResource(ControllerModel model)
7691
{
77-
var resourceType = GetResourceTypeFromController(model.ControllerType);
78-
if (resourceType != null)
92+
if (_registeredResources.TryGetValue(model.ControllerName, out Type resourceType))
7993
{
8094
var template = $"{_namespace}/{_formatter.FormatResourceName(resourceType)}";
81-
if (_registeredTemplates.Add(template))
95+
if (_registeredTemplates.Add(template))
8296
return template;
97+
8398
}
8499
return null;
85100
}
@@ -100,12 +115,30 @@ private string TemplateFromController(ControllerModel model)
100115
/// </summary>
101116
private Type GetResourceTypeFromController(Type type)
102117
{
118+
var controllerBase = typeof(ControllerBase);
119+
var jsonApiMixin = typeof(JsonApiControllerMixin);
103120
var target = typeof(BaseJsonApiController<,>);
104-
var currentBaseType = type.BaseType;
121+
var identifiable = typeof(IIdentifiable);
122+
var currentBaseType = type;
123+
if (type.Name.Contains("TodoItemsCustom"))
124+
{
125+
var x = 123;
126+
}
127+
105128
while (!currentBaseType.IsGenericType || currentBaseType.GetGenericTypeDefinition() != target)
106129
{
107-
currentBaseType = currentBaseType.BaseType;
108-
if (currentBaseType == null) break;
130+
var nextBaseType = currentBaseType.BaseType;
131+
132+
if ( (nextBaseType == controllerBase || nextBaseType == jsonApiMixin) && currentBaseType.IsGenericType)
133+
{
134+
var potentialResource = currentBaseType.GetGenericArguments().FirstOrDefault(t => t.Inherits(identifiable));
135+
if (potentialResource != null)
136+
return potentialResource;
137+
}
138+
139+
currentBaseType = nextBaseType;
140+
if (nextBaseType == null)
141+
break;
109142
}
110143
return currentBaseType?.GetGenericArguments().First();
111144
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
using Microsoft.AspNetCore.Mvc.ApplicationModels;
1+
using System;
2+
using Microsoft.AspNetCore.Mvc.ApplicationModels;
23

34
namespace JsonApiDotNetCore.Internal
45
{
56
/// <summary>
67
/// Service for specifying which routing convention to use. This can be overriden to customize
78
/// the relation between controllers and mapped routes.
89
/// </summary>
9-
public interface IJsonApiRoutingConvention : IApplicationModelConvention { }
10+
public interface IJsonApiRoutingConvention : IApplicationModelConvention
11+
{
12+
Type GetAssociatedResource(string controllerName);
13+
}
1014
}

src/JsonApiDotNetCore/Internal/ResourceGraph.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ public ResourceGraph(List<ContextEntity> entities, bool usesDbContext)
3535
Instance = this;
3636
}
3737

38-
// eventually, this is the planned public constructor
39-
// to avoid breaking changes, we will be leaving the original constructor in place
40-
// until the context graph validation process is completed
41-
// you can track progress on this issue here: https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170
4238
internal ResourceGraph(List<ContextEntity> entities, bool usesDbContext, List<ValidationResult> validationResults, List<ControllerResourceMap> controllerContexts)
4339
{
4440
ControllerResourceMap = controllerContexts;
@@ -57,23 +53,6 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati
5753
return GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation);
5854
}
5955

60-
public ContextEntity GetEntityFromControllerName(string controllerName)
61-
{
62-
63-
if (ControllerResourceMap.Any())
64-
{
65-
// Autodiscovery was used, so there is a well defined mapping between exposed resources and their associated controllers
66-
var resourceType = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource;
67-
if (resourceType == null) return null;
68-
return Entities.First(e => e.EntityType == resourceType);
69-
70-
} else
71-
{
72-
// No autodiscovery: try to guess contextentity from controller name.
73-
return Entities.FirstOrDefault(e => e.EntityName.ToLower().Replace("-", "") == controllerName.ToLower());
74-
}
75-
}
76-
7756
/// <inheritdoc />
7857
public ContextEntity GetContextEntity(string entityName)
7958
=> Entities.SingleOrDefault(e => string.Equals(e.EntityName, entityName, StringComparison.OrdinalIgnoreCase));

src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,25 @@ public class CurrentRequestMiddleware
2121
private readonly RequestDelegate _next;
2222
private HttpContext _httpContext;
2323
private ICurrentRequest _currentRequest;
24-
private IResourceGraph _resourceGraph;
24+
private IContextEntityProvider _contextEntityProvider;
2525
private IJsonApiOptions _options;
26+
private IJsonApiRoutingConvention _routingConvention;
2627

2728
public CurrentRequestMiddleware(RequestDelegate next)
2829
{
2930
_next = next;
3031
}
3132

3233
public async Task Invoke(HttpContext httpContext,
34+
IJsonApiRoutingConvention routingConvention,
3335
IJsonApiOptions options,
3436
ICurrentRequest currentRequest,
35-
IResourceGraph resourceGraph)
37+
IContextEntityProvider contextEntityProvider)
3638
{
3739
_httpContext = httpContext;
3840
_currentRequest = currentRequest;
39-
_resourceGraph = resourceGraph;
41+
_routingConvention = routingConvention;
42+
_contextEntityProvider = contextEntityProvider;
4043
_options = options;
4144
var requestResource = GetCurrentEntity();
4245
if (requestResource != null)
@@ -60,10 +63,7 @@ private string GetBasePath(string entityName)
6063
{
6164
return GetNamespaceFromPath(r.Path, entityName);
6265
}
63-
else
64-
{
65-
return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
66-
}
66+
return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
6767
}
6868
internal static string GetNamespaceFromPath(string path, string entityName)
6969
{
@@ -162,15 +162,15 @@ private void FlushResponse(HttpContext context, int statusCode)
162162
/// <summary>
163163
/// Gets the current entity that we need for serialization and deserialization.
164164
/// </summary>
165-
/// <param name="context"></param>
166-
/// <param name="resourceGraph"></param>
167165
/// <returns></returns>
168166
private ContextEntity GetCurrentEntity()
169167
{
170168
var controllerName = (string)_httpContext.GetRouteData().Values["controller"];
169+
var resourceType = _routingConvention.GetAssociatedResource(controllerName);
170+
var requestResource = _contextEntityProvider.GetContextEntity(resourceType);
171+
if (requestResource == null)
172+
return requestResource;
171173
var rd = _httpContext.GetRouteData().Values;
172-
var requestResource = _resourceGraph.GetEntityFromControllerName(controllerName);
173-
174174
if (rd.TryGetValue("relationshipName", out object relationshipName))
175175
_currentRequest.RequestRelationship = requestResource.Relationships.Single(r => r.PublicRelationshipName == (string)relationshipName);
176176
return requestResource;

test/UnitTests/Controllers/BaseJsonApiController_Tests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,6 @@ public async Task PatchAsync_Calls_Service()
146146
const int id = 0;
147147
var resource = new Resource();
148148
var serviceMock = new Mock<IUpdateService<Resource>>();
149-
//_resourceGraph.Setup(a => a.ApplyContext<Resource>(It.IsAny<BaseJsonApiController<Resource>>())).Returns(_resourceGraph.Object);
150-
151149

152150
var controller = new BaseJsonApiController<Resource>(new JsonApiOptions(), update: serviceMock.Object);
153151

@@ -165,8 +163,6 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled()
165163
const int id = 0;
166164
var resource = new Resource();
167165
var serviceMock = new Mock<IUpdateService<Resource>>();
168-
//_resourceGraph.Setup(a => a.ApplyContext<Resource>(It.IsAny<BaseJsonApiController<Resource>>())).Returns(_resourceGraph.Object);
169-
170166
var controller = new BaseJsonApiController<Resource>(new JsonApiOptions(), update: serviceMock.Object);
171167

172168
// act
@@ -218,7 +214,6 @@ public async Task PostAsync_Calls_Service()
218214
// arrange
219215
var resource = new Resource();
220216
var serviceMock = new Mock<ICreateService<Resource>>();
221-
// _resourceGraph.Setup(a => a.ApplyContext<Resource>(It.IsAny<BaseJsonApiController<Resource>>())).Returns(_resourceGraph.Object);
222217

223218
var controller = new BaseJsonApiController<Resource>(new JsonApiOptions(), create: serviceMock.Object);
224219
serviceMock.Setup(m => m.CreateAsync(It.IsAny<Resource>())).ReturnsAsync(resource);

0 commit comments

Comments
 (0)