Skip to content

Commit b5bdc45

Browse files
committed
Merge pull request #64 from JakeGinnivan/LinksFixes
Links fixes and Non-array embedded serialization fix
2 parents 254c240 + 05a9f90 commit b5bdc45

10 files changed

Lines changed: 198 additions & 48 deletions

Readme.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Release Notes
22

3+
Version 2.3.0.1
4+
- Fix bug introduced in 2.3.0 of non-array embedded Representation getting serialized improperly as array
5+
- Serialize link Title
6+
- Don't create self link if href is unspecified
7+
38
Version 2.3.0
49
- Serialization of one-item SimpleListRepresentation (now is always an array)
510
- Support .net 4.5 (Currently is 4.5.1 for no reason)

WebApi.Hal.Tests/HalResourceListTests.one_item_organisation_list_get_json_test.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
 {
1+
{
22
"_links": {
33
"self": {
44
"href": "/api/organisations"

WebApi.Hal.Tests/HalResourceListTests.one_item_organisation_list_get_json_test.received.txt

Lines changed: 0 additions & 20 deletions
This file was deleted.

WebApi.Hal.Tests/HalResourceTest.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,50 @@ public void organisation_get_json_with_app_path_test()
5959
}
6060
}
6161

62+
[Fact]
63+
[UseReporter(typeof(DiffReporter))]
64+
public void organisation_get_json_with_no_href_test()
65+
{
66+
// arrange
67+
var mediaFormatter = new JsonHalMediaTypeFormatter { Indent = true };
68+
var content = new StringContent(string.Empty);
69+
var resourceWithAppPath = new OrganisationWithNoHrefRepresentation(1, "Org Name");
70+
var type = resourceWithAppPath.GetType();
71+
72+
// act
73+
using (var stream = new MemoryStream())
74+
{
75+
mediaFormatter.WriteToStreamAsync(type, resourceWithAppPath, stream, content, null);
76+
stream.Seek(0, SeekOrigin.Begin);
77+
var serialisedResult = new StreamReader(stream).ReadToEnd();
78+
79+
// assert
80+
Approvals.Verify(serialisedResult, s => s.Replace("\r\n", "\n"));
81+
}
82+
}
83+
84+
[Fact]
85+
[UseReporter(typeof(DiffReporter))]
86+
public void organisation_get_json_with_link_title_test()
87+
{
88+
// arrange
89+
var mediaFormatter = new JsonHalMediaTypeFormatter { Indent = true };
90+
var content = new StringContent(string.Empty);
91+
var resourceWithAppPath = new OrganisationWithLinkTitleRepresentation(1, "Org Name");
92+
var type = resourceWithAppPath.GetType();
93+
94+
// act
95+
using (var stream = new MemoryStream())
96+
{
97+
mediaFormatter.WriteToStreamAsync(type, resourceWithAppPath, stream, content, null);
98+
stream.Seek(0, SeekOrigin.Begin);
99+
var serialisedResult = new StreamReader(stream).ReadToEnd();
100+
101+
// assert
102+
Approvals.Verify(serialisedResult, s => s.Replace("\r\n", "\n"));
103+
}
104+
}
105+
62106
[Fact]
63107
[UseReporter(typeof(DiffReporter))]
64108
public void organisation_get_xml_test()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Id": 1,
3+
"Name": "Org Name",
4+
"_links": {
5+
"someRel": {
6+
"href": "someHref",
7+
"title": "someTitle"
8+
}
9+
}
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"Id": 1,
3+
"Name": "Org Name",
4+
"_links": null
5+
}

WebApi.Hal.Tests/Representations/OrganisationRepresentation.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,71 @@ protected override void CreateHypermedia()
5959
{
6060
}
6161
}
62+
63+
/// <summary>
64+
/// no self link is desired, as is the case when a client generates a represent to send to the server
65+
/// </summary>
66+
public class OrganisationWithNoHrefRepresentation : Representation
67+
{
68+
static readonly Link WithAppPath = new Link("organisation", "~/api/organisations/{0}");
69+
70+
public OrganisationWithNoHrefRepresentation(int id, string name)
71+
{
72+
Id = id;
73+
Name = name;
74+
}
75+
76+
public override string Rel
77+
{
78+
get { return WithAppPath.Rel; }
79+
set { }
80+
}
81+
82+
public override string Href
83+
{
84+
get { return null; }
85+
set { }
86+
}
87+
88+
public int Id { get; set; }
89+
public string Name { get; set; }
90+
91+
protected override void CreateHypermedia()
92+
{
93+
}
94+
}
95+
96+
/// <summary>
97+
/// link title
98+
/// </summary>
99+
public class OrganisationWithLinkTitleRepresentation : Representation
100+
{
101+
static readonly Link WithAppPath = new Link("organisation", "~/api/organisations/{0}");
102+
103+
public OrganisationWithLinkTitleRepresentation(int id, string name)
104+
{
105+
Id = id;
106+
Name = name;
107+
}
108+
109+
public override string Rel
110+
{
111+
get { return WithAppPath.Rel; }
112+
set { }
113+
}
114+
115+
public override string Href
116+
{
117+
get { return null; }
118+
set { }
119+
}
120+
121+
public int Id { get; set; }
122+
public string Name { get; set; }
123+
124+
protected override void CreateHypermedia()
125+
{
126+
Links.Add(new Link("someRel", "someHref", "someTitle"));
127+
}
128+
}
62129
}

