Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/kuehlmeyer/WebApi.Hal int…
Browse files Browse the repository at this point in the history
…o kuehlmeyer-master
  • Loading branch information
panmanphil committed Oct 22, 2020
2 parents a6e9aa1 + eca4722 commit 8997966
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -115,3 +115,4 @@ WebApi.Hal*.nupkg
.vscode/
.DS_Store
*~
*.bak
Expand Up @@ -3,9 +3,11 @@
"self": {
"href": "/api/organisations"
},
"organisation": {
"href": "/api/organisations/1"
}
"organisation": [
{
"href": "/api/organisations/1"
}
]
},
"_embedded": {
"organisation": [
Expand Down
152 changes: 151 additions & 1 deletion WebApi.Hal.Tests/HalResourceTest.cs
@@ -1,7 +1,14 @@
using System.Buffers;
using System;
using System.Buffers;
using System.IO;
using System.Linq;
using Assent;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WebApi.Hal.Tests.Representations;
using Xunit;

Expand Down Expand Up @@ -112,5 +119,148 @@ public void organisation_get_xml_test()
this.Assent(serialisedResult);
}
}

[Fact]
public void organisation_get_json_with_multi_link_linkrel()
{
// arrange
var mediaFormatter = new JsonHalMediaTypeOutputFormatter(
new JsonSerializerSettings { Formatting = Formatting.Indented }, ArrayPool<char>.Shared);
var resourceWithAppPath = new OrganisationWithLinkTitleRepresentation(1, "Org Name");
resourceWithAppPath.Links.Add(new Link("multi-rel-with-single-link", "~/api/organisations/test")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links", "~/api/organisations/test1")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links", "~/api/organisations/test2")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links-with-is-multilink-false", "~/api/organisations/test-f1"));
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links-with-is-multilink-false", "~/api/organisations/test-f2"));

// act
using (var stream = new StringWriter())
{
mediaFormatter.WriteObject(stream, resourceWithAppPath);

string serialisedResult = stream.ToString();

// assert
this.Assent(serialisedResult);
}
}

public class OrganisationRepresentationWithEmbeddedResources : OrganisationWithAppPathRepresentation
{
public OrganisationRepresentationWithEmbeddedResources(int id, string name) : base(id, name)
{
}

public TestRepresentation[] EmbeddedMultiResource { get; set; }

public TestRepresentation EmbeddedSingleResource { get; set; }
}

public class TestRepresentation : Representation
{
}

[Fact]
public void organisation_get_json_with_multi_link_embedded()
{
// arrange
var mediaFormatter = new JsonHalMediaTypeOutputFormatter(
new JsonSerializerSettings { Formatting = Formatting.Indented }, ArrayPool<char>.Shared);
var org = new OrganisationRepresentationWithEmbeddedResources(1, "Org Name");

org.EmbeddedSingleResource = new TestRepresentation
{
Rel = "single-resource",
Href = "~/single"
};
org.EmbeddedMultiResource = new[] { new TestRepresentation
{
Rel = "multi-resource",
Href = "~/multi"
} };

// act
using (var stream = new StringWriter())
{
mediaFormatter.WriteObject(stream, org);

string serialisedResult = stream.ToString();

// assert
this.Assent(serialisedResult);
}
}

private class JsonHalMediaTypeInputFormatterWithCreateSerializer : JsonHalMediaTypeInputFormatter
{
public JsonHalMediaTypeInputFormatterWithCreateSerializer(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions mvcOptions, MvcJsonOptions mvcJsonOptions)
: base(logger, serializerSettings, charPool, objectPoolProvider, mvcOptions, mvcJsonOptions)
{
}

public new JsonSerializer CreateJsonSerializer()
{
return base.CreateJsonSerializer();
}
}

[Fact]
public void organisation_parse_json_with_multi_link_linkrel()
{
// arrange
var mediaFormatter = new JsonHalMediaTypeOutputFormatter(
new JsonSerializerSettings { Formatting = Formatting.Indented }, ArrayPool<char>.Shared);
var resourceWithAppPath = new OrganisationWithLinkTitleRepresentation(1, "Org Name");
resourceWithAppPath.Links.Add(new Link("multi-rel-with-single-link", "~/api/organisations/test")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links", "~/api/organisations/test1")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links", "~/api/organisations/test2")
{
IsMultiLink = true
});
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links-with-is-multilink-false", "~/api/organisations/test1"));
resourceWithAppPath.Links.Add(new Link("multi-rel-with-multiple-links-with-is-multilink-false", "~/api/organisations/test2"));

string serialisedResource;
// serialize
using (var stream = new StringWriter())
{
mediaFormatter.WriteObject(stream, resourceWithAppPath);

serialisedResource = stream.ToString();
}

