Skip to content

Commit fdebb81

Browse files
fdlaneBart Koelman
and
Bart Koelman
authored
Fixed: RelativeLinks (#766)
* Fixed: RelativeLinks * Added tests for relative/absolute paths with/without namespace Co-authored-by: Bart Koelman <bart@degreed.com>
1 parent 4f6b1cd commit fdebb81

File tree

6 files changed

+200
-27
lines changed

6 files changed

+200
-27
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using Microsoft.AspNetCore.Hosting;
3+
4+
namespace JsonApiDotNetCoreExample.Startups
5+
{
6+
/// <summary>
7+
/// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0
8+
/// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373.
9+
/// </summary>
10+
public class NoNamespaceStartup : TestStartup
11+
{
12+
public NoNamespaceStartup(IWebHostEnvironment env) : base(env)
13+
{
14+
}
15+
16+
protected override void ConfigureJsonApiOptions(JsonApiOptions options)
17+
{
18+
base.ConfigureJsonApiOptions(options);
19+
20+
options.Namespace = null;
21+
}
22+
}
23+
}

src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Net;
55
using System.Net.Http.Headers;
6+
using System.Text;
67
using System.Threading.Tasks;
78
using JsonApiDotNetCore.Configuration;
89
using JsonApiDotNetCore.Extensions;
@@ -29,11 +30,11 @@ public JsonApiMiddleware(RequestDelegate next)
2930
_next = next;
3031
}
3132

32-
public async Task Invoke(HttpContext httpContext,
33-
IControllerResourceMapping controllerResourceMapping,
34-
IJsonApiOptions options,
35-
ICurrentRequest currentRequest,
36-
IResourceGraph resourceGraph)
33+
public async Task Invoke(HttpContext httpContext,
34+
IControllerResourceMapping controllerResourceMapping,
35+
IJsonApiOptions options,
36+
ICurrentRequest currentRequest,
37+
IResourceGraph resourceGraph)
3738
{
3839
var routeValues = httpContext.GetRouteData().Values;
3940

@@ -171,18 +172,28 @@ private static string GetBaseId(RouteValueDictionary routeValues)
171172

172173
private static string GetBasePath(string resourceName, IJsonApiOptions options, HttpRequest httpRequest)
173174
{
174-
if (options.RelativeLinks)
175+
var builder = new StringBuilder();
176+
177+
if (!options.RelativeLinks)
175178
{
176-
return options.Namespace;
179+
builder.Append(httpRequest.Scheme);
180+
builder.Append("://");
181+
builder.Append(httpRequest.Host);
177182
}
178183

179-
var customRoute = GetCustomRoute(httpRequest.Path.Value, resourceName, options.Namespace);
180-
var toReturn = $"{httpRequest.Scheme}://{httpRequest.Host}/{options.Namespace}";
181-
if (customRoute != null)
184+
string customRoute = GetCustomRoute(httpRequest.Path.Value, resourceName, options.Namespace);
185+
if (!string.IsNullOrEmpty(customRoute))
186+
{
187+
builder.Append('/');
188+
builder.Append(customRoute);
189+
}
190+
else if (!string.IsNullOrEmpty(options.Namespace))
182191
{
183-
toReturn += $"/{customRoute}";
192+
builder.Append('/');
193+
builder.Append(options.Namespace);
184194
}
185-
return toReturn;
195+
196+
return builder.ToString();
186197
}
187198

188199
private static string GetCustomRoute(string path, string resourceName, string apiNamespace)

src/JsonApiDotNetCore/Serialization/Server/Builders/LinkBuilder.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private void SetPageLinks(ResourceContext resourceContext, TopLevelLinks links)
9292
private string GetSelfTopLevelLink(ResourceContext resourceContext)
9393
{
9494
var builder = new StringBuilder();
95-
builder.Append(GetBasePath());
95+
builder.Append(_currentRequest.BasePath);
9696
builder.Append("/");
9797
builder.Append(resourceContext.ResourceName);
9898

@@ -127,7 +127,7 @@ private string GetPageLink(ResourceContext resourceContext, int pageOffset, int
127127
parameters["page[number]"] = pageOffset.ToString();
128128
});
129129

130-
return $"{GetBasePath()}/{resourceContext.ResourceName}" + queryString;
130+
return $"{_currentRequest.BasePath}/{resourceContext.ResourceName}" + queryString;
131131
}
132132

133133
private string BuildQueryString(Action<Dictionary<string, string>> updateAction)
@@ -175,17 +175,17 @@ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship
175175

176176
private string GetSelfRelationshipLink(string parent, string parentId, string navigation)
177177
{
178-
return $"{GetBasePath()}/{parent}/{parentId}/relationships/{navigation}";
178+
return $"{_currentRequest.BasePath}/{parent}/{parentId}/relationships/{navigation}";
179179
}
180180

181181
private string GetSelfResourceLink(string resource, string resourceId)
182182
{
183-
return $"{GetBasePath()}/{resource}/{resourceId}";
183+
return $"{_currentRequest.BasePath}/{resource}/{resourceId}";
184184
}
185185

186186
private string GetRelatedRelationshipLink(string parent, string parentId, string navigation)
187187
{
188-
return $"{GetBasePath()}/{parent}/{parentId}/{navigation}";
188+
return $"{_currentRequest.BasePath}/{parent}/{parentId}/{navigation}";
189189
}
190190

