Skip to content

Commit 49c18ad

Browse files
author
Bart Koelman
authored
Corrected link rendering for hosting inside an IIS application vdir (#955)
Corrected link rendering for hosting inside an IIS application virtual directory
1 parent 431541e commit 49c18ad

File tree

10 files changed

+254
-1
lines changed

10 files changed

+254
-1
lines changed

src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs

+5
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ private static string GetBasePath(string resourceName, IJsonApiOptions options,
208208
builder.Append(httpRequest.Host);
209209
}
210210

211+
if (httpRequest.PathBase.HasValue)
212+
{
213+
builder.Append(httpRequest.PathBase);
214+
}
215+
211216
string customRoute = GetCustomRoute(resourceName, options.Namespace, httpRequest.HttpContext);
212217
if (!string.IsNullOrEmpty(customRoute))
213218
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
7+
{
8+
public sealed class ArtGalleriesController : JsonApiController<ArtGallery>
9+
{
10+
public ArtGalleriesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
11+
IResourceService<ArtGallery> resourceService)
12+
: base(options, loggerFactory, resourceService)
13+
{
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Resources;
3+
using JsonApiDotNetCore.Resources.Annotations;
4+
5+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
6+
{
7+
public sealed class ArtGallery : Identifiable
8+
{
9+
[Attr]
10+
public string Theme { get; set; }
11+
12+
[HasMany]
13+
public ISet<Painting> Paintings { get; set; }
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
4+
{
5+
public sealed class HostingDbContext : DbContext
6+
{
7+
public DbSet<ArtGallery> ArtGalleries { get; set; }
8+
public DbSet<Painting> Paintings { get; set; }
9+
10+
public HostingDbContext(DbContextOptions<HostingDbContext> options)
11+
: base(options)
12+
{
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using Bogus;
3+
using TestBuildingBlocks;
4+
5+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
6+
{
7+
internal sealed class HostingFakers : FakerContainer
8+
{
9+
private readonly Lazy<Faker<ArtGallery>> _lazyArtGalleryFaker = new Lazy<Faker<ArtGallery>>(() =>
10+
new Faker<ArtGallery>()
11+
.UseSeed(GetFakerSeed())
12+
.RuleFor(artGallery => artGallery.Theme, f => f.Lorem.Word()));
13+
14+
private readonly Lazy<Faker<Painting>> _lazyPaintingFaker = new Lazy<Faker<Painting>>(() =>
15+
new Faker<Painting>()
16+
.UseSeed(GetFakerSeed())
17+
.RuleFor(painting => painting.Title, f => f.Lorem.Sentence()));
18+
19+
public Faker<ArtGallery> ArtGallery => _lazyArtGalleryFaker.Value;
20+
public Faker<Painting> Painting => _lazyPaintingFaker.Value;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCoreExampleTests.Startups;
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.Extensions.Configuration;
7+
8+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
9+
{
10+
public sealed class HostingStartup<TDbContext> : TestableStartup<TDbContext>
11+
where TDbContext : DbContext
12+
{
13+
public HostingStartup(IConfiguration configuration) : base(configuration)
14+
{
15+
}
16+
17+
protected override void SetJsonApiOptions(JsonApiOptions options)
18+
{
19+
base.SetJsonApiOptions(options);
20+
21+
options.Namespace = "public-api";
22+
options.IncludeTotalResourceCount = true;
23+
}
24+
25+
public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
26+
{
27+
app.UsePathBase("/iis-application-virtual-directory");
28+
29+
base.Configure(app, environment);
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System.Linq;
2+
using System.Net;
3+
using System.Threading.Tasks;
4+
using FluentAssertions;
5+
using JsonApiDotNetCore.Serialization.Objects;
6+
using TestBuildingBlocks;
7+
using Xunit;
8+
9+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
10+
{
11+
public sealed class HostingTests
12+
: IClassFixture<ExampleIntegrationTestContext<HostingStartup<HostingDbContext>, HostingDbContext>>
13+
{
14+
private const string HostPrefix = "http://localhost";
15+
16+
private readonly ExampleIntegrationTestContext<HostingStartup<HostingDbContext>, HostingDbContext> _testContext;
17+
private readonly HostingFakers _fakers = new HostingFakers();
18+
19+
public HostingTests(ExampleIntegrationTestContext<HostingStartup<HostingDbContext>, HostingDbContext> testContext)
20+
{
21+
_testContext = testContext;
22+
}
23+
24+
[Fact]
25+
public async Task Get_primary_resources_with_include_returns_links()
26+
{
27+
// Arrange
28+
var gallery = _fakers.ArtGallery.Generate();
29+
gallery.Paintings = _fakers.Painting.Generate(1).ToHashSet();
30+
31+
await _testContext.RunOnDatabaseAsync(async dbContext =>
32+
{
33+
await dbContext.ClearTableAsync<ArtGallery>();
34+
dbContext.ArtGalleries.Add(gallery);
35+
await dbContext.SaveChangesAsync();
36+
});
37+
38+
const string route = "/iis-application-virtual-directory/public-api/artGalleries?include=paintings";
39+
40+
// Act
41+
var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
42+
43+
// Assert
44+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
45+
46+
responseDocument.Links.Self.Should().Be(HostPrefix + route);
47+
responseDocument.Links.Related.Should().BeNull();
48+
responseDocument.Links.First.Should().Be(HostPrefix + route);
49+
responseDocument.Links.Last.Should().Be(HostPrefix + route);
50+
responseDocument.Links.Prev.Should().BeNull();
51+
responseDocument.Links.Next.Should().BeNull();
52+
53+
string galleryLink = HostPrefix + $"/iis-application-virtual-directory/public-api/artGalleries/{gallery.StringId}";
54+
55+
responseDocument.ManyData.Should().HaveCount(1);
56+
responseDocument.ManyData[0].Links.Self.Should().Be(galleryLink);
57+
responseDocument.ManyData[0].Relationships["paintings"].Links.Self.Should().Be(galleryLink + "/relationships/paintings");
58+
responseDocument.ManyData[0].Relationships["paintings"].Links.Related.Should().Be(galleryLink + "/paintings");
59+
60+
// TODO: The next link is wrong: it should use the custom route.
61+
// https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/956
62+
string paintingLink = HostPrefix + $"/iis-application-virtual-directory/public-api/paintings/{gallery.Paintings.ElementAt(0).StringId}";
63+
64+
responseDocument.Included.Should().HaveCount(1);
65+
responseDocument.Included[0].Links.Self.Should().Be(paintingLink);
66+
responseDocument.Included[0].Relationships["exposedAt"].Links.Self.Should().Be(paintingLink + "/relationships/exposedAt");
67+
responseDocument.Included[0].Relationships["exposedAt"].Links.Related.Should().Be(paintingLink + "/exposedAt");
68+
}
69+
70+
[Fact]
71+
public async Task Get_primary_resources_with_include_on_custom_route_returns_links()
72+
{
73+
// Arrange
74+
var painting = _fakers.Painting.Generate();
75+
painting.ExposedAt = _fakers.ArtGallery.Generate();
76+
77+
await _testContext.RunOnDatabaseAsync(async dbContext =>
78+
{
79+
await dbContext.ClearTableAsync<Painting>();
80+
dbContext.Paintings.Add(painting);
81+
await dbContext.SaveChangesAsync();
82+
});
83+
84+
const string route = "/iis-application-virtual-directory/custom/path/to/paintings?include=exposedAt";
85+
86+
// Act
87+
var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
88+
89+
// Assert
90+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
91+
92+
responseDocument.Links.Self.Should().Be(HostPrefix + route);
93+
responseDocument.Links.Related.Should().BeNull();
94+
responseDocument.Links.First.Should().Be(HostPrefix + route);
95+
responseDocument.Links.Last.Should().Be(HostPrefix + route);
96+
responseDocument.Links.Prev.Should().BeNull();
97+
responseDocument.Links.Next.Should().BeNull();
98+
99+
string paintingLink = HostPrefix + $"/iis-application-virtual-directory/custom/path/to/paintings/{painting.StringId}";
100+
101+
responseDocument.ManyData.Should().HaveCount(1);
102+
responseDocument.ManyData[0].Links.Self.Should().Be(paintingLink);
103+
responseDocument.ManyData[0].Relationships["exposedAt"].Links.Self.Should().Be(paintingLink + "/relationships/exposedAt");
104+
responseDocument.ManyData[0].Relationships["exposedAt"].Links.Related.Should().Be(paintingLink + "/exposedAt");
105+
106+
// TODO: The next link is wrong: it should not use the custom route.
107+
// https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/956
108+
string galleryLink = HostPrefix + $"/iis-application-virtual-directory/custom/path/to/artGalleries/{painting.ExposedAt.StringId}";
109+
110+
responseDocument.Included.Should().HaveCount(1);
111+
responseDocument.Included[0].Links.Self.Should().Be(galleryLink);
112+
responseDocument.Included[0].Relationships["paintings"].Links.Self.Should().Be(galleryLink + "/relationships/paintings");
113+
responseDocument.Included[0].Relationships["paintings"].Links.Related.Should().Be(galleryLink + "/paintings");
114+
}
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using JsonApiDotNetCore.Resources;
2+
using JsonApiDotNetCore.Resources.Annotations;
3+
4+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
5+
{
6+
public sealed class Painting : Identifiable
7+
{
8+
[Attr]
9+
public string Title { get; set; }
10+
11+
[HasOne]
12+
public ArtGallery ExposedAt { get; set; }
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Controllers.Annotations;
4+
using JsonApiDotNetCore.Services;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.HostingInIIS
9+
{
10+
[DisableRoutingConvention, Route("custom/path/to/paintings")]
11+
public sealed class PaintingsController : JsonApiController<Painting>
12+
{
13+
public PaintingsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
14+
IResourceService<Painting> resourceService)
15+
: base(options, loggerFactory, resourceService)
16+
{
17+
}
18+
}
19+
}

test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/KebabCasingConventionStartup.cs

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ protected override void SetJsonApiOptions(JsonApiOptions options)
1717
{
1818
base.SetJsonApiOptions(options);
1919

20-
options.IncludeExceptionStackTraceInErrors = true;
2120
options.Namespace = "public-api";
2221
options.UseRelativeLinks = true;
2322
options.IncludeTotalResourceCount = true;

0 commit comments

Comments
 (0)