From b5a781389d1022c4986fd3ca4640d6531fad12c5 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Fri, 23 Jan 2015 09:32:17 +0100 Subject: [PATCH 1/3] Closes #22 and begins to address #23. --- JSONAPI.Tests/Core/ModelManagerTests.cs | 40 ++++++++++++++++ JSONAPI.Tests/JSONAPI.Tests.csproj | 1 + JSONAPI/Core/ModelManager.cs | 61 +++++++++++++++++++++++++ JSONAPI/JSONAPI.csproj | 1 + JSONAPI/Json/JsonApiFormatter.cs | 8 ++-- 5 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 JSONAPI.Tests/Core/ModelManagerTests.cs create mode 100644 JSONAPI/Core/ModelManager.cs diff --git a/JSONAPI.Tests/Core/ModelManagerTests.cs b/JSONAPI.Tests/Core/ModelManagerTests.cs new file mode 100644 index 00000000..e88d5ad6 --- /dev/null +++ b/JSONAPI.Tests/Core/ModelManagerTests.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using JSONAPI.Core; +using JSONAPI.Tests.Models; +using System.Reflection; + +namespace JSONAPI.Tests.Core +{ + [TestClass] + public class ModelManagerTests + { + private class InvalidModel + { + public string Data { get; set; } + } + + [TestMethod] + public void FindsIdNamedId() + { + // Arrange + // Act + PropertyInfo idprop = ModelManager.Instance.GetIdProperty(typeof(Author)); + + // Assert + Assert.AreSame(typeof(Author).GetProperty("Id"), idprop); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void DoesntFindMissingId() + { + // Arrange + // Act + PropertyInfo idprop = ModelManager.Instance.GetIdProperty(typeof(InvalidModel)); + + // Assert + Assert.Fail("An InvalidOperationException should be thrown and we shouldn't get here!"); + } + } +} diff --git a/JSONAPI.Tests/JSONAPI.Tests.csproj b/JSONAPI.Tests/JSONAPI.Tests.csproj index 9e36c180..3f00810a 100644 --- a/JSONAPI.Tests/JSONAPI.Tests.csproj +++ b/JSONAPI.Tests/JSONAPI.Tests.csproj @@ -77,6 +77,7 @@ + diff --git a/JSONAPI/Core/ModelManager.cs b/JSONAPI/Core/ModelManager.cs new file mode 100644 index 00000000..9cd8da1a --- /dev/null +++ b/JSONAPI/Core/ModelManager.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JSONAPI.Core +{ + class ModelManager + { + #region Singleton pattern + + private static readonly ModelManager instance = new ModelManager(); + + private ModelManager() { } + + public static ModelManager Instance + { + get + { + return instance; + } + } + + #endregion + + #region Cache storage + + private Lazy> _idProperties + = new Lazy>( + () => new Dictionary() + ); + + #endregion + + #region Id property determination + + public PropertyInfo GetIdProperty(Type type) + { + PropertyInfo idprop = null; + + var idPropCache = _idProperties.Value; + + if (idPropCache.TryGetValue(type, out idprop)) return idprop; + + //TODO: Enable attribute-based determination + + idprop = type.GetProperty("Id"); + + if (idprop == null) + throw new InvalidOperationException(String.Format("Unable to determine Id property for type {0}", type)); + + _idProperties.Value.Add(type, idprop); + + return idprop; + } + + #endregion + } +} diff --git a/JSONAPI/JSONAPI.csproj b/JSONAPI/JSONAPI.csproj index b7f4ff2b..69842d17 100644 --- a/JSONAPI/JSONAPI.csproj +++ b/JSONAPI/JSONAPI.csproj @@ -71,6 +71,7 @@ + diff --git a/JSONAPI/Json/JsonApiFormatter.cs b/JSONAPI/Json/JsonApiFormatter.cs index 63a47e01..5e8fb41e 100644 --- a/JSONAPI/Json/JsonApiFormatter.cs +++ b/JSONAPI/Json/JsonApiFormatter.cs @@ -152,7 +152,7 @@ protected void Serialize(object value, Stream writeStream, JsonWriter writer, Js // Do the Id now... writer.WritePropertyName("id"); - var idProp = GetIdProperty(value.GetType()); + var idProp = ModelManager.Instance.GetIdProperty(value.GetType()); writer.WriteValue(GetValueForIdProperty(idProp, value)); PropertyInfo[] props = value.GetType().GetProperties(); @@ -819,15 +819,17 @@ protected object GetById(Type type, string id) { // Only good for creating dummy relationship objects... object retval = Activator.CreateInstance(type); - PropertyInfo idprop = GetIdProperty(type); + PropertyInfo idprop = ModelManager.Instance.GetIdProperty(type); idprop.SetValue(retval, System.Convert.ChangeType(id, idprop.PropertyType)); return retval; } + /* protected PropertyInfo GetIdProperty(Type type) { return type.GetProperty("Id"); } + */ protected string GetValueForIdProperty(PropertyInfo idprop, object obj) { @@ -846,7 +848,7 @@ protected string GetValueForIdProperty(PropertyInfo idprop, object obj) protected string GetIdFor(object obj) { Type type = obj.GetType(); - PropertyInfo idprop = GetIdProperty(type); + PropertyInfo idprop = ModelManager.Instance.GetIdProperty(type); return GetValueForIdProperty(idprop, obj); } From d79f6ba8fe87e90e4d5c06ce70ecdab18c7c6106 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Fri, 23 Jan 2015 10:21:46 +0100 Subject: [PATCH 2/3] Whoops...probably need some thread safety here. --- JSONAPI/Core/ModelManager.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/JSONAPI/Core/ModelManager.cs b/JSONAPI/Core/ModelManager.cs index 9cd8da1a..2fe29fc7 100644 --- a/JSONAPI/Core/ModelManager.cs +++ b/JSONAPI/Core/ModelManager.cs @@ -42,16 +42,19 @@ public PropertyInfo GetIdProperty(Type type) var idPropCache = _idProperties.Value; - if (idPropCache.TryGetValue(type, out idprop)) return idprop; - - //TODO: Enable attribute-based determination + lock (idPropCache) + { + if (idPropCache.TryGetValue(type, out idprop)) return idprop; + + //TODO: Enable attribute-based determination - idprop = type.GetProperty("Id"); + idprop = type.GetProperty("Id"); - if (idprop == null) - throw new InvalidOperationException(String.Format("Unable to determine Id property for type {0}", type)); + if (idprop == null) + throw new InvalidOperationException(String.Format("Unable to determine Id property for type {0}", type)); - _idProperties.Value.Add(type, idprop); + idPropCache.Add(type, idprop); + } return idprop; } From ffc3413c8efbf706b46db3b113698148faf344e2 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Mon, 26 Jan 2015 09:15:29 +0100 Subject: [PATCH 3/3] Refactored to make ModelManager not a singleton, rather a instance variable of JsonApiFormatter. Adds an IModelManager interface to allow the user to create their own ModelManager. --- JSONAPI.Tests/Core/ModelManagerTests.cs | 8 ++++++-- JSONAPI/Core/IModelManager.cs | 14 ++++++++++++++ JSONAPI/Core/ModelManager.cs | 18 ++---------------- JSONAPI/JSONAPI.csproj | 1 + JSONAPI/Json/JsonApiFormatter.cs | 17 ++++++++++++----- 5 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 JSONAPI/Core/IModelManager.cs diff --git a/JSONAPI.Tests/Core/ModelManagerTests.cs b/JSONAPI.Tests/Core/ModelManagerTests.cs index e88d5ad6..7d364777 100644 --- a/JSONAPI.Tests/Core/ModelManagerTests.cs +++ b/JSONAPI.Tests/Core/ModelManagerTests.cs @@ -18,8 +18,10 @@ private class InvalidModel public void FindsIdNamedId() { // Arrange + var mm = new ModelManager(); + // Act - PropertyInfo idprop = ModelManager.Instance.GetIdProperty(typeof(Author)); + PropertyInfo idprop = mm.GetIdProperty(typeof(Author)); // Assert Assert.AreSame(typeof(Author).GetProperty("Id"), idprop); @@ -30,8 +32,10 @@ public void FindsIdNamedId() public void DoesntFindMissingId() { // Arrange + var mm = new ModelManager(); + // Act - PropertyInfo idprop = ModelManager.Instance.GetIdProperty(typeof(InvalidModel)); + PropertyInfo idprop = mm.GetIdProperty(typeof(InvalidModel)); // Assert Assert.Fail("An InvalidOperationException should be thrown and we shouldn't get here!"); diff --git a/JSONAPI/Core/IModelManager.cs b/JSONAPI/Core/IModelManager.cs new file mode 100644 index 00000000..78c21d83 --- /dev/null +++ b/JSONAPI/Core/IModelManager.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace JSONAPI.Core +{ + public interface IModelManager + { + PropertyInfo GetIdProperty(Type type); + } +} diff --git a/JSONAPI/Core/ModelManager.cs b/JSONAPI/Core/ModelManager.cs index 2fe29fc7..93292a79 100644 --- a/JSONAPI/Core/ModelManager.cs +++ b/JSONAPI/Core/ModelManager.cs @@ -7,23 +7,9 @@ namespace JSONAPI.Core { - class ModelManager + public class ModelManager : IModelManager { - #region Singleton pattern - - private static readonly ModelManager instance = new ModelManager(); - - private ModelManager() { } - - public static ModelManager Instance - { - get - { - return instance; - } - } - - #endregion + public ModelManager() { } #region Cache storage diff --git a/JSONAPI/JSONAPI.csproj b/JSONAPI/JSONAPI.csproj index 69842d17..5b9fc1e4 100644 --- a/JSONAPI/JSONAPI.csproj +++ b/JSONAPI/JSONAPI.csproj @@ -68,6 +68,7 @@ + diff --git a/JSONAPI/Json/JsonApiFormatter.cs b/JSONAPI/Json/JsonApiFormatter.cs index 5e8fb41e..e1f62f4c 100644 --- a/JSONAPI/Json/JsonApiFormatter.cs +++ b/JSONAPI/Json/JsonApiFormatter.cs @@ -22,16 +22,23 @@ public class JsonApiFormatter : JsonMediaTypeFormatter public JsonApiFormatter() : this(new ErrorSerializer()) { + if (_modelManager == null) _modelManager = new ModelManager(); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.api+json")); } internal JsonApiFormatter(IErrorSerializer errorSerializer) { _errorSerializer = errorSerializer; - SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.api+json")); } - public IPluralizationService PluralizationService { get; set; } + public JsonApiFormatter(IModelManager modelManager) : this() + { + _modelManager = modelManager; + } + + public IPluralizationService PluralizationService { get; set; } //FIXME: Deprecated, will be removed shortly private readonly IErrorSerializer _errorSerializer; + private readonly IModelManager _modelManager; private Lazy> _relationAggregators = new Lazy>( @@ -152,7 +159,7 @@ protected void Serialize(object value, Stream writeStream, JsonWriter writer, Js // Do the Id now... writer.WritePropertyName("id"); - var idProp = ModelManager.Instance.GetIdProperty(value.GetType()); + var idProp = _modelManager.GetIdProperty(value.GetType()); writer.WriteValue(GetValueForIdProperty(idProp, value)); PropertyInfo[] props = value.GetType().GetProperties(); @@ -819,7 +826,7 @@ protected object GetById(Type type, string id) { // Only good for creating dummy relationship objects... object retval = Activator.CreateInstance(type); - PropertyInfo idprop = ModelManager.Instance.GetIdProperty(type); + PropertyInfo idprop = _modelManager.GetIdProperty(type); idprop.SetValue(retval, System.Convert.ChangeType(id, idprop.PropertyType)); return retval; } @@ -848,7 +855,7 @@ protected string GetValueForIdProperty(PropertyInfo idprop, object obj) protected string GetIdFor(object obj) { Type type = obj.GetType(); - PropertyInfo idprop = ModelManager.Instance.GetIdProperty(type); + PropertyInfo idprop = _modelManager.GetIdProperty(type); return GetValueForIdProperty(idprop, obj); }