191191
/// <summary>
@@ -221,15 +221,5 @@ private bool ShouldAddRelationshipLink(ResourceContext resourceContext, Relation
221221

222222
return _options.RelationshipLinks.HasFlag(link);
223223
}
224-
225-
protected string GetBasePath()
226-
{
227-
if (_options.RelativeLinks)
228-
{
229-
return string.Empty;
230-
}
231-
232-
return _currentRequest.BasePath;
233-
}
234224
}
235225
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Configuration;
5+
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCoreExample.Models;
7+
using Newtonsoft.Json;
8+
using Xunit;
9+
10+
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
11+
{
12+
public sealed class LinksWithNamespaceTests : FunctionalTestCollection<StandardApplicationFactory>
13+
{
14+
public LinksWithNamespaceTests(StandardApplicationFactory factory) : base(factory)
15+
{
16+
}
17+
18+
[Fact]
19+
public async Task GET_RelativeLinks_True_With_Namespace_Returns_RelativeLinks()
20+
{
21+
// Arrange
22+
var person = new Person();
23+
24+
_dbContext.People.Add(person);
25+
_dbContext.SaveChanges();
26+
27+
var route = "/api/v1/people/" + person.StringId;
28+
var request = new HttpRequestMessage(HttpMethod.Get, route);
29+
30+
var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
31+
options.RelativeLinks = true;
32+
33+
// Act
34+
var response = await _factory.Client.SendAsync(request);
35+
var responseString = await response.Content.ReadAsStringAsync();
36+
var document = JsonConvert.DeserializeObject<Document>(responseString);
37+
38+
// Assert
39+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
40+
Assert.Equal("/api/v1/people/" + person.StringId, document.Links.Self);
41+
}
42+
43+
[Fact]
44+
public async Task GET_RelativeLinks_False_With_Namespace_Returns_AbsoluteLinks()
45+
{
46+
// Arrange
47+
var person = new Person();
48+
49+
_dbContext.People.Add(person);
50+
_dbContext.SaveChanges();
51+
52+
var route = "/api/v1/people/" + person.StringId;
53+
var request = new HttpRequestMessage(HttpMethod.Get, route);
54+
55+
var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
56+
options.RelativeLinks = false;
57+
58+
// Act
59+
var response = await _factory.Client.SendAsync(request);
60+
var responseString = await response.Content.ReadAsStringAsync();
61+
var document = JsonConvert.DeserializeObject<Document>(responseString);
62+
63+
// Assert
64+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
65+
Assert.Equal($"http://localhost/api/v1/people/" + person.StringId, document.Links.Self);
66+
}
67+
}
68+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Models;
5+
using Newtonsoft.Json;
6+
using Xunit;
7+
using JsonApiDotNetCore.Configuration;
8+
using JsonApiDotNetCoreExample.Models;
9+
10+
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
11+
{
12+
public sealed class LinksWithoutNamespaceTests : FunctionalTestCollection<NoNamespaceApplicationFactory>
13+
{
14+
public LinksWithoutNamespaceTests(NoNamespaceApplicationFactory factory) : base(factory)
15+
{
16+
}
17+
18+
[Fact]
19+
public async Task GET_RelativeLinks_True_Without_Namespace_Returns_RelativeLinks()
20+
{
21+
// Arrange
22+
var person = new Person();
23+
24+
_dbContext.People.Add(person);
25+
_dbContext.SaveChanges();
26+
27+
var route = "/people/" + person.StringId;
28+
var request = new HttpRequestMessage(HttpMethod.Get, route);
29+
30+
var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
31+
options.RelativeLinks = true;
32+
33+
// Act
34+
var response = await _factory.Client.SendAsync(request);
35+
var responseString = await response.Content.ReadAsStringAsync();
36+
var document = JsonConvert.DeserializeObject<Document>(responseString);
37+
38+
// Assert
39+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
40+
Assert.Equal("/people/" + person.StringId, document.Links.Self);
41+
}
42+
43+
[Fact]
44+
public async Task GET_RelativeLinks_False_Without_Namespace_Returns_AbsoluteLinks()
45+
{
46+
// Arrange
47+
var person = new Person();
48+
49+
_dbContext.People.Add(person);
50+
_dbContext.SaveChanges();
51+
52+
var route = "/people/" + person.StringId;
53+
var request = new HttpRequestMessage(HttpMethod.Get, route);
54+
55+
var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
56+
options.RelativeLinks = false;
57+
58+
// Act
59+
var response = await _factory.Client.SendAsync(request);
60+
var responseString = await response.Content.ReadAsStringAsync();
61+
var document = JsonConvert.DeserializeObject<Document>(responseString);
62+
63+
// Assert
64+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
65+
Assert.Equal($"http://localhost/people/" + person.StringId, document.Links.Self);
66+
}
67+
}
68+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using JsonApiDotNetCoreExample.Startups;
2+
using Microsoft.AspNetCore.Hosting;
3+
4+
namespace JsonApiDotNetCoreExampleTests
5+
{
6+
public class NoNamespaceApplicationFactory : CustomApplicationFactoryBase
7+
{
8+
protected override void ConfigureWebHost(IWebHostBuilder builder)
9+
{
10+
builder.UseStartup<NoNamespaceStartup>();
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)