WebApi.Hal/JsonConverters/EmbeddedResourceConverter.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Linq;
2+
using System.Collections.Generic;
33
using Newtonsoft.Json;
44
using WebApi.Hal.Interfaces;
55

@@ -9,22 +9,30 @@ public class EmbeddedResourceConverter : JsonConverter
99
{
1010
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
1111
{
12-
var resourceList = (ILookup<string, IResource>)value;
12+
var resourceList = (IList<EmbeddedResource>)value;
1313
if (resourceList.Count == 0) return;
1414

1515
writer.WriteStartObject();
1616

1717
foreach (var rel in resourceList)
1818
{
19-
writer.WritePropertyName(rel.Key);
20-
writer.WriteStartArray();
21-
foreach (var res in rel)
19+
writer.WritePropertyName(NormalizeRel(rel.Resources[0]));
20+
if (rel.IsSourceAnArray)
21+
writer.WriteStartArray();
22+
foreach (var res in rel.Resources)
2223
serializer.Serialize(writer, res);
23-
writer.WriteEndArray();
24+
if (rel.IsSourceAnArray)
25+
writer.WriteEndArray();
2426
}
2527
writer.WriteEndObject();
2628
}
2729

30+
private static string NormalizeRel(IResource res)
31+
{
32+
if (!string.IsNullOrEmpty(res.Rel)) return res.Rel;
33+
return "unknownRel-" + res.GetType().Name;
34+
}
35+
2836
public override bool CanRead
2937
{
3038
get { return false; }
@@ -38,7 +46,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
3846

3947
public override bool CanConvert(Type objectType)
4048
{
41-
return typeof(ILookup<string, IResource>).IsAssignableFrom(objectType);
49+
return typeof(IList<EmbeddedResource>).IsAssignableFrom(objectType);
4250
}
4351
}
4452
}

WebApi.Hal/JsonConverters/LinksConverter.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ public class LinksConverter : JsonConverter
1111
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
1212
{
1313
var links = new HashSet<Link>((IList<Link>)value, new LinkEqualityComparer());
14+
var lookup = links.ToLookup(l => l.Rel);
15+
if (lookup.Count == 0) return;
1416

1517
writer.WriteStartObject();
1618

17-
var lookup = links.ToLookup(l => l.Rel);
18-
1919
foreach (var rel in lookup)
2020
{
2121
writer.WritePropertyName(rel.Key);
@@ -32,6 +32,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
3232
writer.WritePropertyName("templated");
3333
writer.WriteValue(true);
3434
}
35+
if (!string.IsNullOrEmpty(link.Title))
36+
{
37+
writer.WritePropertyName("title");
38+
writer.WriteValue(link.Title);
39+
}
3540

3641
writer.WriteEndObject();
3742
}
@@ -58,7 +63,7 @@ public override bool CanConvert(Type objectType)
5863

