Skip to content

Commit 9380b76

Browse files
author
Bart Koelman
authored
Rewrite of LinkBuilder to use ASP.NET Core routing to render links (#987)
Rewrite of LinkBuilder to use ASP.NET Core routing to render links. The limitation that custom routes must end in the public resource name no longer applies. - Fixed: Resource-level Self links in atomic:operations responses are now hidden when no controller exists for the resource type. - Fixed: For determining which links to render, settings from primary resource were used on secondary endpoints. For example, if you configure Customer to show all links, but Orders no show none, then /customer/1/orders would show all links. - Optimization: Compound `page[size]` parameter value (example: `10,articles:5`) is calculated once, instead of again for each pagination link. - Deprecated: `IJsonApiRequest.BasePath`. This information is no longer needed, but we still set it for back-compat. - Added support for non-standard route parameters in links, for example: `[DisableRoutingConvention, Route("{shopName}/products")]`
1 parent 64f3b83 commit 9380b76

33 files changed

+623
-255
lines changed

docs/usage/routing.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ GET /orderLines HTTP/1.1
6262
It is possible to bypass the default routing convention for a controller.
6363

6464
```c#
65-
[Route("v1/custom/route/orderLines"), DisableRoutingConvention]
65+
[Route("v1/custom/route/lines-in-order"), DisableRoutingConvention]
6666
public class OrderLineController : JsonApiController<OrderLine>
6767
{
6868
public OrderLineController(IJsonApiOptions options, ILoggerFactory loggerFactory,
@@ -73,8 +73,6 @@ public class OrderLineController : JsonApiController<OrderLine>
7373
}
7474
```
7575

76-
It is required to match your custom url with the exposed name of the associated resource.
77-
7876
## Advanced Usage: Custom Routing Convention
7977

8078
It is possible to replace the built-in routing convention with a [custom routing convention](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-3.1#sample-custom-routing-convention) by registering an implementation of `IJsonApiRoutingConvention`.

src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ namespace JsonApiDotNetCore.Middleware
88
public interface IControllerResourceMapping
99
{
1010
/// <summary>
11-
/// Get the associated resource type for the provided controller type.
11+
/// Gets the associated resource type for the provided controller type.
1212
/// </summary>
1313
Type GetResourceTypeForController(Type controllerType);
14+
15+
/// <summary>
16+
/// Gets the associated controller name for the provided resource type.
17+
/// </summary>
18+
string GetControllerNameForResourceType(Type resourceType);
1419
}
1520
}

src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using JsonApiDotNetCore.Configuration;
23
using JsonApiDotNetCore.Resources.Annotations;
34

@@ -22,6 +23,7 @@ public interface IJsonApiRequest
2223
/// Relative: /api/v1
2324
/// ]]></code>
2425
/// </example>
26+
[Obsolete("This value is calculated for backwards compatibility, but it is no longer used and will be removed in a future version.")]
2527
string BasePath { get; }
2628

2729
/// <summary>

src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ public void CopyFrom(IJsonApiRequest other)
4444
ArgumentGuard.NotNull(other, nameof(other));
4545

4646
Kind = other.Kind;
47+
#pragma warning disable CS0618 // Type or member is obsolete
4748
BasePath = other.BasePath;
49+
#pragma warning restore CS0618 // Type or member is obsolete
4850
PrimaryId = other.PrimaryId;
4951
PrimaryResource = other.PrimaryResource;
5052
SecondaryResource = other.SecondaryResource;

src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs

+20-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class JsonApiRoutingConvention : IJsonApiRoutingConvention
3535
private readonly IResourceContextProvider _resourceContextProvider;
3636
private readonly Dictionary<string, string> _registeredControllerNameByTemplate = new Dictionary<string, string>();
3737
private readonly Dictionary<Type, ResourceContext> _resourceContextPerControllerTypeMap = new Dictionary<Type, ResourceContext>();
38+
private readonly Dictionary<ResourceContext, ControllerModel> _controllerPerResourceContextMap = new Dictionary<ResourceContext, ControllerModel>();
3839

3940
public JsonApiRoutingConvention(IJsonApiOptions options, IResourceContextProvider resourceContextProvider)
4041
{
@@ -58,6 +59,22 @@ public Type GetResourceTypeForController(Type controllerType)
5859
return null;
5960
}
6061

62+
/// <inheritdoc />
63+
public string GetControllerNameForResourceType(Type resourceType)
64+
{
65+
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
66+
67+
ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType);
68+
69+
if (_controllerPerResourceContextMap.TryGetValue(resourceContext, out ControllerModel controllerModel))
70+
71+
{
72+
return controllerModel.ControllerName;
73+
}
74+
75+
return null;
76+
}
77+
6178
/// <inheritdoc />
6279
public void Apply(ApplicationModel application)
6380
{
@@ -78,6 +95,7 @@ public void Apply(ApplicationModel application)
7895
if (resourceContext != null)
7996
{
8097
_resourceContextPerControllerTypeMap.Add(controller.ControllerType, resourceContext);
98+
_controllerPerResourceContextMap.Add(resourceContext, controller);
8199
}
82100
}
83101
}
@@ -117,9 +135,7 @@ private string TemplateFromResource(ControllerModel model)
117135
{
118136
if (_resourceContextPerControllerTypeMap.TryGetValue(model.ControllerType, out ResourceContext resourceContext))
119137
{
120-
string template = $"{_options.Namespace}/{resourceContext.PublicName}";
121-
122-
return template;
138+
return $"{_options.Namespace}/{resourceContext.PublicName}";
123139
}
124140

125141
return null;
@@ -131,9 +147,7 @@ private string TemplateFromResource(ControllerModel model)
131147
private string TemplateFromController(ControllerModel model)
132148
{
133149
string controllerName = _options.SerializerNamingStrategy.GetPropertyName(model.ControllerName, false);
134-
string template = $"{_options.Namespace}/{controllerName}";
135-
136-
return template;
150+
return $"{_options.Namespace}/{controllerName}";
137151
}
138152

139153
/// <summary>

0 commit comments

Comments
 (0)