Skip to content

Commit 83d4a1d

Browse files
authored
Merge pull request #159 from Research-Institute/develop
v2.1.4 Relative Links
2 parents 7637123 + d9d6f7f commit 83d4a1d

File tree

5 files changed

+94
-7
lines changed

5 files changed

+94
-7
lines changed

docs/Options.md

+36
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,39 @@ public IServiceProvider ConfigureServices(IServiceCollection services) {
4545
// ...
4646
}
4747
```
48+
49+
## Relative Links
50+
51+
All links are absolute by default. However, you can configure relative links:
52+
53+
```csharp
54+
public IServiceProvider ConfigureServices(IServiceCollection services) {
55+
services.AddJsonApi<AppDbContext>(
56+
opt => opt.RelativeLinks = true);
57+
// ...
58+
}
59+
```
60+
61+
62+
```http
63+
GET /api/v1/articles/4309 HTTP/1.1
64+
Accept: application/vnd.api+json
65+
```
66+
67+
```json
68+
{
69+
"type": "articles",
70+
"id": "4309",
71+
"attributes": {
72+
"name": "Voluptas iure est molestias."
73+
},
74+
"relationships": {
75+
"author": {
76+
"links": {
77+
"self": "/api/v1/articles/4309/relationships/author",
78+
"related": "/api/v1/articles/4309/author"
79+
}
80+
}
81+
}
82+
}
83+
```

src/JsonApiDotNetCore/Builders/LinkBuilder.cs

+8-6
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,30 @@ public class LinkBuilder
99

1010
public LinkBuilder(IJsonApiContext context)
1111
{
12-
_context = context;
12+
_context = context;
1313
}
1414

1515
public string GetBasePath(HttpContext context, string entityName)
1616
{
1717
var r = context.Request;
18-
return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
18+
return (_context.Options.RelativeLinks)
19+
? $"{GetNamespaceFromPath(r.Path, entityName)}"
20+
: $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
1921
}
2022

2123
private string GetNamespaceFromPath(string path, string entityName)
2224
{
2325
var nSpace = string.Empty;
2426
var segments = path.Split('/');
25-
26-
for(var i = 1; i < segments.Length; i++)
27+
28+
for (var i = 1; i < segments.Length; i++)
2729
{
28-
if(segments[i].ToLower() == entityName)
30+
if (segments[i].ToLower() == entityName)
2931
break;
3032

3133
nSpace += $"/{segments[i]}";
3234
}
33-
35+
3436
return nSpace;
3537
}
3638

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class JsonApiOptions
1414
public bool IncludeTotalRecordCount { get; set; }
1515
public bool AllowClientGeneratedIds { get; set; }
1616
public IContextGraph ContextGraph { get; set; }
17+
public bool RelativeLinks { get; set; }
1718
public IContractResolver JsonContractResolver { get; set; } = new DasherizedResolver();
1819
internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder();
1920

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>2.1.3</VersionPrefix>
3+
<VersionPrefix>2.1.4</VersionPrefix>
44
<TargetFrameworks>netstandard1.6</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using JsonApiDotNetCore.Builders;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.AspNetCore.Http;
5+
using Moq;
6+
using Xunit;
7+
8+
namespace UnitTests
9+
{
10+
public class LinkBuilder_Tests
11+
{
12+
[Theory]
13+
[InlineData("http", "localhost", "/api/v1/articles", false, "http://localhost/api/v1")]
14+
[InlineData("https", "localhost", "/api/v1/articles", false, "https://localhost/api/v1")]
15+
[InlineData("http", "example.com", "/api/v1/articles", false, "http://example.com/api/v1")]
16+
[InlineData("https", "example.com", "/api/v1/articles", false, "https://example.com/api/v1")]
17+
[InlineData("https", "example.com", "/articles", false, "https://example.com")]
18+
[InlineData("https", "example.com", "/articles", true, "")]
19+
[InlineData("https", "example.com", "/api/v1/articles", true, "/api/v1")]
20+
public void GetBasePath_Returns_Path_Before_Resource(string scheme,
21+
string host, string path, bool isRelative, string expectedPath)
22+
{
23+
// arrange
24+
const string resource = "articles";
25+
var jsonApiContextMock = new Mock<IJsonApiContext>();
26+
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions
27+
{
28+
RelativeLinks = isRelative
29+
});
30+
31+
var requestMock = new Mock<HttpRequest>();
32+
requestMock.Setup(m => m.Scheme).Returns(scheme);
33+
requestMock.Setup(m => m.Host).Returns(new HostString(host));
34+
requestMock.Setup(m => m.Path).Returns(new PathString(path));
35+
36+
var contextMock = new Mock<HttpContext>();
37+
contextMock.Setup(m => m.Request).Returns(requestMock.Object);
38+
39+
var linkBuilder = new LinkBuilder(jsonApiContextMock.Object);
40+
41+
// act
42+
var basePath = linkBuilder.GetBasePath(contextMock.Object, resource);
43+
44+
// assert
45+
Assert.Equal(expectedPath, basePath);
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)