// parse again
var inputFormatter = new JsonHalMediaTypeInputFormatterWithCreateSerializer(
NullLogger.Instance,
new JsonSerializerSettings { Formatting = Formatting.Indented }, ArrayPool<char>.Shared,
new DefaultObjectPoolProvider(), new MvcOptions(), new MvcJsonOptions());
var inputSerializer = inputFormatter.CreateJsonSerializer();
using (var stream = new StringReader(serialisedResource))
{
using (var jsonReader = new JsonTextReader(stream))
{
var parsedResource = inputSerializer.Deserialize<OrganisationWithLinkTitleRepresentation>(jsonReader);
Assert.True(parsedResource.Links.Where(l => l.Rel == "multi-rel-with-single-link").All(l => l.IsMultiLink));
Assert.True(parsedResource.Links.Where(l => l.Rel == "multi-rel-with-multiple-links").All(l => l.IsMultiLink));
Assert.True(parsedResource.Links.Where(l => l.Rel == "multi-rel-with-multiple-links-with-is-multilink-false").All(l => l.IsMultiLink));
Assert.True(parsedResource.Links.Where(l => l.Rel == "someRel").All(l => !l.IsMultiLink));
}
}
}
}
}
@@ -0,0 +1,35 @@
{
"Id": 1,
"Name": "Org Name",
"_links": {
"self": {
"href": "/api/organisations/1"
},
"multi-resource": [
{
"href": "/multi"
}
],
"single-resource": {
"href": "/single"
}
},
"_embedded": {
"multi-resource": [
{
"_links": {
"self": {
"href": "/multi"
}
}
}
],
"single-resource": {
"_links": {
"self": {
"href": "/single"
}
}
}
}
}
@@ -0,0 +1,31 @@
{
"Id": 1,
"Name": "Org Name",
"_links": {
"multi-rel-with-single-link": [
{
"href": "/api/organisations/test"
}
],
"multi-rel-with-multiple-links": [
{
"href": "/api/organisations/test1"
},
{
"href": "/api/organisations/test2"
}
],
"multi-rel-with-multiple-links-with-is-multilink-false": [
{
"href": "/api/organisations/test-f1"
},
{
"href": "/api/organisations/test-f2"
}
],
"somerel": {
"href": "someHref",
"title": "someTitle"
}
}
}
6 changes: 4 additions & 2 deletions WebApi.Hal/JsonConverters/LinksConverter.cs
Expand Up @@ -24,13 +24,15 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s

writer.WritePropertyName(rel.Key);

if ((count > 1) || (rel.Key == Link.RelForCuries))
bool serializeAsArray = (count > 1) || (rel.Any(l => l.IsMultiLink)) || (rel.Key == Link.RelForCuries);

if (serializeAsArray)
writer.WriteStartArray();

foreach (var link in rel)
WriteLink(writer, link);

if ((count > 1) || (rel.Key == Link.RelForCuries))
if ((count > 1) || (rel.Any(l => l.IsMultiLink)) || (rel.Key == Link.RelForCuries))
writer.WriteEndArray();
}

Expand Down
1 change: 1 addition & 0 deletions WebApi.Hal/JsonConverters/ResourceConverter.cs
Expand Up @@ -161,6 +161,7 @@ static void CreateLinks(JProperty rel, IResource resource)
foreach (var link in arr.Select(item => item.ToObject<Link>()))
{
link.Rel = rel.Name;
link.IsMultiLink = true;
resource.Links.Add(link);
}
}
Expand Down
1 change: 1 addition & 0 deletions WebApi.Hal/Link.cs
Expand Up @@ -76,6 +76,7 @@ public string Rel
public string Name { get; set; }
public string Profile { get; set; }
public string HrefLang { get; set; }
public bool IsMultiLink { get; set; }

public bool IsTemplated => !string.IsNullOrEmpty(Href) && isTemplatedRegex.IsMatch(Href);

Expand Down
3 changes: 3 additions & 0 deletions WebApi.Hal/Representation.cs
Expand Up @@ -146,7 +146,10 @@ private void ProcessPropertyValue(IHypermediaResolver resolver, List<CuriesLink>
var link = representation.ToLink(resolver);

if (link != null)
{
link.IsMultiLink = true;
Links.Add(link); // add a link to embedded to the container ...
}
}

Embedded.Add(embeddedResource);
Expand Down
10 changes: 4 additions & 6 deletions WebApi.Hal/WebApi.Hal.csproj
Expand Up @@ -4,16 +4,14 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Version>3.1.0</Version>
<PackageVersion>3.1.0</PackageVersion>

<Version>3.2.0</Version>
<PackageVersion>3.2.0</PackageVersion>
<Copyright>Copyright © Jake Ginnivan 2018</Copyright>
<Description>Adds support for the Hal Media Type (and Hypermedia) to Asp.net</Description>
<ProjectUrl>https://github.com/JakeGinnivan/WebApi.Hal</ProjectUrl>
<IsPackable>true</IsPackable>
<PackageReleaseNotes>
3.10 updates to support multithreaded usage
3.0.0 first .net standard release
</PackageReleaseNotes>
<PackageReleaseNotes>3.2.0 ability to mark a link-rel as multi-link to ensure that it always serialzies to an array, even if there's only one link at runtime</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 8997966

Please sign in to comment.