Skip to content

Commit

Permalink
Merge pull request #64 from JakeGinnivan/LinksFixes
Browse files Browse the repository at this point in the history
Links fixes and Non-array embedded serialization fix
  • Loading branch information
JakeGinnivan committed Sep 12, 2014
2 parents 254c240 + 05a9f90 commit b5bdc45
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 48 deletions.
5 changes: 5 additions & 0 deletions Readme.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
Release Notes

Version 2.3.0.1
- Fix bug introduced in 2.3.0 of non-array embedded Representation getting serialized improperly as array
- Serialize link Title
- Don't create self link if href is unspecified

Version 2.3.0
- Serialization of one-item SimpleListRepresentation (now is always an array)
- Support .net 4.5 (Currently is 4.5.1 for no reason)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
 {
{
"_links": {
"self": {
"href": "/api/organisations"
Expand Down

This file was deleted.

44 changes: 44 additions & 0 deletions WebApi.Hal.Tests/HalResourceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,50 @@ public void organisation_get_json_with_app_path_test()
}
}

[Fact]
[UseReporter(typeof(DiffReporter))]
public void organisation_get_json_with_no_href_test()
{
// arrange
var mediaFormatter = new JsonHalMediaTypeFormatter { Indent = true };
var content = new StringContent(string.Empty);
var resourceWithAppPath = new OrganisationWithNoHrefRepresentation(1, "Org Name");
var type = resourceWithAppPath.GetType();

// act
using (var stream = new MemoryStream())
{
mediaFormatter.WriteToStreamAsync(type, resourceWithAppPath, stream, content, null);
stream.Seek(0, SeekOrigin.Begin);
var serialisedResult = new StreamReader(stream).ReadToEnd();

// assert
Approvals.Verify(serialisedResult, s => s.Replace("\r\n", "\n"));
}
}

[Fact]
[UseReporter(typeof(DiffReporter))]
public void organisation_get_json_with_link_title_test()
{
// arrange
var mediaFormatter = new JsonHalMediaTypeFormatter { Indent = true };
var content = new StringContent(string.Empty);
var resourceWithAppPath = new OrganisationWithLinkTitleRepresentation(1, "Org Name");
var type = resourceWithAppPath.GetType();

// act
using (var stream = new MemoryStream())
{
mediaFormatter.WriteToStreamAsync(type, resourceWithAppPath, stream, content, null);
stream.Seek(0, SeekOrigin.Begin);
var serialisedResult = new StreamReader(stream).ReadToEnd();

// assert
Approvals.Verify(serialisedResult, s => s.Replace("\r\n", "\n"));
}
}

[Fact]
[UseReporter(typeof(DiffReporter))]
public void organisation_get_xml_test()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Id": 1,
"Name": "Org Name",
"_links": {
"someRel": {
"href": "someHref",
"title": "someTitle"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"Id": 1,
"Name": "Org Name",
"_links": null
}
67 changes: 67 additions & 0 deletions WebApi.Hal.Tests/Representations/OrganisationRepresentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,71 @@ protected override void CreateHypermedia()
{
}
}

/// <summary>
/// no self link is desired, as is the case when a client generates a represent to send to the server
/// </summary>
public class OrganisationWithNoHrefRepresentation : Representation
{
static readonly Link WithAppPath = new Link("organisation", "~/api/organisations/{0}");

public OrganisationWithNoHrefRepresentation(int id, string name)
{
Id = id;
Name = name;
}

public override string Rel
{
get { return WithAppPath.Rel; }
set { }
}

public override string Href
{
get { return null; }
set { }
}

public int Id { get; set; }
public string Name { get; set; }

protected override void CreateHypermedia()
{
}
}

/// <summary>
/// link title
/// </summary>
public class OrganisationWithLinkTitleRepresentation : Representation
{
static readonly Link WithAppPath = new Link("organisation", "~/api/organisations/{0}");

public OrganisationWithLinkTitleRepresentation(int id, string name)
{
Id = id;
Name = name;
}

public override string Rel
{
get { return WithAppPath.Rel; }
set { }
}

public override string Href
{
get { return null; }
set { }
}

public int Id { get; set; }
public string Name { get; set; }

protected override void CreateHypermedia()
{
Links.Add(new Link("someRel", "someHref", "someTitle"));
}
}
}
22 changes: 15 additions & 7 deletions WebApi.Hal/JsonConverters/EmbeddedResourceConverter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using WebApi.Hal.Interfaces;

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

writer.WriteStartObject();

foreach (var rel in resourceList)
{
writer.WritePropertyName(rel.Key);
writer.WriteStartArray();
foreach (var res in rel)
writer.WritePropertyName(NormalizeRel(rel.Resources[0]));
if (rel.IsSourceAnArray)
writer.WriteStartArray();
foreach (var res in rel.Resources)
serializer.Serialize(writer, res);
writer.WriteEndArray();
if (rel.IsSourceAnArray)
writer.WriteEndArray();
}
writer.WriteEndObject();
}

