Permalink
Browse files

Allow custom formatting of Links in JSON.NET

  • Loading branch information...
1 parent 7a4c185 commit ef16b593911119fbced6b48e8ced3ad1f4c33f49 @markrendle committed Nov 28, 2013
View
@@ -1 +1 @@
-BUILD_VERSION = "0.10.4"
+BUILD_VERSION = "0.11.1"
@@ -14,7 +14,7 @@
<tags>simple.web</tags>
<dependencies>
<dependency id="Simple.Web" version="0.0.0.0" />
- <dependency id="Fix.AspNet" version="0.4" />
+ <dependency id="Fix.AspNet" version="0.6" />
</dependencies>
</metadata>
<files>
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
- <metadata>
- <id>Simple.Web.Hosting.Self</id>
- <version>0.0.0-alpha</version>
- <authors>$authors$</authors>
- <owners>Mark Rendle, Ian Battersby</owners>
- <copyright>Mark Rendle</copyright>
- <licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
- <projectUrl>http://github.com/markrendle/Simple.Web</projectUrl>
- <requireLicenseAcceptance>false</requireLicenseAcceptance>
- <description>Enables OWIN-compatible hosting via OwinHost/Katana.</description>
- <summary>A REST-focused, object-oriented Web Framework for .NET 4.</summary>
- <tags>simple.web</tags>
- <dependencies>
- <dependency id="Simple.Web" version="0.0.0.0" />
- <dependency id="Microsoft.Owin-mono" version="2.0.0-rtw-20815-004" />
- <dependency id="Microsoft.Owin.Hosting-mono" version="2.0.0-rtw-20815-004" />
- <dependency id="Microsoft.Owin.Diagnostics-mono" version="2.0.0-rtw-20815-004" />
- <dependency id="Microsoft.Owin.Host.HttpListener-mono" version="2.0.0-rtw-20815-004" />
- </dependencies>
- </metadata>
- <files>
- <file src="..\..\build\Simple.Web.Hosting.Self.dll" target="lib\net40" />
- <file src="..\..\build\Simple.Web.Hosting.Self.*db" target="lib\net40" />
- <file src="..\..\src\Simple.Web.Hosting.Self\**\*.cs" target="src" />
- </files>
-</package>
@@ -62,6 +62,7 @@
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="HalJsonMediaTypeHandlerTests.cs" />
+ <Compile Include="SimpleLinkFormatterTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@@ -0,0 +1,65 @@
+namespace Simple.Web.JsonNet.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using MediaTypeHandling;
+ using Newtonsoft.Json.Linq;
+ using TestHelpers;
+ using TestHelpers.Sample;
+ using Xunit;
+
+ public class SimpleLinkFormatterTests
+ {
+ [Fact]
+ public void PicksUpOrdersLinkFromCustomer()
+ {
+ const string idProperty = @"""id"":42";
+ const string ordersLink =
+ @"{""title"":null,""href"":""/customer/42/orders"",""rel"":""customer.orders"",""type"":""application/vnd.list.order+json""}";
+ const string selfLink =
+ @"{""title"":null,""href"":""/customer/42"",""rel"":""self"",""type"":""application/vnd.customer+json""}";
+
+ var content = new Content(new Uri("http://test.com/customer/42"), new CustomerHandler(),
+ new Customer {Id = 42});
+ var target = new JsonMediaTypeHandler
+ {
+ JsonLinksFormatter = new SimpleJsonLinksFormatter()
+ };
+ string json;
+ using (var stream = new StringBuilderStream())
+ {
+ target.Write<Customer>(content, stream).Wait();
+ json = stream.StringValue;
+ }
+ var jobj = JObject.Parse(json);
+ var links = jobj["_links"] as JObject;
+ Assert.NotNull(links);
+ Assert.Equal(links["customer.orders"].ToString(), "/customer/42/orders");
+ Assert.Equal(links["self"].ToString(), "/customer/42");
+ }
+
+ [Fact]
+ public void PicksUpPathFromThing()
+ {
+ const string thingLink =
+ @"{""title"":null,""href"":""/things?path=%2Ffoo%2Fbar"",""rel"":""self"",""type"":""application/json""}";
+
+ var content = new Content(new Uri("http://test.com/foo/bar"), new ThingHandler(),
+ new Thing {Path = "/foo/bar"});
+ var target = new JsonMediaTypeHandler
+ {
+ JsonLinksFormatter = new SimpleJsonLinksFormatter()
+ };
+ string json;
+ using (var stream = new StringBuilderStream())
+ {
+ target.Write<Thing>(content, stream).Wait();
+ json = stream.StringValue;
+ }
+ var jobj = JObject.Parse(json);
+ var links = jobj["_links"] as JObject;
+ Assert.NotNull(links);
+ Assert.Equal(links["self"].ToString(), "/things?path=%2Ffoo%2Fbar");
+ }
+ }
+}
@@ -0,0 +1,28 @@
+namespace Simple.Web.JsonNet
+{
+ using System.Collections.Generic;
+ using Links;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Linq;
+
+ public class DefaultJsonLinksFormatter : IJsonLinksFormatter
+ {
+ private string _linksPropertyName = "links";
+
+ public string LinksPropertyName
+ {
+ get { return _linksPropertyName; }
+ set { _linksPropertyName = value; }
+ }
+
+ public void FormatLinks(JContainer container, IEnumerable<Link> links, JsonSerializer serializer)
+ {
+ var jLinks = new JArray();
+ foreach (var link in links)
+ {
+ jLinks.Add(JObject.FromObject(link, serializer));
+ }
+ container[_linksPropertyName] = jLinks;
+ }
+ }
+}
@@ -0,0 +1,26 @@
+namespace Simple.Web.JsonNet
+{
+ using System.Collections.Generic;
+ using Links;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Linq;
+
+ /// <summary>
+ /// Interface for types that format <see cref="Link"/> collections for JSON.NET
+ /// </summary>
+ /// <remarks>
+ /// The implementing types do not write the actual JSON text, rather, they create a JSON.NET <see cref="JToken"/>
+ /// allowing them to specify the structure or layout of the object.
+ /// </remarks>
+ public interface IJsonLinksFormatter
+ {
+ /// <summary>
+ /// Formats the links for a JSON item.
+ /// </summary>
+ /// <param name="container">The object to which the links will be added</param>
+ /// <param name="links">The links. Guaranteed to be non-empty.</param>
+ /// <param name="serializer">The serializer.</param>
+ /// <returns></returns>
+ void FormatLinks(JContainer container, IEnumerable<Link> links, JsonSerializer serializer);
+ }
+}
@@ -11,6 +11,22 @@ namespace Simple.Web.JsonNet
[MediaTypes(MediaType.Json, "application/*+json")]
public class JsonMediaTypeHandler : JsonNetMediaTypeHandlerBase
{
+ private static IJsonLinksFormatter _defaultJsonLinksFormatter = new DefaultJsonLinksFormatter();
+
+ public static IJsonLinksFormatter DefaultJsonLinksFormatter
+ {
+ get { return _defaultJsonLinksFormatter; }
+ set { _defaultJsonLinksFormatter = value; }
+ }
+
+ private IJsonLinksFormatter _jsonLinksFormatter = _defaultJsonLinksFormatter;
+
+ public IJsonLinksFormatter JsonLinksFormatter
+ {
+ get { return _jsonLinksFormatter; }
+ set { _jsonLinksFormatter = value; }
+ }
+
static JsonMediaTypeHandler()
{
SetDefaultSettings(new JsonSerializerSettings
@@ -23,18 +39,12 @@ static JsonMediaTypeHandler()
protected override void AddWireFormattedLinks(JToken wireFormattedItem, IEnumerable<Link> itemLinks)
{
- IList<Link> links = itemLinks as IList<Link> ?? itemLinks.ToList();
+ var links = itemLinks as IList<Link> ?? itemLinks.ToList();
if (links.Count == 0)
{
return;
}
- var jLinks = new JArray();
- foreach (Link link in links)
- {
- EnsureLinkTypeIsJson(link);
- jLinks.Add(JObject.FromObject(link, Serializer));
- }
- wireFormattedItem["links"] = jLinks;
+ _jsonLinksFormatter.FormatLinks((JContainer)wireFormattedItem, links.Where(EnsureLinkTypeIsJson), Serializer);
}
protected override JToken WrapCollection(IList<JToken> collection, IEnumerable<Link> collectionLinks)
@@ -28,7 +28,7 @@ protected JsonSerializer Serializer
get { return _serializer ?? (_serializer = JsonSerializer.Create(Settings)); }
}
- protected void EnsureLinkTypeIsJson(Link link)
+ protected bool EnsureLinkTypeIsJson(Link link)
{
if (String.IsNullOrWhiteSpace(link.Type))
{
@@ -37,7 +37,8 @@ protected void EnsureLinkTypeIsJson(Link link)
if (!link.Type.EndsWith("json"))
{
link.Type += "+json";
- }
+ }
+ return true;
}
protected override Task<T> FromWireFormat<T>(JToken wireFormat)
@@ -45,6 +45,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="DefaultJsonLinksFormatter.cs" />
+ <Compile Include="IJsonLinksFormatter.cs" />
<Compile Include="JsonMediaTypeHandlerWithDeepLinks.cs" />
<Compile Include="JsonNetMediaTypeHandlerBase.cs" />
<Compile Include="LinkConverter.cs" />
@@ -54,6 +56,7 @@
</Compile>
<Compile Include="HalJsonMediaTypeHandler.cs" />
<Compile Include="JsonMediaTypeHandler.cs" />
+ <Compile Include="SimpleJsonLinksFormatter.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Simple.Web\Simple.Web.csproj">
@@ -0,0 +1,36 @@
+namespace Simple.Web.JsonNet
+{
+ using System.Collections.Generic;
+ using Links;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Linq;
+
+ /// <summary>
+ /// Formats links in JSON using a light-weight object format.
+ /// </summary>
+ /// <example>
+ /// A customer object might be formatted like this:
+ ///
+ /// {"name":"ACME","_links":{"self":"/customers/acme","orders":"customers/acme/orders"}}
+ /// </example>
+ public class SimpleJsonLinksFormatter : IJsonLinksFormatter
+ {
+ private string _linksPropertyName = "_links";
+
+ public string LinksPropertyName
+ {
+ get { return _linksPropertyName; }
+ set { _linksPropertyName = value; }
+ }
+
+ public void FormatLinks(JContainer container, IEnumerable<Link> links, JsonSerializer serializer)
+ {
+ var jLinks = new JObject();
+ foreach (var link in links)
+ {
+ jLinks[link.Rel] = JValue.CreateString(link.Href);
+ }
+ container[_linksPropertyName] = jLinks;
+ }
+ }
+}
@@ -7,82 +7,82 @@ public class ContentTypeTests
[Fact]
public void FileWithASingleAcceptTypeReturnsThatType()
{
- var type = Application.GetContentType("any", new[] { "test/type" });
+ var type = StaticContent.GetContentType("any", new[] { "test/type" });
Assert.Equal("test/type", type);
}
[Fact]
public void FileWithMultipleAcceptTypeReturnsFirstType()
{
- var type = Application.GetContentType("any", new[] { "type/one", "type/two", "*/*" });
+ var type = StaticContent.GetContentType("any", new[] { "type/one", "type/two", "*/*" });
Assert.Equal("type/one", type);
}
[Fact]
public void FileWithNullAcceptTypesReturnsPlainText()
{
- var type = Application.GetContentType("any", null);
+ var type = StaticContent.GetContentType("any", null);
Assert.Equal("text/plain", type);
}
[Fact]
public void FileWithEmptyAcceptTypesReturnsPlainText()
{
- var type = Application.GetContentType("any", new string[] { });
+ var type = StaticContent.GetContentType("any", new string[] { });
Assert.Equal("text/plain", type);
}
[Fact]
public void FileWithWildcardAcceptTypeAndNoKnownExtensionReturnsPlainText()
{
- var type = Application.GetContentType("any", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any", new[] { "*/*" });
Assert.Equal("text/plain", type);
}
[Fact]
public void FileWithWildcardAcceptType_Jpg_ExtensionReturns_Jpeg()
{
- var type = Application.GetContentType("any.jpg", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.jpg", new[] { "*/*" });
Assert.Equal("image/jpeg", type);
}
[Fact]
public void FileWithWildcardAcceptType_Jpeg_ExtensionReturns_Jpeg()
{
- var type = Application.GetContentType("any.jpeg", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.jpeg", new[] { "*/*" });
Assert.Equal("image/jpeg", type);
}
[Fact]
public void FileWithWildcardAcceptType_Png_ExtensionReturns_Png()
{
- var type = Application.GetContentType("any.png", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.png", new[] { "*/*" });
Assert.Equal("image/png", type);
}
[Fact]
public void FileWithWildcardAcceptType_Gif_ExtensionReturns_gif()
{
- var type = Application.GetContentType("any.gif", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.gif", new[] { "*/*" });
Assert.Equal("image/gif", type);
}
[Fact]
public void FileWithWildcardAcceptType_Js_ExtensionReturns_JavaScript()
{
- var type = Application.GetContentType("any.js", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.js", new[] { "*/*" });
Assert.Equal("text/javascript", type);
}
[Fact]
public void FileWithWildcardAcceptType_Javascript_ExtensionReturns_JavaScript()
{
- var type = Application.GetContentType("any.JavaScript", new[] { "*/*" });
+ var type = StaticContent.GetContentType("any.JavaScript", new[] { "*/*" });
Assert.Equal("text/javascript", type);
}
Oops, something went wrong.

0 comments on commit ef16b59

Please sign in to comment.