|
7 | 7 | using JsonApiDotNetCore.Resources;
|
8 | 8 | using Microsoft.AspNetCore.Mvc;
|
9 | 9 | using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
| 10 | +using Microsoft.AspNetCore.Mvc.Routing; |
10 | 11 | using Microsoft.Extensions.Logging;
|
11 | 12 |
|
12 | 13 | namespace JsonApiDotNetCore.Middleware;
|
13 | 14 |
|
14 | 15 | /// <summary>
|
15 |
| -/// The default routing convention registers the name of the resource as the route using the serializer naming convention. The default for this is a |
16 |
| -/// camel case formatter. If the controller directly inherits from <see cref="CoreJsonApiController" /> and there is no resource directly associated, it |
17 |
| -/// uses the name of the controller instead of the name of the type. |
| 16 | +/// Registers routes based on the JSON:API resource name, which defaults to camel-case pluralized form of the resource CLR type name. If unavailable (for |
| 17 | +/// example, when a controller directly inherits from <see cref="CoreJsonApiController" />), the serializer naming convention is applied on the |
| 18 | +/// controller type name (camel-case by default). |
18 | 19 | /// </summary>
|
19 | 20 | /// <example><![CDATA[
|
20 |
| -/// public class SomeResourceController : JsonApiController<SomeResource> { } // => /someResources/relationship/relatedResource |
| 21 | +/// // controller name is ignored when resource type is available: |
| 22 | +/// public class RandomNameController<SomeResource> : JsonApiController<SomeResource> { } // => /someResources |
21 | 23 | ///
|
22 |
| -/// public class RandomNameController<SomeResource> : JsonApiController<SomeResource> { } // => /someResources/relationship/relatedResource |
| 24 | +/// // when using kebab-case naming convention in options: |
| 25 | +/// public class RandomNameController<SomeResource> : JsonApiController<SomeResource> { } // => /some-resources |
23 | 26 | ///
|
24 |
| -/// // when using kebab-case naming convention: |
25 |
| -/// public class SomeResourceController<SomeResource> : JsonApiController<SomeResource> { } // => /some-resources/relationship/related-resource |
26 |
| -/// |
27 |
| -/// public class SomeVeryCustomController<SomeResource> : CoreJsonApiController { } // => /someVeryCustoms/relationship/relatedResource |
| 27 | +/// // unable to determine resource type: |
| 28 | +/// public class SomeVeryCustomController<SomeResource> : CoreJsonApiController { } // => /someVeryCustom |
28 | 29 | /// ]]></example>
|
29 | 30 | [PublicAPI]
|
30 | 31 | public sealed partial class JsonApiRoutingConvention : IJsonApiRoutingConvention
|
31 | 32 | {
|
32 | 33 | private readonly IJsonApiOptions _options;
|
33 | 34 | private readonly IResourceGraph _resourceGraph;
|
| 35 | + private readonly IJsonApiEndpointFilter _jsonApiEndpointFilter; |
34 | 36 | private readonly ILogger<JsonApiRoutingConvention> _logger;
|
35 | 37 | private readonly Dictionary<string, string> _registeredControllerNameByTemplate = [];
|
36 | 38 | private readonly Dictionary<Type, ResourceType> _resourceTypePerControllerTypeMap = [];
|
37 | 39 | private readonly Dictionary<ResourceType, ControllerModel> _controllerPerResourceTypeMap = [];
|
38 | 40 |
|
39 |
| - public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph, ILogger<JsonApiRoutingConvention> logger) |
| 41 | + public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph, IJsonApiEndpointFilter jsonApiEndpointFilter, |
| 42 | + ILogger<JsonApiRoutingConvention> logger) |
40 | 43 | {
|
41 | 44 | ArgumentGuard.NotNull(options);
|
42 | 45 | ArgumentGuard.NotNull(resourceGraph);
|
| 46 | + ArgumentGuard.NotNull(jsonApiEndpointFilter); |
43 | 47 | ArgumentGuard.NotNull(logger);
|
44 | 48 |
|
45 | 49 | _options = options;
|
46 | 50 | _resourceGraph = resourceGraph;
|
| 51 | + _jsonApiEndpointFilter = jsonApiEndpointFilter; |
47 | 52 | _logger = logger;
|
48 | 53 | }
|
49 | 54 |
|
@@ -106,6 +111,8 @@ public void Apply(ApplicationModel application)
|
106 | 111 | $"Multiple controllers found for resource type '{resourceType}': '{existingModel.ControllerType}' and '{controller.ControllerType}'.");
|
107 | 112 | }
|
108 | 113 |
|
| 114 | + RemoveDisabledActionMethods(controller, resourceType); |
| 115 | + |
109 | 116 | _resourceTypePerControllerTypeMap.Add(controller.ControllerType, resourceType);
|
110 | 117 | _controllerPerResourceTypeMap.Add(resourceType, controller);
|
111 | 118 | }
|
@@ -148,34 +155,10 @@ private static bool HasApiControllerAttribute(ControllerModel controller)
|
148 | 155 | return controller.ControllerType.GetCustomAttribute<ApiControllerAttribute>() != null;
|
149 | 156 | }
|
150 | 157 |
|
151 |
| - private static bool IsRoutingConventionDisabled(ControllerModel controller) |
152 |
| - { |
153 |
| - return controller.ControllerType.GetCustomAttribute<DisableRoutingConventionAttribute>(true) != null; |
154 |
| - } |
155 |
| - |
156 |
| - /// <summary> |
157 |
| - /// Derives a template from the resource type, and checks if this template was already registered. |
158 |
| - /// </summary> |
159 |
| - private string? TemplateFromResource(ControllerModel model) |
160 |
| - { |
161 |
| - if (_resourceTypePerControllerTypeMap.TryGetValue(model.ControllerType, out ResourceType? resourceType)) |
162 |
| - { |
163 |
| - return $"{_options.Namespace}/{resourceType.PublicName}"; |
164 |
| - } |
165 |
| - |
166 |
| - return null; |
167 |
| - } |
168 |
| - |
169 |
| - /// <summary> |
170 |
| - /// Derives a template from the controller name, and checks if this template was already registered. |
171 |
| - /// </summary> |
172 |
| - private string TemplateFromController(ControllerModel model) |
| 158 | + private static bool IsOperationsController(Type type) |
173 | 159 | {
|
174 |
| - string controllerName = _options.SerializerOptions.PropertyNamingPolicy == null |
175 |
| - ? model.ControllerName |
176 |
| - : _options.SerializerOptions.PropertyNamingPolicy.ConvertName(model.ControllerName); |
177 |
| - |
178 |
| - return $"{_options.Namespace}/{controllerName}"; |
| 160 | + Type baseControllerType = typeof(BaseJsonApiOperationsController); |
| 161 | + return baseControllerType.IsAssignableFrom(type); |
179 | 162 | }
|
180 | 163 |
|
181 | 164 | /// <summary>
|
@@ -213,10 +196,47 @@ private string TemplateFromController(ControllerModel model)
|
213 | 196 | return currentType?.GetGenericArguments().First();
|
214 | 197 | }
|
215 | 198 |
|
216 |
| - private static bool IsOperationsController(Type type) |
| 199 | + private void RemoveDisabledActionMethods(ControllerModel controller, ResourceType resourceType) |
217 | 200 | {
|
218 |
| - Type baseControllerType = typeof(BaseJsonApiOperationsController); |
219 |
| - return baseControllerType.IsAssignableFrom(type); |
| 201 | + foreach (ActionModel actionModel in controller.Actions.ToArray()) |
| 202 | + { |
| 203 | + JsonApiEndpoints endpoint = actionModel.Attributes.OfType<HttpMethodAttribute>().GetJsonApiEndpoint(); |
| 204 | + |
| 205 | + if (endpoint != JsonApiEndpoints.None && !_jsonApiEndpointFilter.IsEnabled(resourceType, endpoint)) |
| 206 | + { |
| 207 | + controller.Actions.Remove(actionModel); |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + private static bool IsRoutingConventionDisabled(ControllerModel controller) |
| 213 | + { |
| 214 | + return controller.ControllerType.GetCustomAttribute<DisableRoutingConventionAttribute>(true) != null; |
| 215 | + } |
| 216 | + |
| 217 | + /// <summary> |
| 218 | + /// Derives a template from the resource type, and checks if this template was already registered. |
| 219 | + /// </summary> |
| 220 | + private string? TemplateFromResource(ControllerModel model) |
| 221 | + { |
| 222 | + if (_resourceTypePerControllerTypeMap.TryGetValue(model.ControllerType, out ResourceType? resourceType)) |
| 223 | + { |
| 224 | + return $"{_options.Namespace}/{resourceType.PublicName}"; |
| 225 | + } |
| 226 | + |
| 227 | + return null; |
| 228 | + } |
| 229 | + |
| 230 | + /// <summary> |
| 231 | + /// Derives a template from the controller name, and checks if this template was already registered. |
| 232 | + /// </summary> |
| 233 | + private string TemplateFromController(ControllerModel model) |
| 234 | + { |
| 235 | + string controllerName = _options.SerializerOptions.PropertyNamingPolicy == null |
| 236 | + ? model.ControllerName |
| 237 | + : _options.SerializerOptions.PropertyNamingPolicy.ConvertName(model.ControllerName); |
| 238 | + |
| 239 | + return $"{_options.Namespace}/{controllerName}"; |
220 | 240 | }
|
221 | 241 |
|
222 | 242 | [LoggerMessage(Level = LogLevel.Warning,
|
|
0 commit comments