private static string NormalizeRel(IResource res)
{
if (!string.IsNullOrEmpty(res.Rel)) return res.Rel;
return "unknownRel-" + res.GetType().Name;
}

public override bool CanRead
{
get { return false; }
Expand All @@ -38,7 +46,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist

public override bool CanConvert(Type objectType)
{
return typeof(ILookup<string, IResource>).IsAssignableFrom(objectType);
return typeof(IList<EmbeddedResource>).IsAssignableFrom(objectType);
}
}
}
11 changes: 8 additions & 3 deletions WebApi.Hal/JsonConverters/LinksConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ public class LinksConverter : JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var links = new HashSet<Link>((IList<Link>)value, new LinkEqualityComparer());
var lookup = links.ToLookup(l => l.Rel);
if (lookup.Count == 0) return;

writer.WriteStartObject();

var lookup = links.ToLookup(l => l.Rel);

foreach (var rel in lookup)
{
writer.WritePropertyName(rel.Key);
Expand All @@ -32,6 +32,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
writer.WritePropertyName("templated");
writer.WriteValue(true);
}
if (!string.IsNullOrEmpty(link.Title))
{
writer.WritePropertyName("title");
writer.WriteValue(link.Title);
}

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

public string ResolveUri(string href)
{
if (VirtualPathUtility.IsAppRelative(href))
if (!string.IsNullOrEmpty(href) && VirtualPathUtility.IsAppRelative(href))
return HttpContext.Current != null ? VirtualPathUtility.ToAbsolute(href) : href.Replace("~/", "/");
return href;
}
Expand Down
60 changes: 43 additions & 17 deletions WebApi.Hal/Representation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protected Representation()
}

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

[JsonIgnore]
readonly IDictionary<PropertyInfo, object> embeddedResourceProperties = new Dictionary<PropertyInfo, object>();
Expand All @@ -33,27 +33,42 @@ private void OnSerialize(StreamingContext context)
if (ResourceConverter.IsResourceConverterContext(context))
{
// put all embedded resources and lists of resources into Embedded for the _embedded serializer
var resourceList = new List<IResource>();
Embedded = new List<EmbeddedResource>();
foreach (var prop in GetType().GetProperties().Where(p => IsEmbeddedResourceType(p.PropertyType)))
{
var val = prop.GetValue(this, null);
if (val != null)
if (val == null) continue;
// remember embedded resource property for restoring after serialization
embeddedResourceProperties.Add(prop, val);
// add embedded resource to collection for the serializtion
var res = val as IResource;
var embeddedResource = new EmbeddedResource();
if (res != null)
{
// remember embedded resource property for restoring after serialization
embeddedResourceProperties.Add(prop, val);
// add embedded resource to collection for the serializtion
var res = val as IResource;
if (res != null)
resourceList.Add(res);
else
resourceList.AddRange((IEnumerable<IResource>) val);
// null out the embedded property so it doesn't serialize separately as a property
prop.SetValue(this, null, null);
embeddedResource.IsSourceAnArray = false;
embeddedResource.Resources.Add(res);
Embedded.Add(embeddedResource);
}
else
{
var resEnum = val as IEnumerable<IResource>;
if (resEnum != null)
{
var resList = resEnum.ToList();
if (resList.Count > 0)
{
embeddedResource.IsSourceAnArray = true;
foreach (var resElem in resList)
embeddedResource.Resources.Add(resElem);
Embedded.Add(embeddedResource);
}
}
}
// null out the embedded property so it doesn't serialize separately as a property
prop.SetValue(this, null, null);
}
foreach (var res in resourceList.Where(r => string.IsNullOrEmpty(r.Rel)))
res.Rel = "unknownRel-" + res.GetType().Name;
Embedded = resourceList.Count > 0 ? resourceList.ToLookup(r => r.Rel) : null;
if (Embedded.Count == 0)
Embedded = null;
}
}

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

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

protected internal abstract void CreateHypermedia();
}

internal class EmbeddedResource
{
public EmbeddedResource()
{
Resources = new List<IResource>();
}

public bool IsSourceAnArray { get; set; }
public IList<IResource> Resources { get; private set; }
}
}

0 comments on commit b5bdc45

Please sign in to comment.