Skip to content

Fixed: RelativeLinks #766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Examples/GettingStarted/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace GettingStarted
{
public sealed class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SampleDbContext>(
Expand All @@ -19,7 +19,7 @@ public void ConfigureServices(IServiceCollection services)
options => options.Namespace = "api");
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, SampleDbContext context)
{
context.Database.EnsureDeleted();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using JsonApiDotNetCore.Configuration;
using Microsoft.AspNetCore.Hosting;

namespace JsonApiDotNetCoreExample.Startups
{
/// <summary>
/// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0
/// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373.
/// </summary>
public class NoNamespaceStartup : TestStartup
{
public NoNamespaceStartup(IWebHostEnvironment env) : base(env)
{
}

protected override void ConfigureJsonApiOptions(JsonApiOptions options)
{
base.ConfigureJsonApiOptions(options);

options.Namespace = null;
}
}
}
35 changes: 23 additions & 12 deletions src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Extensions;
Expand All @@ -29,11 +30,11 @@ public JsonApiMiddleware(RequestDelegate next)
_next = next;
}

public async Task Invoke(HttpContext httpContext,
IControllerResourceMapping controllerResourceMapping,
IJsonApiOptions options,
ICurrentRequest currentRequest,
IResourceGraph resourceGraph)
public async Task Invoke(HttpContext httpContext,
IControllerResourceMapping controllerResourceMapping,
IJsonApiOptions options,
ICurrentRequest currentRequest,
IResourceGraph resourceGraph)
{
var routeValues = httpContext.GetRouteData().Values;

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

private static string GetBasePath(string resourceName, IJsonApiOptions options, HttpRequest httpRequest)
{
if (options.RelativeLinks)
var builder = new StringBuilder();

if (!options.RelativeLinks)
{
return options.Namespace;
builder.Append(httpRequest.Scheme);
builder.Append("://");
builder.Append(httpRequest.Host);
}

var customRoute = GetCustomRoute(httpRequest.Path.Value, resourceName, options.Namespace);
var toReturn = $"{httpRequest.Scheme}://{httpRequest.Host}/{options.Namespace}";
if (customRoute != null)
string customRoute = GetCustomRoute(httpRequest.Path.Value, resourceName, options.Namespace);
if (!string.IsNullOrEmpty(customRoute))
{
builder.Append('/');
builder.Append(customRoute);
}
else if (!string.IsNullOrEmpty(options.Namespace))
{
toReturn += $"/{customRoute}";
builder.Append('/');
builder.Append(options.Namespace);
}
return toReturn;

return builder.ToString();
}

private static string GetCustomRoute(string path, string resourceName, string apiNamespace)
Expand Down
20 changes: 5 additions & 15 deletions src/JsonApiDotNetCore/Serialization/Server/Builders/LinkBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private void SetPageLinks(ResourceContext resourceContext, TopLevelLinks links)
private string GetSelfTopLevelLink(ResourceContext resourceContext)
{
var builder = new StringBuilder();
builder.Append(GetBasePath());
builder.Append(_currentRequest.BasePath);
builder.Append("/");
builder.Append(resourceContext.ResourceName);

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

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

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

private string GetSelfRelationshipLink(string parent, string parentId, string navigation)
{
return $"{GetBasePath()}/{parent}/{parentId}/relationships/{navigation}";
return $"{_currentRequest.BasePath}/{parent}/{parentId}/relationships/{navigation}";
}

private string GetSelfResourceLink(string resource, string resourceId)
{
return $"{GetBasePath()}/{resource}/{resourceId}";
return $"{_currentRequest.BasePath}/{resource}/{resourceId}";
}

private string GetRelatedRelationshipLink(string parent, string parentId, string navigation)
{
return $"{GetBasePath()}/{parent}/{parentId}/{navigation}";
return $"{_currentRequest.BasePath}/{parent}/{parentId}/{navigation}";
}

/// <summary>
Expand Down Expand Up @@ -221,15 +221,5 @@ private bool ShouldAddRelationshipLink(ResourceContext resourceContext, Relation

return _options.RelationshipLinks.HasFlag(link);
}

protected string GetBasePath()
{
if (_options.RelativeLinks)
{
return string.Empty;
}

return _currentRequest.BasePath;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Models;
using Newtonsoft.Json;
using Xunit;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
{
public sealed class LinksWithNamespaceTests : FunctionalTestCollection<StandardApplicationFactory>
{
public LinksWithNamespaceTests(StandardApplicationFactory factory) : base(factory)
{
}

[Fact]
public async Task GET_RelativeLinks_True_With_Namespace_Returns_RelativeLinks()
{
// Arrange
var person = new Person();

_dbContext.People.Add(person);
_dbContext.SaveChanges();

var route = "/api/v1/people/" + person.StringId;
var request = new HttpRequestMessage(HttpMethod.Get, route);

var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
options.RelativeLinks = true;

// Act
var response = await _factory.Client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/api/v1/people/" + person.StringId, document.Links.Self);
}

[Fact]
public async Task GET_RelativeLinks_False_With_Namespace_Returns_AbsoluteLinks()
{
// Arrange
var person = new Person();

_dbContext.People.Add(person);
_dbContext.SaveChanges();

var route = "/api/v1/people/" + person.StringId;
var request = new HttpRequestMessage(HttpMethod.Get, route);

var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
options.RelativeLinks = false;

// Act
var response = await _factory.Client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal($"http://localhost/api/v1/people/" + person.StringId, document.Links.Self);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using JsonApiDotNetCore.Models;
using Newtonsoft.Json;
using Xunit;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCoreExample.Models;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
{
public sealed class LinksWithoutNamespaceTests : FunctionalTestCollection<NoNamespaceApplicationFactory>
{
public LinksWithoutNamespaceTests(NoNamespaceApplicationFactory factory) : base(factory)
{
}

[Fact]
public async Task GET_RelativeLinks_True_Without_Namespace_Returns_RelativeLinks()
{
// Arrange
var person = new Person();

_dbContext.People.Add(person);
_dbContext.SaveChanges();

var route = "/people/" + person.StringId;
var request = new HttpRequestMessage(HttpMethod.Get, route);

var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
options.RelativeLinks = true;

// Act
var response = await _factory.Client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/people/" + person.StringId, document.Links.Self);
}

[Fact]
public async Task GET_RelativeLinks_False_Without_Namespace_Returns_AbsoluteLinks()
{
// Arrange
var person = new Person();

_dbContext.People.Add(person);
_dbContext.SaveChanges();

var route = "/people/" + person.StringId;
var request = new HttpRequestMessage(HttpMethod.Get, route);

var options = (JsonApiOptions) _factory.GetService<IJsonApiOptions>();
options.RelativeLinks = false;

// Act
var response = await _factory.Client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal($"http://localhost/people/" + person.StringId, document.Links.Self);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using JsonApiDotNetCoreExample.Startups;
using Microsoft.AspNetCore.Hosting;

namespace JsonApiDotNetCoreExampleTests
{
public class NoNamespaceApplicationFactory : CustomApplicationFactoryBase
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseStartup<NoNamespaceStartup>();
}
}
}