5964
public string ResolveUri(string href)
6065
{
61-
if (VirtualPathUtility.IsAppRelative(href))
66+
if (!string.IsNullOrEmpty(href) && VirtualPathUtility.IsAppRelative(href))
6267
return HttpContext.Current != null ? VirtualPathUtility.ToAbsolute(href) : href.Replace("~/", "/");
6368
return href;
6469
}

WebApi.Hal/Representation.cs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protected Representation()
1717
}
1818

1919
[JsonProperty("_embedded")]
20-
private ILookup<string, IResource> Embedded { get; set; }
20+
private IList<EmbeddedResource> Embedded { get; set; }
2121

2222
[JsonIgnore]
2323
readonly IDictionary<PropertyInfo, object> embeddedResourceProperties = new Dictionary<PropertyInfo, object>();
@@ -33,27 +33,42 @@ private void OnSerialize(StreamingContext context)
3333
if (ResourceConverter.IsResourceConverterContext(context))
3434
{
3535
// put all embedded resources and lists of resources into Embedded for the _embedded serializer
36-
var resourceList = new List<IResource>();
36+
Embedded = new List<EmbeddedResource>();
3737
foreach (var prop in GetType().GetProperties().Where(p => IsEmbeddedResourceType(p.PropertyType)))
3838
{
3939
var val = prop.GetValue(this, null);
40-
if (val != null)
40+
if (val == null) continue;
41+
// remember embedded resource property for restoring after serialization
42+
embeddedResourceProperties.Add(prop, val);
43+
// add embedded resource to collection for the serializtion
44+
var res = val as IResource;
45+
var embeddedResource = new EmbeddedResource();
46+
if (res != null)
4147
{
42-
// remember embedded resource property for restoring after serialization
43-
embeddedResourceProperties.Add(prop, val);
44-
// add embedded resource to collection for the serializtion
45-
var res = val as IResource;
46-
if (res != null)
47-
resourceList.Add(res);
48-
else
49-
resourceList.AddRange((IEnumerable<IResource>) val);
50-
// null out the embedded property so it doesn't serialize separately as a property
51-
prop.SetValue(this, null, null);
48+
embeddedResource.IsSourceAnArray = false;
49+
embeddedResource.Resources.Add(res);
50+
Embedded.Add(embeddedResource);
5251
}
52+
else
53+
{
54+
var resEnum = val as IEnumerable<IResource>;
55+
if (resEnum != null)
56+
{
57+
var resList = resEnum.ToList();
58+
if (resList.Count > 0)
59+
{
60+
embeddedResource.IsSourceAnArray = true;
61+
foreach (var resElem in resList)
62+
embeddedResource.Resources.Add(resElem);
63+
Embedded.Add(embeddedResource);
64+
}
65+
}
66+
}
67+
// null out the embedded property so it doesn't serialize separately as a property
68+
prop.SetValue(this, null, null);
5369
}
54-
foreach (var res in resourceList.Where(r => string.IsNullOrEmpty(r.Rel)))
55-
res.Rel = "unknownRel-" + res.GetType().Name;
56-
Embedded = resourceList.Count > 0 ? resourceList.ToLookup(r => r.Rel) : null;
70+
if (Embedded.Count == 0)
71+
Embedded = null;
5772
}
5873
}
5974

@@ -77,7 +92,7 @@ internal static bool IsEmbeddedResourceType(Type type)
7792
public void RepopulateHyperMedia()
7893
{
7994
CreateHypermedia();
80-
if (Links.Count(l=>l.Rel == "self") == 0)
95+
if (!string.IsNullOrEmpty(Href) && Links.Count(l=>l.Rel == "self") == 0)
8196
Links.Insert(0, new Link { Rel = "self", Href = Href });
8297
}
8398

@@ -94,4 +109,15 @@ public void RepopulateHyperMedia()
94109

95110
protected internal abstract void CreateHypermedia();
96111
}
112+
113+
internal class EmbeddedResource
114+
{
115+
public EmbeddedResource()
116+
{
117+
Resources = new List<IResource>();
118+
}
119+
120+
public bool IsSourceAnArray { get; set; }
121+
public IList<IResource> Resources { get; private set; }
122+
}
97123
}

0 commit comments

Comments
 (0)