From d077bc9d577b17fd4fb1925cfa9eb893dd8ad9b8 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 14:04:28 -0700 Subject: [PATCH 001/149] Aligned equal signs --- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 140 +++++++++--------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index 526a24f8..9f187e0b 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -62,7 +62,7 @@ public void TopicMappingService_MapGeneric() { topic.Attributes.SetValue("Title", "Value1"); topic.Attributes.SetValue("IsHidden", "1"); - var target = (PageTopicViewModel)mappingService.Map(topic, new PageTopicViewModel()); + var target = (PageTopicViewModel)mappingService.Map(topic, new PageTopicViewModel()); Assert.AreEqual("ValueA", target.MetaTitle); Assert.AreEqual("Value1", target.Title); @@ -87,7 +87,7 @@ public void TopicMappingService_MapDynamic() { topic.Attributes.SetValue("Title", "Value1"); topic.Attributes.SetValue("IsHidden", "1"); - var target = (PageTopicViewModel)mappingService.Map(topic); + var target = (PageTopicViewModel)mappingService.Map(topic); Assert.AreEqual("ValueA", target.MetaTitle); Assert.AreEqual("Value1", target.Title); @@ -146,16 +146,15 @@ public void TopicMappingService_MapParents() { [TestMethod] public void TopicMappingService_InheritValues() { - var mappingService = new TopicMappingService(_topicRepository); - - var grandParent = TopicFactory.Create("Grandparent", "Page"); - var parent = TopicFactory.Create("Parent", "Page", grandParent); - var topic = TopicFactory.Create("Test", "Sample", parent); + var mappingService = new TopicMappingService(_topicRepository); + var grandParent = TopicFactory.Create("Grandparent", "Page"); + var parent = TopicFactory.Create("Parent", "Page", grandParent); + var topic = TopicFactory.Create("Test", "Sample", parent); grandParent.Attributes.SetValue("Property", "ValueA"); grandParent.Attributes.SetValue("InheritedProperty", "ValueB"); - var viewModel = (SampleTopicViewModel)mappingService.Map(topic); + var viewModel = (SampleTopicViewModel)mappingService.Map(topic); Assert.AreEqual(null, viewModel.Property); Assert.AreEqual("ValueB", viewModel.InheritedProperty); @@ -172,14 +171,13 @@ public void TopicMappingService_InheritValues() { [TestMethod] public void TopicMappingService_AlternateAttributeKey() { - var mappingService = new TopicMappingService(_topicRepository); - - var topic = TopicFactory.Create("Test", "Sample"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Test", "Sample"); topic.Attributes.SetValue("Property", "ValueA"); topic.Attributes.SetValue("PropertyAlias", "ValueB"); - var viewModel = (SampleTopicViewModel)mappingService.Map(topic); + var viewModel = (SampleTopicViewModel)mappingService.Map(topic); Assert.AreEqual("ValueA", viewModel.PropertyAlias); @@ -194,17 +192,17 @@ public void TopicMappingService_AlternateAttributeKey() { [TestMethod] public void TopicMappingService_MapRelationships() { - var mappingService = new TopicMappingService(_topicRepository); - var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); - var relatedTopic2 = TopicFactory.Create("RelatedTopic2", "Index"); - var relatedTopic3 = TopicFactory.Create("RelatedTopic3", "Page"); - var topic = TopicFactory.Create("Test", "Sample"); + var mappingService = new TopicMappingService(_topicRepository); + var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); + var relatedTopic2 = TopicFactory.Create("RelatedTopic2", "Index"); + var relatedTopic3 = TopicFactory.Create("RelatedTopic3", "Page"); + var topic = TopicFactory.Create("Test", "Sample"); topic.Relationships.SetTopic("Cousins", relatedTopic1); topic.Relationships.SetTopic("Cousins", relatedTopic2); topic.Relationships.SetTopic("Siblings", relatedTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)mappingService.Map(topic); Assert.AreEqual(2, target.Cousins.Count); Assert.IsNotNull(target.Cousins.FirstOrDefault((t) => t.Key.StartsWith("RelatedTopic1"))); @@ -320,24 +318,24 @@ public void TopicMappingService_MapChildren() { [TestMethod] public void TopicMappingService_RecursiveRelationships() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository); //Self - var topic = TopicFactory.Create("Test", "Sample"); + var topic = TopicFactory.Create("Test", "Sample"); //First cousins - var cousinTopic1 = TopicFactory.Create("CousinTopic1", "Page"); - var cousinTopic2 = TopicFactory.Create("CousinTopic2", "Index"); - var cousinTopic3 = TopicFactory.Create("CousinTopic3", "Sample"); + var cousinTopic1 = TopicFactory.Create("CousinTopic1", "Page"); + var cousinTopic2 = TopicFactory.Create("CousinTopic2", "Index"); + var cousinTopic3 = TopicFactory.Create("CousinTopic3", "Sample"); //Children of cousins - var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", cousinTopic3); - var childTopic2 = TopicFactory.Create("ChildTopic2", "Page", cousinTopic3); - var childTopic3 = TopicFactory.Create("ChildTopic3", "Sample", cousinTopic3); + var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", cousinTopic3); + var childTopic2 = TopicFactory.Create("ChildTopic2", "Page", cousinTopic3); + var childTopic3 = TopicFactory.Create("ChildTopic3", "Sample", cousinTopic3); //Other cousins - var secondCousin = TopicFactory.Create("SecondCousin", "Page"); - var cousinTwiceRemoved = TopicFactory.Create("CousinOnceRemoved", "Page", childTopic3); + var secondCousin = TopicFactory.Create("SecondCousin", "Page"); + var cousinTwiceRemoved = TopicFactory.Create("CousinOnceRemoved", "Page", childTopic3); //Set first cousins topic.Relationships.SetTopic("Cousins", cousinTopic1); @@ -347,9 +345,9 @@ public void TopicMappingService_RecursiveRelationships() { //Set ancillary relationships cousinTopic3.Relationships.SetTopic("Cousins", secondCousin); - var target = (SampleTopicViewModel)mappingService.Map(topic); - var cousinTarget = (SampleTopicViewModel)target.Cousins.FirstOrDefault((t) => t.Key.StartsWith("CousinTopic3")); - var distantCousinTarget = (SampleTopicViewModel)cousinTarget.Children.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic3")); + var target = (SampleTopicViewModel)mappingService.Map(topic); + var cousinTarget = (SampleTopicViewModel)target.Cousins.FirstOrDefault((t) => t.Key.StartsWith("CousinTopic3")); + var distantCousinTarget = (SampleTopicViewModel)cousinTarget.Children.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic3")); //Because Cousins is set to recurse over Children, its children should be set Assert.AreEqual(3, cousinTarget.Children.Count); @@ -373,15 +371,15 @@ public void TopicMappingService_RecursiveRelationships() { [TestMethod] public void TopicMappingService_MapSlideshow() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Test", "Slideshow"); - var slides = TopicFactory.Create("ContentItems", "List", topic); - var childTopic1 = TopicFactory.Create("ChildTopic1", "Slide", slides); - var childTopic2 = TopicFactory.Create("ChildTopic2", "Slide", slides); - var childTopic3 = TopicFactory.Create("ChildTopic3", "Slide", slides); - var childTopic4 = TopicFactory.Create("ChildTopic4", "ContentItem", slides); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Test", "Slideshow"); + var slides = TopicFactory.Create("ContentItems", "List", topic); + var childTopic1 = TopicFactory.Create("ChildTopic1", "Slide", slides); + var childTopic2 = TopicFactory.Create("ChildTopic2", "Slide", slides); + var childTopic3 = TopicFactory.Create("ChildTopic3", "Slide", slides); + var childTopic4 = TopicFactory.Create("ChildTopic4", "ContentItem", slides); - var target = (SlideshowTopicViewModel)mappingService.Map(topic); + var target = (SlideshowTopicViewModel)mappingService.Map(topic); Assert.AreEqual(4, target.ContentItems.Count); Assert.IsNotNull(target.ContentItems.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic1"))); @@ -432,10 +430,10 @@ public void TopicMappingService_MapTopics() { [TestMethod] public void TopicMappingService_MapMetadata() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Test", "MetadataLookup"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Test", "MetadataLookup"); - var target = (MetadataLookupTopicViewModel)mappingService.Map(topic); + var target = (MetadataLookupTopicViewModel)mappingService.Map(topic); Assert.AreEqual(5, target.Categories.Count); @@ -451,16 +449,16 @@ public void TopicMappingService_MapMetadata() { [TestMethod] public void TopicMappingService_FilterByContentType() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Test", "Sample"); - var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); - var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); - var childTopic3 = TopicFactory.Create("ChildTopic3", "Index", topic); - var childTopic4 = TopicFactory.Create("ChildTopic4", "Index", childTopic3); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Test", "Sample"); + var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); + var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); + var childTopic3 = TopicFactory.Create("ChildTopic3", "Index", topic); + var childTopic4 = TopicFactory.Create("ChildTopic4", "Index", childTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)mappingService.Map(topic); - var indexes = target.Children.GetByContentType("Index"); + var indexes = target.Children.GetByContentType("Index"); Assert.AreEqual(2, indexes.Count); Assert.IsNotNull(indexes.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic2"))); @@ -479,10 +477,10 @@ public void TopicMappingService_FilterByContentType() { [TestMethod] public void TopicMappingService_MapGetterMethods() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "Sample"); - var childTopic = TopicFactory.Create("Child", "Page", topic); - var grandChildTopic = TopicFactory.Create("GrandChild", "Index", childTopic); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "Sample"); + var childTopic = TopicFactory.Create("Child", "Page", topic); + var grandChildTopic = TopicFactory.Create("GrandChild", "Index", childTopic); var target = (IndexTopicViewModel)mappingService.Map(grandChildTopic); @@ -499,8 +497,8 @@ public void TopicMappingService_MapGetterMethods() { [TestMethod] public void TopicMappingService_MapRequiredProperty() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "Required"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "Required"); topic.Attributes.SetValue("RequiredAttribute", "Required"); @@ -520,8 +518,8 @@ public void TopicMappingService_MapRequiredProperty() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapRequiredPropertyException() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "Required"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "Required"); var target = (RequiredTopicViewModel)mappingService.Map(topic); @@ -537,8 +535,8 @@ public void TopicMappingService_MapRequiredPropertyException() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapRequiredObjectPropertyException() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "RequiredObject"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "RequiredObject"); var target = (RequiredTopicViewModel)mappingService.Map(topic); @@ -553,10 +551,10 @@ public void TopicMappingService_MapRequiredObjectPropertyException() { [TestMethod] public void TopicMappingService_MapDefaultValueProperties() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "DefaultValue"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "DefaultValue"); - var target = (DefaultValueTopicViewModel)mappingService.Map(topic); + var target = (DefaultValueTopicViewModel)mappingService.Map(topic); Assert.AreEqual("Default", target.DefaultString); Assert.AreEqual(10, target.DefaultInt); @@ -574,8 +572,8 @@ public void TopicMappingService_MapDefaultValueProperties() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapMinimumValueProperties() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Topic", "MinimumLengthProperty"); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Topic", "MinimumLengthProperty"); topic.Attributes.SetValue("MinimumLength", "Hello World"); @@ -594,12 +592,12 @@ public void TopicMappingService_MapMinimumValueProperties() { [TestMethod] public void TopicMappingService_FilterByAttribute() { - var mappingService = new TopicMappingService(_topicRepository); - var topic = TopicFactory.Create("Test", "Filtered"); - var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); - var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); - var childTopic3 = TopicFactory.Create("ChildTopic3", "Page", topic); - var childTopic4 = TopicFactory.Create("ChildTopic4", "Page", childTopic3); + var mappingService = new TopicMappingService(_topicRepository); + var topic = TopicFactory.Create("Test", "Filtered"); + var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); + var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); + var childTopic3 = TopicFactory.Create("ChildTopic3", "Page", topic); + var childTopic4 = TopicFactory.Create("ChildTopic4", "Page", childTopic3); childTopic1.Attributes.SetValue("SomeAttribute", "ValueA"); childTopic2.Attributes.SetValue("SomeAttribute", "ValueA"); From 339f87916f0afef38401264260bd5d10bde27a76 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 14:18:54 -0700 Subject: [PATCH 002/149] Fixed bug with mapping `Id` field Since the `Id` field isn't reflected in `Topic.Attributes`, it requires special handling. --- Ignia.Topics/Mapping/TopicMappingService.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 077753f1..225d30cd 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -328,6 +328,15 @@ private void SetProperty(Topic topic, object target, Relationships relationships } } + /*------------------------------------------------------------------------------------------------------------------------ + | Property: Identity + >------------------------------------------------------------------------------------------------------------------------- + | ### NOTE JJC043018: The identity property requires special handling since it isn't stored as an attribute on topic. + \-----------------------------------------------------------------------------------------------------------------------*/ + if (property.Name.Equals("Id", StringComparison.InvariantCultureIgnoreCase)) { + _typeCache.SetProperty(target, property.Name, topic.Id.ToString()); + } + /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ From 64e6ea23f23c39c540a7724cc30a29bb5a999f39 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 14:25:39 -0700 Subject: [PATCH 003/149] Introduced support for Reference Topics If a property name corresponds to an attribute named `{Property}Id`, that value points to a topic in the supplied `ITopicRepository`, and that topic maps to a type assignable to the property, then the property is populated with that mapped instance. --- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 22 +++++++++++++++++++ .../ViewModels/SampleTopicViewModel.cs | 2 ++ Ignia.Topics/Mapping/Relationships.cs | 3 ++- Ignia.Topics/Mapping/TopicMappingService.cs | 14 +++++++++++- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index 9f187e0b..585c1229 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -308,6 +308,28 @@ public void TopicMappingService_MapChildren() { } + /*========================================================================================================================== + | TEST: MAP TOPIC REFERENCES + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a and tests whether it successfully maps referenced topics. + /// + [TestMethod] + public void TopicMappingService_MapTopicReferences() { + + var mappingService = new TopicMappingService(_topicRepository); + + var topic = TopicFactory.Create("Test", "Sample"); + + topic.Attributes.SetInteger("TopicReferenceId", 11111); + + var target = (SampleTopicViewModel)mappingService.Map(topic); + + Assert.IsNotNull(target.TopicReference); + Assert.AreEqual(11111, target.TopicReference.Id); + + } + /*========================================================================================================================== | TEST: RECURSIVE RELATIONSHIPS \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs index b16b7ccf..1736d8b2 100644 --- a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs +++ b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs @@ -29,6 +29,8 @@ public class SampleTopicViewModel : PageTopicViewModel { [AttributeKey("Property")] public string PropertyAlias { get; set; } + public TopicViewModel TopicReference { get; set; } + [Recurse(Relationships.Relationships)] public TopicViewModelCollection Children { get; set; } diff --git a/Ignia.Topics/Mapping/Relationships.cs b/Ignia.Topics/Mapping/Relationships.cs index f2d7f5af..e4285fdd 100644 --- a/Ignia.Topics/Mapping/Relationships.cs +++ b/Ignia.Topics/Mapping/Relationships.cs @@ -26,7 +26,8 @@ public enum Relationships { Children = 1 << 1, Relationships = 1 << 2, IncomingRelationships = 1 << 3, - All = Parents | Children | Relationships | IncomingRelationships + References = 1 << 4, + All = Parents | Children | Relationships | IncomingRelationships | References #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 225d30cd..b404ab5a 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -268,6 +268,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships var crawlRelationships = Relationships.None; var metadataKey = (string)null; var attributeFilters = new Dictionary(); + var topicReferenceId = topic.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value @@ -340,7 +341,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ - if (_typeCache.HasSettableProperty(targetType, property.Name)) { + else if (_typeCache.HasSettableProperty(targetType, property.Name)) { var getterMethod = sourceType.GetRuntimeMethod("Get" + attributeKey, new Type[] { }); var attributeValue = (string)null; //Attempt to get value from topic.Get{Property}() @@ -469,6 +470,17 @@ private void SetProperty(Topic topic, object target, Relationships relationships } } + /*------------------------------------------------------------------------------------------------------------------------ + | Property: Topic Reference + \-----------------------------------------------------------------------------------------------------------------------*/ + else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { + var topicReference = _topicRepository.Load(topicReferenceId); + var viewModelReference = Map(topicReference, crawlRelationships); + if (property.PropertyType.IsAssignableFrom(viewModelReference.GetType())) { + property.SetValue(target, viewModelReference); + } + } + /*------------------------------------------------------------------------------------------------------------------------ | Validate fields \-----------------------------------------------------------------------------------------------------------------------*/ From 909da0caf3b98085da22a15d7fd052e88a356810 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 15:32:37 -0700 Subject: [PATCH 004/149] Established `Reflection` namespace Used to separate `private` reflection-oriented classes from `public` Topic-specific classes. --- Ignia.Topics/Collections/AttributeValueCollection.cs | 1 + Ignia.Topics/Ignia.Topics.csproj | 4 ++-- Ignia.Topics/Mapping/TopicMappingService.cs | 1 + .../{Collections => Reflection}/PropertyInfoCollection.cs | 2 +- Ignia.Topics/{Collections => Reflection}/TypeCollection.cs | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) rename Ignia.Topics/{Collections => Reflection}/PropertyInfoCollection.cs (98%) rename Ignia.Topics/{Collections => Reflection}/TypeCollection.cs (99%) diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index fb1f161e..1d5b1257 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -6,6 +6,7 @@ using System; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; +using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; namespace Ignia.Topics.Collections { diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 8e9195ce..e8bd68dc 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -137,8 +137,6 @@ - - @@ -154,6 +152,8 @@ + + diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index b404ab5a..8d1fc808 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Reflection; using Ignia.Topics.Collections; +using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; using Ignia.Topics.ViewModels; diff --git a/Ignia.Topics/Collections/PropertyInfoCollection.cs b/Ignia.Topics/Reflection/PropertyInfoCollection.cs similarity index 98% rename from Ignia.Topics/Collections/PropertyInfoCollection.cs rename to Ignia.Topics/Reflection/PropertyInfoCollection.cs index 46a8caa3..73727857 100644 --- a/Ignia.Topics/Collections/PropertyInfoCollection.cs +++ b/Ignia.Topics/Reflection/PropertyInfoCollection.cs @@ -8,7 +8,7 @@ using System.Diagnostics.Contracts; using System.Reflection; -namespace Ignia.Topics.Collections { +namespace Ignia.Topics.Reflection { /*============================================================================================================================ | CLASS: PROPERTY INFO COLLECTION diff --git a/Ignia.Topics/Collections/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs similarity index 99% rename from Ignia.Topics/Collections/TypeCollection.cs rename to Ignia.Topics/Reflection/TypeCollection.cs index ac0adb59..d71021dc 100644 --- a/Ignia.Topics/Collections/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -9,7 +9,7 @@ using System.Diagnostics.Contracts; using System.Reflection; -namespace Ignia.Topics.Collections { +namespace Ignia.Topics.Reflection { /*============================================================================================================================ | CLASS: TYPE COLLECTION From a01abfeae6934289f18fa193497232a044799047 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 15:33:41 -0700 Subject: [PATCH 005/149] Fixed reference to `TypeCollection` --- Ignia.Topics.Tests/TypeCollectionTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index e76e5243..ebefc664 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -5,6 +5,7 @@ \=============================================================================================================================*/ using System; using Ignia.Topics.Collections; +using Ignia.Topics.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Ignia.Topics.Tests { From c4ac02a3ed54eb625729527343c06231bceec68a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 16:44:02 -0700 Subject: [PATCH 006/149] Abstracted out `MemberInfoCollection{T}` Instead of maintaining a `PropertyInfoCollection`, abstracted it out into a generic `MemberInfoCollection{T}`, and migrated dependencies to use this. Additionally included a non-generic `MemberInfoCollection` for convenience. --- Ignia.Topics.Tests/TypeCollectionTest.cs | 11 ++--- Ignia.Topics/Ignia.Topics.csproj | 3 +- Ignia.Topics/Mapping/TopicMappingService.cs | 2 +- .../Reflection/MemberInfoCollection.cs | 44 +++++++++++++++++++ ...llection.cs => MemberInfoCollection{T}.cs} | 41 ++++++++++++----- Ignia.Topics/Reflection/TypeCollection.cs | 38 +++++++++++----- 6 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 Ignia.Topics/Reflection/MemberInfoCollection.cs rename Ignia.Topics/Reflection/{PropertyInfoCollection.cs => MemberInfoCollection{T}.cs} (66%) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index ebefc664..adc75697 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -4,6 +4,7 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Reflection; using Ignia.Topics.Collections; using Ignia.Topics.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -14,7 +15,7 @@ namespace Ignia.Topics.Tests { | CLASS: TYPE COLLECTION TESTS \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides unit tests for the and classes. + /// Provides unit tests for the and classes. /// /// /// These are internal collections and not accessible publicly. @@ -26,13 +27,13 @@ public class TypeCollectionTest { | TEST: PROPERTY INFO COLLECTION CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a based on a type, and confirms that the property collection is + /// Establishes a based on a type, and confirms that the property collection is /// returning expected types. /// [TestMethod] public void PropertyInfoCollection_ConstructorTest() { - var properties = new PropertyInfoCollection(typeof(ContentTypeDescriptor)); + var properties = new MemberInfoCollection(typeof(ContentTypeDescriptor)); Assert.IsTrue(properties.Contains("Key")); //Inherited string property Assert.IsTrue(properties.Contains("AttributeDescriptors")); //First class collection property @@ -45,7 +46,7 @@ public void PropertyInfoCollection_ConstructorTest() { | TEST: GET TYPE \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a and confirms that + /// Establishes a and confirms that /// functions. /// [TestMethod] @@ -53,7 +54,7 @@ public void TypeCollection_GetPropertiesTest() { var types = new TypeCollection(); - var properties = types.GetProperties(typeof(ContentTypeDescriptor)); + var properties = types.GetMembers(typeof(ContentTypeDescriptor)); Assert.IsTrue(properties.Contains("Key")); Assert.IsTrue(properties.Contains("AttributeDescriptors")); diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index e8bd68dc..5b2975da 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -152,7 +152,8 @@ - + + diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 8d1fc808..db535f1a 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -232,7 +232,7 @@ public object Map(Topic topic, object target, Relationships relationships = Rela /*------------------------------------------------------------------------------------------------------------------------ | Loop through properties, mapping each one \-----------------------------------------------------------------------------------------------------------------------*/ - foreach (var property in _typeCache.GetProperties(target.GetType())) { + foreach (var property in _typeCache.GetMembers(target.GetType())) { SetProperty(topic, target, relationships, property); } diff --git a/Ignia.Topics/Reflection/MemberInfoCollection.cs b/Ignia.Topics/Reflection/MemberInfoCollection.cs new file mode 100644 index 00000000..f130f441 --- /dev/null +++ b/Ignia.Topics/Reflection/MemberInfoCollection.cs @@ -0,0 +1,44 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Ignia.Topics.Reflection { + + /*============================================================================================================================ + | CLASS: MEMBER INFO COLLECTION + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides keyed access to a collection of instances. + /// + internal class MemberInfoCollection : MemberInfoCollection { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Initializes a new instance of the class associated with a + /// name. + /// + /// The associated with the collection. + internal MemberInfoCollection(Type type) : base(type) { + } + + /// + /// Initializes a new instance of the class associated with a + /// name and prepopulates it with a predetermined set of instances. + /// + /// The associated with the collection. + /// + /// An of instances to populate the collection. + /// + internal MemberInfoCollection(Type type, IEnumerable members) : base(type, members) { + } + + } //Class + +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Reflection/PropertyInfoCollection.cs b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs similarity index 66% rename from Ignia.Topics/Reflection/PropertyInfoCollection.cs rename to Ignia.Topics/Reflection/MemberInfoCollection{T}.cs index 73727857..f04d069f 100644 --- a/Ignia.Topics/Reflection/PropertyInfoCollection.cs +++ b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs @@ -4,46 +4,65 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; +using System.Linq; using System.Reflection; namespace Ignia.Topics.Reflection { /*============================================================================================================================ - | CLASS: PROPERTY INFO COLLECTION + | CLASS: MEMBER INFO COLLECTION {T} \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides keyed access to a collection of instances. + /// Provides keyed access to a collection of instances. /// - public class PropertyInfoCollection : KeyedCollection { + internal class MemberInfoCollection : KeyedCollection where T : MemberInfo { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - Type _type = null; + Type _type = null; /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Initializes a new instance of the class associated with a + /// Initializes a new instance of the class associated with a /// name. /// /// The associated with the collection. - public PropertyInfoCollection(Type type) : base(StringComparer.OrdinalIgnoreCase) { + internal MemberInfoCollection(Type type) : base(StringComparer.OrdinalIgnoreCase) { Contract.Requires(type != null); _type = type; foreach ( - var property - in type.GetProperties( + var member + in type.GetMembers( BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public - ) + ).Where(m => typeof(T).IsAssignableFrom(m.GetType())) ) { - Add(property); + Add((T)member); + } + } + + /// + /// Initializes a new instance of the class associated with a + /// name and prepopulates it with a predetermined set of instances. + /// + /// The associated with the collection. + /// + /// An of instances to populate the collection. + /// + internal MemberInfoCollection(Type type, IEnumerable members) : base(StringComparer.OrdinalIgnoreCase) { + Contract.Requires(type != null); + Contract.Requires(members != null); + _type = type; + foreach (var member in members) { + Add(member); } } @@ -63,7 +82,7 @@ in type.GetProperties( /// /// The object from which to extract the key. /// The key for the specified collection item. - protected override string GetKeyForItem(PropertyInfo item) { + protected override string GetKeyForItem(T item) { Contract.Assume(item != null, "Assumes the item is available when deriving its key."); return item.Name; } diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index d71021dc..d7cdea92 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; +using System.Linq; using System.Reflection; namespace Ignia.Topics.Reflection { @@ -15,15 +16,16 @@ namespace Ignia.Topics.Reflection { | CLASS: TYPE COLLECTION \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// A collection of instances, each associated with a specific . + /// A collection of instances, each associated with a specific . /// - internal class TypeCollection : KeyedCollection { + internal class TypeCollection : KeyedCollection { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ static List _settableTypes = null; private Type _attributeFlag = null; + /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -38,22 +40,36 @@ internal TypeCollection(Type attributeFlag = null) : base() { } /*========================================================================================================================== - | METHOD: GET PROPERTIES + | METHOD: GET MEMBERS \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Returns a collection of objects associated with a specific type. + /// Returns a collection of objects associated with a specific type. /// /// /// If the collection cannot be found locally, it will be created. /// - /// The type for which the properties should be retrieved. - internal PropertyInfoCollection GetProperties(Type type) { + /// The type for which the members should be retrieved. + internal MemberInfoCollection GetMembers(Type type) { if (!Contains(type)) { - Add(new PropertyInfoCollection(type)); + Add(new MemberInfoCollection(type)); } return this[type]; } + /*========================================================================================================================== + | METHOD: GET MEMBERS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Returns a collection of objects associated with a specific type. + /// + /// + /// If the collection cannot be found locally, it will be created. + /// + /// The type for which the members should be retrieved. + internal MemberInfoCollection GetMembers(Type type) where T: MemberInfo { + return new MemberInfoCollection(type, GetMembers(type).Where(m => typeof(T).IsAssignableFrom(m.GetType())).Cast()); + } + /*========================================================================================================================== | METHOD: GET PROPERTY \-------------------------------------------------------------------------------------------------------------------------*/ @@ -62,9 +78,9 @@ internal PropertyInfoCollection GetProperties(Type type) { /// instance. /// internal PropertyInfo GetProperty(Type type, string name) { - var properties = GetProperties(type); - if (properties.Contains(name)) { - return properties[name]; + var properties = GetMembers(type); + if (properties.Contains(name) && properties[name].MemberType.Equals(MemberTypes.Property)) { + return properties[name] as PropertyInfo; } return null; } @@ -168,7 +184,7 @@ internal List SettableTypes { /// /// The object from which to extract the key. /// The key for the specified collection item. - protected override Type GetKeyForItem(PropertyInfoCollection item) { + protected override Type GetKeyForItem(MemberInfoCollection item) { Contract.Assume(item != null, "Assumes the item is available when deriving its key."); return item.Type; } From 4d3928b851dc8b1fa27c437ccffdfe111b04490e Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 18:33:45 -0700 Subject: [PATCH 007/149] Converted `GetMember` and `HasMember` Converted `GetProperty()` to `GetMember()` and generic `GetMember()`; converted `HasProperty()` to `HasMember()` and `HasMember()`. --- Ignia.Topics.Tests/TypeCollectionTest.cs | 16 ++++---- Ignia.Topics/Reflection/TypeCollection.cs | 49 ++++++++++++++++------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index adc75697..e6502948 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -64,21 +64,23 @@ public void TypeCollection_GetPropertiesTest() { } /*========================================================================================================================== - | TEST: GET PROPERTY + | TEST: GET MEMBER \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a and confirms that + /// Establishes a and confirms that /// correctly returns the expected properties. /// [TestMethod] - public void TypeCollection_GetPropertyTest() { + public void TypeCollection_GetMemberTest() { var types = new TypeCollection(); - Assert.IsTrue(types.GetProperty(typeof(ContentTypeDescriptor), "Key") != null); - Assert.IsTrue(types.GetProperty(typeof(ContentTypeDescriptor), "AttributeDescriptors") != null); - Assert.IsFalse(types.GetProperty(typeof(ContentTypeDescriptor), "IsTypeOf") != null); - Assert.IsFalse(types.GetProperty(typeof(ContentTypeDescriptor), "InvalidPropertyName") != null); + Assert.IsTrue(types.GetMember(typeof(ContentTypeDescriptor), "Key") != null); + Assert.IsTrue(types.GetMember(typeof(ContentTypeDescriptor), "AttributeDescriptors") != null); + Assert.IsFalse(types.GetMember(typeof(ContentTypeDescriptor), "IsTypeOf") != null); + Assert.IsFalse(types.GetMember(typeof(ContentTypeDescriptor), "InvalidPropertyName") != null); + Assert.IsTrue(types.GetMember(typeof(ContentTypeDescriptor), "GetWebPath") != null); + Assert.IsFalse(types.GetMember(typeof(ContentTypeDescriptor), "AttributeDescriptors") != null); } diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index d7cdea92..34913f4e 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -57,7 +57,7 @@ internal MemberInfoCollection GetMembers(Type type) { } /*========================================================================================================================== - | METHOD: GET MEMBERS + | METHOD: GET MEMBERS {T} \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Returns a collection of objects associated with a specific type. @@ -71,27 +71,50 @@ internal MemberInfoCollection GetMembers(Type type) where T: MemberInfo { } /*========================================================================================================================== - | METHOD: GET PROPERTY + | METHOD: GET MEMBER + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Used reflection to identify a local member by a given name, and returns the associated + /// instance. + /// + internal MemberInfo GetMember(Type type, string name) { + var members = GetMembers(type); + if (members.Contains(name)) { + return members[name]; + } + return null; + } + + /*========================================================================================================================== + | METHOD: GET MEMBER {T} \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Used reflection to identify a local property by a given name, and returns the associated + /// Used reflection to identify a local member by a given name, and returns the associated /// instance. /// - internal PropertyInfo GetProperty(Type type, string name) { - var properties = GetMembers(type); - if (properties.Contains(name) && properties[name].MemberType.Equals(MemberTypes.Property)) { - return properties[name] as PropertyInfo; + internal T GetMember(Type type, string name) where T : MemberInfo { + var members = GetMembers(type); + if (members.Contains(name) && typeof(T).IsAssignableFrom(members[name].GetType())) { + return members[name] as T; } return null; } /*========================================================================================================================== - | METHOD: HAS PROPERTY + | METHOD: HAS MEMBER + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Used reflection to identify if a local member is available. + /// + internal bool HasMember(Type type, string name) => GetMember(type, name) != null; + + /*========================================================================================================================== + | METHOD: HAS MEMBER {T} \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Used reflection to identify if a local property is available. + /// Used reflection to identify if a local member of type is available. /// - internal bool HasProperty(Type type, string name) => GetProperty(type, name) != null; + internal bool HasMember(Type type, string name) where T: MemberInfo => GetMember(type, name) != null; /*========================================================================================================================== | METHOD: HAS SETTABLE PROPERTY @@ -103,7 +126,7 @@ internal PropertyInfo GetProperty(Type type, string name) { /// Will return false if the property is not available. /// internal bool HasSettableProperty(Type type, string name) { - var property = GetProperty(type, name); + var property = GetMember(type, name); return ( property != null && property.CanWrite && @@ -125,9 +148,7 @@ internal bool SetProperty(object target, string name, string value) { return false; } - var property = GetProperty(target.GetType(), name); - - object valueObject = null; + var property = GetMember(target.GetType(), name); Contract.Assume(property != null); From 63b02b10c034773baf1342a1d905f60c42e6e2f1 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 18:36:41 -0700 Subject: [PATCH 008/149] Renamed `SetProperty()` to `SetPropertyValue()` This is more explicit, and will provide better consistency with future members (such as the proposed `SetMethodValue()` and `GetMethodValue()`). --- Ignia.Topics.Tests/TypeCollectionTest.cs | 14 +++++++------- .../Collections/AttributeValueCollection.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 4 ++-- Ignia.Topics/Reflection/TypeCollection.cs | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index e6502948..993419a1 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -97,13 +97,13 @@ public void TypeCollection_SetPropertyTest() { var types = new TypeCollection(); var topic = TopicFactory.Create("Test", "ContentType"); - types.SetProperty(topic, "IsHidden", "1"); + types.SetPropertyValue(topic, "IsHidden", "1"); - var isDateSet = types.SetProperty(topic, "LastModified", "June 3, 2008"); - isDateSet = types.SetProperty(topic, "LastModified", "2008-06-03") && isDateSet; - isDateSet = types.SetProperty(topic, "LastModified", "06/03/2008") && isDateSet; - var isKeySet = types.SetProperty(topic, "Key", "NewKey"); - var isInvalidPropertySet = types.SetProperty(topic, "InvalidProperty", "Invalid"); + var isDateSet = types.SetPropertyValue(topic, "LastModified", "June 3, 2008"); + isDateSet = types.SetPropertyValue(topic, "LastModified", "2008-06-03") && isDateSet; + isDateSet = types.SetPropertyValue(topic, "LastModified", "06/03/2008") && isDateSet; + var isKeySet = types.SetPropertyValue(topic, "Key", "NewKey"); + var isInvalidPropertySet = types.SetPropertyValue(topic, "InvalidProperty", "Invalid"); Assert.IsTrue(isDateSet); Assert.IsTrue(isKeySet); @@ -135,7 +135,7 @@ public void TypeCollection_ReflectionPerformanceTest() { var i = 0; for (i = 0; i < totalIterations; i++) { - types.SetProperty(topic, "Key", "Key" + i); + types.SetPropertyValue(topic, "Key", "Key" + i); } Assert.AreEqual("Key" + (i-1), topic.Key); diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index 1d5b1257..a27c9cfa 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -539,7 +539,7 @@ private bool EnforceBusinessLogic(AttributeValue originalAttribute, out Attribut "`; be sure to call `Topic.SetAttributeValue()` when setting attributes from `Topic` properties." ); } - _typeCache.SetProperty(_associatedTopic, originalAttribute.Key, originalAttribute.Value); + _typeCache.SetPropertyValue(_associatedTopic, originalAttribute.Key, originalAttribute.Value); this[originalAttribute.Key].IsDirty = originalAttribute.IsDirty; _setCounter = 0; return false; diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index db535f1a..e21b87d4 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -336,7 +336,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships | ### NOTE JJC043018: The identity property requires special handling since it isn't stored as an attribute on topic. \-----------------------------------------------------------------------------------------------------------------------*/ if (property.Name.Equals("Id", StringComparison.InvariantCultureIgnoreCase)) { - _typeCache.SetProperty(target, property.Name, topic.Id.ToString()); + _typeCache.SetPropertyValue(target, property.Name, topic.Id.ToString()); } /*------------------------------------------------------------------------------------------------------------------------ @@ -354,7 +354,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships attributeValue = topic.Attributes.GetValue(attributeKey, defaultValue, inheritValue); } if (attributeValue != null) { - _typeCache.SetProperty(target, property.Name, attributeValue); + _typeCache.SetPropertyValue(target, property.Name, attributeValue); } } diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 34913f4e..4fdb7723 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -136,13 +136,13 @@ internal bool HasSettableProperty(Type type, string name) { } /*========================================================================================================================== - | METHOD: SET PROPERTY + | METHOD: SET PROPERTY VALUE \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Uses reflection to call a property, assuming that it is a) writable, and b) of type , /// , or . /// - internal bool SetProperty(object target, string name, string value) { + internal bool SetPropertyValue(object target, string name, string value) { if (!HasSettableProperty(target.GetType(), name)) { return false; @@ -181,7 +181,7 @@ internal bool SetProperty(object target, string name, string value) { | PROPERTY: SETTABLE TYPES \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// A list of types that are allowed to be set using . + /// A list of types that are allowed to be set using . /// internal List SettableTypes { get { From f7e32fe75509b50cfa389c0cda5a9c811b3ad16f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 18:39:18 -0700 Subject: [PATCH 009/149] Established `HasSettableMethod()`, `SetMethodValue()`, `GetMethodValue()`, These provide counterparts to `HasSettableProperty()` and `SetPropertyValue()` for use with method-setters. Assumes form of `void Set{Property}({type} {value})` where `{type}` is one of the `SettableTypes`. --- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + Ignia.Topics.Tests/TypeCollectionTest.cs | 26 +++++ .../ViewModels/MethodBasedViewModel.cs | 28 +++++ Ignia.Topics/Reflection/TypeCollection.cs | 103 ++++++++++++++++-- 4 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 Ignia.Topics.Tests/ViewModels/MethodBasedViewModel.cs diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index 58dd991a..c16cac73 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -114,6 +114,7 @@ + diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index 993419a1..f8535ee6 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -7,6 +7,7 @@ using System.Reflection; using Ignia.Topics.Collections; using Ignia.Topics.Reflection; +using Ignia.Topics.Tests.ViewModels; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Ignia.Topics.Tests { @@ -114,6 +115,31 @@ public void TypeCollection_SetPropertyTest() { } + /*========================================================================================================================== + | TEST: SET MEMBER + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a and confirms that a value can be properly set using the + /// method. + /// + [TestMethod] + public void TypeCollection_SetMemberTest() { + + var types = new TypeCollection(); + var source = new MethodBasedViewModel(); + + var isValueSet = types.SetMethodValue(source, "SetMethod", "123"); + var isInvalidSet = types.SetMethodValue(source, "BogusMethod", "123"); + + var value = types.GetMethodValue(source, "GetMethod"); + + Assert.IsTrue(isValueSet); + Assert.IsFalse(isInvalidSet); + Assert.IsTrue(value is int); + Assert.AreEqual(123, (int)value); + + } + /*========================================================================================================================== | TEST: REFLECTION PERFORMANCE \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.Tests/ViewModels/MethodBasedViewModel.cs b/Ignia.Topics.Tests/ViewModels/MethodBasedViewModel.cs new file mode 100644 index 00000000..5919328a --- /dev/null +++ b/Ignia.Topics.Tests/ViewModels/MethodBasedViewModel.cs @@ -0,0 +1,28 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System.ComponentModel; +using Ignia.Topics.ViewModels; + +namespace Ignia.Topics.Tests.ViewModels { + + /*============================================================================================================================ + | VIEW MODEL: METHOD BASED + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides a strongly-typed data transfer object for testing settable methods and gettable methods. + /// + /// + /// This is a sample class intended for test purposes only; it is not designed for use in a production environment. + /// + public class MethodBasedViewModel { + + private int _methodValue = 0; + + public void SetMethod(int methodValue) => _methodValue = methodValue; + public int GetMethod() => _methodValue; + + } //Class +} //Namespace diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 4fdb7723..d7b9c661 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -152,28 +152,111 @@ internal bool SetPropertyValue(object target, string name, string value) { Contract.Assume(property != null); - if (property.PropertyType.Equals(typeof(bool))) { + object valueObject = GetValueObject(property.PropertyType, value); + + if (valueObject == null) { + return false; + } + + property.SetValue(target, valueObject); + return true; + + } + + /*========================================================================================================================== + | METHOD: HAS SETTABLE METHOD + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Used reflection to identify if a local method is available and settable. + /// + /// + /// Will return false if the method is not available. Methods are only considered settable if they have one parameter of + /// a settable type. + /// + internal bool HasSettableMethod(Type type, string name) { + var method = GetMember(type, name); + return ( + method != null && + method.GetParameters().Count().Equals(1) && + SettableTypes.Contains(method.GetParameters().First().ParameterType) && + (_attributeFlag == null || System.Attribute.IsDefined(method, _attributeFlag)) + ); + } + + /*========================================================================================================================== + | METHOD: SET METHOD VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Uses reflection to call a method, assuming that it is a) writable, and b) of type , + /// , or . + /// + internal bool SetMethodValue(object target, string name, string value) { + + if (!HasSettableMethod(target.GetType(), name)) { + return false; + } + + var method = GetMember(target.GetType(), name); + + Contract.Assume(method != null); + + object valueObject = GetValueObject(method.GetParameters().First().ParameterType, value); + + if (valueObject == null) { + return false; + } + + method.Invoke(target, new object[] {valueObject}); + + return true; + + } + + /*========================================================================================================================== + | METHOD: GET METHOD VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Uses reflection to call a method, assuming that it has no parameters. + /// + internal object GetMethodValue(object target, string name, Type type = null) { + + var getter = GetMember(target.GetType(), name); + + if (getter != null && getter.GetParameters().Length.Equals(0)) { + return (object)getter.Invoke(target, new object[] { }); + } + + return null; + + } + + /*========================================================================================================================== + | METHOD: GET VALUE OBJECT + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Converts a string value to an object of the target type. + /// + private object GetValueObject(Type type, string value) { + + var valueObject = (object)null; + + if (type.Equals(typeof(bool))) { valueObject = value.Equals("1") || value.Equals("true", StringComparison.InvariantCultureIgnoreCase); } - else if (property.PropertyType.Equals(typeof(int))) { + else if (type.Equals(typeof(int))) { Int32.TryParse(value, out var intValue); valueObject = intValue; } - else if (property.PropertyType.Equals(typeof(string))) { + else if (type.Equals(typeof(string))) { valueObject = value; } - else if (property.PropertyType.Equals(typeof(DateTime))) { + else if (type.Equals(typeof(DateTime))) { if (DateTime.TryParse(value, out var date)) { valueObject = date; } } - if (valueObject == null) { - return false; - } - - property.SetValue(target, valueObject); - return true; + return valueObject; } From 851762d97dc6ad26fef8458d5a6a863b952819b0 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 18:42:51 -0700 Subject: [PATCH 010/149] Removed unused overload parameter --- Ignia.Topics/Reflection/TypeCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index d7b9c661..d206162e 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -218,7 +218,7 @@ internal bool SetMethodValue(object target, string name, string value) { /// /// Uses reflection to call a method, assuming that it has no parameters. /// - internal object GetMethodValue(object target, string name, Type type = null) { + internal object GetMethodValue(object target, string name) { var getter = GetMember(target.GetType(), name); From 8211fab213dff72e17a7fee727d1f11aa3113e54 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 18:49:04 -0700 Subject: [PATCH 011/149] Updated to use new `GetMethodValue()` Previously, `TopicMappingService` made a reflection call to `GetRuntimeMethod()` each time it came across a `HasSettableProperty()`. This has been updated to use the new `GetMethodValue()`, which reduces the amount of code (slightly) and, more importantly, improves performance by caching reflection data for methods. --- Ignia.Topics/Mapping/TopicMappingService.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index e21b87d4..3cfbc81c 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -343,12 +343,8 @@ private void SetProperty(Topic topic, object target, Relationships relationships | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ else if (_typeCache.HasSettableProperty(targetType, property.Name)) { - var getterMethod = sourceType.GetRuntimeMethod("Get" + attributeKey, new Type[] { }); - var attributeValue = (string)null; //Attempt to get value from topic.Get{Property}() - if (getterMethod != null) { - attributeValue = getterMethod.Invoke(topic, new object[] { }).ToString(); - } + var attributeValue = _typeCache.GetMethodValue(topic, "Get" + property.Name) as string; //Otherwise, attempts to get value from topic.Attributes.GetValue({Property}) if (String.IsNullOrEmpty(attributeValue)) { attributeValue = topic.Attributes.GetValue(attributeKey, defaultValue, inheritValue); From a354a0072d9916e33845e51bfaa38cf7a8a9df70 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 19:54:06 -0700 Subject: [PATCH 012/149] Introduced internal cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The internal cache is intended to help avoid scenarios where the same topic is referenced from multiple places within a mapped object graph. This will avoid wasting resources (by mapping the same object)—and, potentially, avoid infinite loops in some (admittedly outside) scenarios. --- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + Ignia.Topics.Tests/TopicMappingServiceTest.cs | 21 ++++ .../ViewModels/CircularTopicViewModel.cs | 30 ++++++ Ignia.Topics/Mapping/TopicMappingService.cs | 100 +++++++++++++++--- 4 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index c16cac73..d583de26 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -112,6 +112,7 @@ + diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index 585c1229..fad4faea 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -461,6 +461,27 @@ public void TopicMappingService_MapMetadata() { } + /*========================================================================================================================== + | TEST: MAP CIRCULAR REFERENCE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a and tests whether it successfully handles a circular reference by + /// taking advantage of its internal caching mechanism. + /// + [TestMethod] + public void TopicMappingService_MapCircularReference() { + + var mappingService = new TopicMappingService(_topicRepository); + + var topic = TopicFactory.Create("Test", "Circular", 1); + var childTopic = TopicFactory.Create("ChildTopic", "Circular", 2, topic); + + var mappedTopic = (CircularTopicViewModel)mappingService.Map(topic); + + Assert.AreEqual(mappedTopic, mappedTopic.Children.First().Parent); + + } + /*========================================================================================================================== | TEST: FILTER BY CONTENT TYPE \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs new file mode 100644 index 00000000..8afbe1b8 --- /dev/null +++ b/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs @@ -0,0 +1,30 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Ignia.Topics.Mapping; + +namespace Ignia.Topics.Tests.ViewModels { + + /*============================================================================================================================ + | VIEW MODEL: CIRCULAR TOPIC + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides a strongly-typed data transfer object for testing views with a circular reference. + /// + /// + /// This is a sample class intended for test purposes only; it is not designed for use in a production environment. + /// + public class CircularTopicViewModel { + + [Recurse(Relationships.Parents)] + public CircularTopicViewModel Parent { get; set; } + + [Recurse(Relationships.Children | Relationships.Parents)] + public List Children { get; set; } + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 3cfbc81c..53006169 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -11,7 +11,6 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; -using Ignia.Topics.Collections; using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; using Ignia.Topics.ViewModels; @@ -154,12 +153,48 @@ private static Type GetViewModelType(string contentType) { /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. public object Map(Topic topic, Relationships relationships = Relationships.All) { + return Map(topic, relationships, new Dictionary()); + + } + + /// + /// Given a topic, will identify any View Models named, by convention, "{ContentType}TopicViewModel" and populate them + /// according to the rules of the mapping implementation. + /// + /// + /// + /// Because the class is using reflection to determine the target View Models, the return type is . + /// These results may need to be cast to a specific type, depending on the context. That said, strongly-typed views + /// should be able to cast the object to the appropriate View Model type. If the type of the View Model is known + /// upfront, and it is imperative that it be strongly-typed, then prefer . + /// + /// + /// Because the target object is being dynamically constructed, it must implement a default constructor. + /// + /// + /// This internal version passes a private cache of mapped objects from this run. This helps prevent problems with + /// recursion in case is referred to multiple times (e.g., a Children collection with + /// set to include ). + /// + /// + /// The entity to derive the data from. + /// Determines what relationships the mapping should follow, if any. + /// A cache to keep track of already-mapped object instances. + /// An instance of the dynamically determined View Model with properties appropriately mapped. + private object Map(Topic topic, Relationships relationships, Dictionary cache) { /*---------------------------------------------------------------------------------------------------------------------- | Handle null source \---------------------------------------------------------------------------------------------------------------------*/ if (topic == null) return null; + /*------------------------------------------------------------------------------------------------------------------------ + | Handle cached objects + \-----------------------------------------------------------------------------------------------------------------------*/ + if (cache.ContainsKey(topic.Id)) { + return cache[topic.Id]; + } + /*---------------------------------------------------------------------------------------------------------------------- | Instantiate object \---------------------------------------------------------------------------------------------------------------------*/ @@ -170,7 +205,12 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /*---------------------------------------------------------------------------------------------------------------------- | Provide mapping \---------------------------------------------------------------------------------------------------------------------*/ - return Map(topic, target, relationships); + var mappedTarget = Map(topic, target, relationships, cache); + + /*---------------------------------------------------------------------------------------------------------------------- + | Provide mapping + \---------------------------------------------------------------------------------------------------------------------*/ + return mappedTarget; } @@ -214,6 +254,25 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// The target view model with the properties appropriately mapped. /// public object Map(Topic topic, object target, Relationships relationships = Relationships.All) { + return Map(topic, target, relationships, new Dictionary()); + } + + /// + /// Given a topic and an instance of a DTO, will populate the DTO according to the default mapping rules. + /// + /// The entity to derive the data from. + /// The target object to map the data to. + /// Determines what relationships the mapping should follow, if any. + /// A cache to keep track of already-mapped object instances. + /// + /// This internal version passes a private cache of mapped objects from this run. This helps prevent problems with + /// recursion in case is referred to multiple times (e.g., a Children collection with + /// set to include ). + /// + /// + /// The target view model with the properties appropriately mapped. + /// + private object Map(Topic topic, object target, Relationships relationships, Dictionary cache) { /*------------------------------------------------------------------------------------------------------------------------ | Validate input @@ -229,11 +288,25 @@ public object Map(Topic topic, object target, Relationships relationships = Rela return topic; } + /*------------------------------------------------------------------------------------------------------------------------ + | Handle cached objects + \-----------------------------------------------------------------------------------------------------------------------*/ + if (cache.ContainsKey(topic.Id)) { + return cache[topic.Id]; + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Cache results + \-----------------------------------------------------------------------------------------------------------------------*/ + if (topic.Id > 0 && !cache.ContainsKey(topic.Id)) { + cache.Add(topic.Id, target); + } + /*------------------------------------------------------------------------------------------------------------------------ | Loop through properties, mapping each one \-----------------------------------------------------------------------------------------------------------------------*/ foreach (var property in _typeCache.GetMembers(target.GetType())) { - SetProperty(topic, target, relationships, property); + SetProperty(topic, target, relationships, property, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -254,7 +327,14 @@ public object Map(Topic topic, object target, Relationships relationships = Rela /// The target object to map the data to. /// Determines what relationships the mapping should follow, if any. /// Information related to the current property. - private void SetProperty(Topic topic, object target, Relationships relationships, PropertyInfo property) { + /// A cache to keep track of already-mapped object instances. + private void SetProperty( + Topic topic, + object target, + Relationships relationships, + PropertyInfo property, + Dictionary cache + ) { /*------------------------------------------------------------------------------------------------------------------------ | Establish per-property variables @@ -437,10 +517,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships } //Otherwise, assume the list type is a DTO else { - var childDto = Map( - childTopic, - crawlRelationships - ); + var childDto = Map(childTopic, crawlRelationships, cache); //Ensure the mapped type derives from the list type if (listType.IsAssignableFrom(childDto.GetType())) { list.Add(childDto); @@ -457,10 +534,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships \-----------------------------------------------------------------------------------------------------------------------*/ else if (attributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { if (topic.Parent != null) { - var parent = Map( - topic.Parent, - crawlRelationships - ); + var parent = Map(topic.Parent, crawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(parent.GetType())) { property.SetValue(target, parent); } @@ -472,7 +546,7 @@ private void SetProperty(Topic topic, object target, Relationships relationships \-----------------------------------------------------------------------------------------------------------------------*/ else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); - var viewModelReference = Map(topicReference, crawlRelationships); + var viewModelReference = Map(topicReference, crawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(viewModelReference.GetType())) { property.SetValue(target, viewModelReference); } From 862dafe94cc42bf8d8e317e10618305000b913ae Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 30 Apr 2018 19:54:23 -0700 Subject: [PATCH 013/149] Fixed XmlDoc references --- Ignia.Topics.Tests/TypeCollectionTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index f8535ee6..7002d248 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -90,7 +90,7 @@ public void TypeCollection_GetMemberTest() { \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Establishes a and confirms that a value can be properly set using the - /// method. + /// method. /// [TestMethod] public void TypeCollection_SetPropertyTest() { @@ -120,7 +120,7 @@ public void TypeCollection_SetPropertyTest() { \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Establishes a and confirms that a value can be properly set using the - /// method. + /// method. /// [TestMethod] public void TypeCollection_SetMemberTest() { From 87abbce6db4752070b523aa640698a4f9be9d74c Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 10:26:52 -0700 Subject: [PATCH 014/149] Fixed typo in XmlDoc --- Ignia.Topics/Mapping/TopicMappingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 53006169..72a1e09c 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -385,7 +385,7 @@ Dictionary cache } /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine recusion settings + | Attributes: Determine recursion settings \-----------------------------------------------------------------------------------------------------------------------*/ var recurseAttribute = (RecurseAttribute)property.GetCustomAttribute(typeof(RecurseAttribute), true); if (recurseAttribute != null) { From a19d109b9548ed569075c1421eb378a156620224 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 10:32:34 -0700 Subject: [PATCH 015/149] Fixed string conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is fully expected that `GetMethodValue()` might return an `int` or `bool` or `DateTime`. With `as string` those would be treated as `null`. Instead, converted to `ToString()`. Negatively, this means reference objects might be converted to their default `ToString()` behavior—but that should be an outside case where there is a `Get{Property}()` method that the developer does not intend to call and which returns an incompatible content type. --- Ignia.Topics/Mapping/TopicMappingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 72a1e09c..80185765 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -424,7 +424,7 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ else if (_typeCache.HasSettableProperty(targetType, property.Name)) { //Attempt to get value from topic.Get{Property}() - var attributeValue = _typeCache.GetMethodValue(topic, "Get" + property.Name) as string; + var attributeValue = _typeCache.GetMethodValue(topic, "Get" + property.Name)?.ToString(); //Otherwise, attempts to get value from topic.Attributes.GetValue({Property}) if (String.IsNullOrEmpty(attributeValue)) { attributeValue = topic.Attributes.GetValue(attributeKey, defaultValue, inheritValue); From 58df3ca0262f11a9a8a799dbaf2537984d55bbbf Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 11:42:00 -0700 Subject: [PATCH 016/149] Renamed to `SetMethodTest()` This is more accurate than `SetMemberTest()` since it explicitly tests setting a _method_. --- Ignia.Topics.Tests/TypeCollectionTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index 7002d248..5b1776b1 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -116,14 +116,14 @@ public void TypeCollection_SetPropertyTest() { } /*========================================================================================================================== - | TEST: SET MEMBER + | TEST: SET METHOD \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Establishes a and confirms that a value can be properly set using the /// method. /// [TestMethod] - public void TypeCollection_SetMemberTest() { + public void TypeCollection_SetMethodTest() { var types = new TypeCollection(); var source = new MethodBasedViewModel(); From 22e7b020b7e0a5eadee64ed2186563dab0294bb6 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:02:57 -0700 Subject: [PATCH 017/149] Introduced `IsSettableType()` helper method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This determines whether the type of a property or method is in the `SettableTypes` collection—or, alternatively, an optionally supplied target type. --- Ignia.Topics/Reflection/TypeCollection.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index d206162e..2d994178 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -130,7 +130,7 @@ internal bool HasSettableProperty(Type type, string name) { return ( property != null && property.CanWrite && - SettableTypes.Contains(property.PropertyType) && + IsSettableType(property.PropertyType) && (_attributeFlag == null || System.Attribute.IsDefined(property, _attributeFlag)) ); } @@ -178,7 +178,7 @@ internal bool HasSettableMethod(Type type, string name) { return ( method != null && method.GetParameters().Count().Equals(1) && - SettableTypes.Contains(method.GetParameters().First().ParameterType) && + IsSettableType(method.GetParameters().First().ParameterType) && (_attributeFlag == null || System.Attribute.IsDefined(method, _attributeFlag)) ); } @@ -228,6 +228,20 @@ internal object GetMethodValue(object target, string name) { return null; + /*========================================================================================================================== + | METHOD: IS SETTABLE TYPE? + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Determines whether a given type is settable, either assuming the list of , or provided a + /// specific . + /// + private bool IsSettableType(Type sourceType, Type targetType = null) { + + if (targetType != null) { + return sourceType.Equals(targetType); + } + return SettableTypes.Contains(sourceType); + } /*========================================================================================================================== From 3bd12925d737c492e3a2e15f521edf8d1f2dfa6a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:03:33 -0700 Subject: [PATCH 018/149] Added XmlDocs for method parameters --- Ignia.Topics/Reflection/TypeCollection.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 2d994178..813e1c3a 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -125,6 +125,8 @@ internal T GetMember(Type type, string name) where T : MemberInfo { /// /// Will return false if the property is not available. /// + /// The on which the property is defined. + /// The name of the property to assess. internal bool HasSettableProperty(Type type, string name) { var property = GetMember(type, name); return ( @@ -142,6 +144,9 @@ internal bool HasSettableProperty(Type type, string name) { /// Uses reflection to call a property, assuming that it is a) writable, and b) of type , /// , or . /// + /// The object on which the property is defined. + /// The name of the property to assess. + /// The value to set on the property. internal bool SetPropertyValue(object target, string name, string value) { if (!HasSettableProperty(target.GetType(), name)) { @@ -173,6 +178,8 @@ internal bool SetPropertyValue(object target, string name, string value) { /// Will return false if the method is not available. Methods are only considered settable if they have one parameter of /// a settable type. /// + /// The on which the method is defined. + /// The name of the method to assess. internal bool HasSettableMethod(Type type, string name) { var method = GetMember(type, name); return ( @@ -190,6 +197,9 @@ internal bool HasSettableMethod(Type type, string name) { /// Uses reflection to call a method, assuming that it is a) writable, and b) of type , /// , or . /// + /// The object instance on which the method is defined. + /// The name of the method to assess. + /// The value to set the method to. internal bool SetMethodValue(object target, string name, string value) { if (!HasSettableMethod(target.GetType(), name)) { @@ -219,6 +229,8 @@ internal bool SetMethodValue(object target, string name, string value) { /// Uses reflection to call a method, assuming that it has no parameters. /// internal object GetMethodValue(object target, string name) { + /// The object instance on which the method is defined. + /// The name of the method to assess. var getter = GetMember(target.GetType(), name); From a2aa127b2f86b5056f031b47f3e75dd871dfd516 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:05:00 -0700 Subject: [PATCH 019/149] Introduced `HasGettableProperty()` and `GetPropertyValue()` These complement the existing `HasSettableProperty()` and `SetPropertyValue()`. While these are simple methods, they round out the interface and provide a more consistent read/write experience. --- Ignia.Topics/Reflection/TypeCollection.cs | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 813e1c3a..f8d417b3 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -168,6 +168,52 @@ internal bool SetPropertyValue(object target, string name, string value) { } + /*========================================================================================================================== + | METHOD: HAS GETTABLE PROPERTY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Used reflection to identify if a local property is available and gettable. + /// + /// + /// Will return false if the property is not available. + /// + /// The on which the property is defined. + /// The name of the property to assess. + /// Optional, the expected. + internal bool HasGettableProperty(Type type, string name, Type targetType = null) { + var property = GetMember(type, name); + return ( + property != null && + property.CanRead && + IsSettableType(property.PropertyType, targetType) && + (_attributeFlag == null || System.Attribute.IsDefined(property, _attributeFlag)) + ); + } + + /*========================================================================================================================== + | METHOD: GET PROPERTY VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Uses reflection to call a property, assuming that it is a) readable, and b) of type , + /// , or . + /// + /// The object instance on which the property is defined. + /// The name of the property to assess. + /// Optional, the expected. + internal object GetPropertyValue(object target, string name, Type targetType = null) { + + if (!HasGettableProperty(target.GetType(), name, targetType)) { + return null; + } + + var property = GetMember(target.GetType(), name); + + Contract.Assume(property != null); + + return property.GetValue(target); + + } + /*========================================================================================================================== | METHOD: HAS SETTABLE METHOD \-------------------------------------------------------------------------------------------------------------------------*/ From d13ff900e2891a5bf9196eb7019b274d6009a5cf Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:06:20 -0700 Subject: [PATCH 020/149] Introduced `HasGettableMethod()` and updated `GetMethodValue()` Introduced the validation routine, `HasGettableMethod()`, and added it to `GetMethodValue()` alongside the new (optional) `targetType` parameter. --- Ignia.Topics/Reflection/TypeCollection.cs | 38 +++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index f8d417b3..1d7107cf 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -268,23 +268,49 @@ internal bool SetMethodValue(object target, string name, string value) { } + /*========================================================================================================================== + | METHOD: HAS GETTABLE METHOD + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Used reflection to identify if a local method is available and gettable. + /// + /// + /// Will return false if the method is not available. Methods are only considered gettable if they have no parameters and + /// their return value is a settable type. + /// + /// The on which the method is defined. + /// The name of the method to assess. + /// Optional, the expected. + internal bool HasGettableMethod(Type type, string name, Type targetType = null) { + var method = GetMember(type, name); + return ( + method != null && + method.GetParameters().Count().Equals(0) && + IsSettableType(method.ReturnType, targetType) && + (_attributeFlag == null || System.Attribute.IsDefined(method, _attributeFlag)) + ); + } + /*========================================================================================================================== | METHOD: GET METHOD VALUE \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Uses reflection to call a method, assuming that it has no parameters. /// - internal object GetMethodValue(object target, string name) { /// The object instance on which the method is defined. /// The name of the method to assess. + /// Optional, the expected. + internal object GetMethodValue(object target, string name, Type targetType = null) { - var getter = GetMember(target.GetType(), name); - - if (getter != null && getter.GetParameters().Length.Equals(0)) { - return (object)getter.Invoke(target, new object[] { }); + if (!HasGettableMethod(target.GetType(), name, targetType)) { + return null; } - return null; + var method = GetMember(target.GetType(), name); + + return method.Invoke(target, new object[] { }); + + } /*========================================================================================================================== | METHOD: IS SETTABLE TYPE? From 8c2a4c2d9e7cd1aa5f8eb4fa26a364b8090eec96 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:08:20 -0700 Subject: [PATCH 021/149] Introduced assertions for new `GetPropertyValue()` Cases test both typed version (with a `DateTime` target type) and the default version (which assumes any of the supported types). --- Ignia.Topics.Tests/TypeCollectionTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index 5b1776b1..0658e286 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -106,11 +106,16 @@ public void TypeCollection_SetPropertyTest() { var isKeySet = types.SetPropertyValue(topic, "Key", "NewKey"); var isInvalidPropertySet = types.SetPropertyValue(topic, "InvalidProperty", "Invalid"); + var lastModified = DateTime.Parse(types.GetPropertyValue(topic, "LastModified", typeof(DateTime)).ToString()); + var key = types.GetPropertyValue(topic, "Key", typeof(string)).ToString(); + Assert.IsTrue(isDateSet); Assert.IsTrue(isKeySet); Assert.IsFalse(isInvalidPropertySet); Assert.AreEqual("NewKey", topic.Key); + Assert.AreEqual("NewKey", key); Assert.AreEqual(new DateTime(2008, 6, 3), topic.LastModified); + Assert.AreEqual(new DateTime(2008, 6, 3), lastModified); Assert.IsTrue(topic.IsHidden); } From 82faa3995975fe870fdd434821c8d12c897c8e88 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 12:10:55 -0700 Subject: [PATCH 022/149] Introduced support for 1:1 property matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previousy, if there was a property on topic named the same value as a property on the target DTO, the mapping library wouldn't bind them. This is because it was primarily looking for e.g. `Attribute.GetValue()` mappings. This new addition provides support for 1:1 property matching—at least assuming `String`, `Int`, `DateTime`, or `Bool`. This also eliminates the need for the `Id` fix (since `Id` doesn't have an attribute, but can now be mapped using the generic `GetPropertyValue()` technique). --- Ignia.Topics/Mapping/TopicMappingService.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 80185765..0a216077 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -410,28 +410,29 @@ Dictionary cache } } - /*------------------------------------------------------------------------------------------------------------------------ - | Property: Identity - >------------------------------------------------------------------------------------------------------------------------- - | ### NOTE JJC043018: The identity property requires special handling since it isn't stored as an attribute on topic. - \-----------------------------------------------------------------------------------------------------------------------*/ - if (property.Name.Equals("Id", StringComparison.InvariantCultureIgnoreCase)) { - _typeCache.SetPropertyValue(target, property.Name, topic.Id.ToString()); - } - /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ - else if (_typeCache.HasSettableProperty(targetType, property.Name)) { + if (_typeCache.HasSettableProperty(targetType, property.Name)) { + //Attempt to get value from topic.Get{Property}() var attributeValue = _typeCache.GetMethodValue(topic, "Get" + property.Name)?.ToString(); + + //Otherwise, attempts to get value from topic.{Property} + if (String.IsNullOrEmpty(attributeValue)) { + attributeValue = _typeCache.GetPropertyValue(topic, property.Name)?.ToString(); + } + //Otherwise, attempts to get value from topic.Attributes.GetValue({Property}) if (String.IsNullOrEmpty(attributeValue)) { attributeValue = topic.Attributes.GetValue(attributeKey, defaultValue, inheritValue); } + + //Sets the value, assuming it is defined if (attributeValue != null) { _typeCache.SetPropertyValue(target, property.Name, attributeValue); } + } /*------------------------------------------------------------------------------------------------------------------------ From e67eeecccc66dad8466e3fe1badfd02faec87e18 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 15:38:33 -0700 Subject: [PATCH 023/149] Renamed `[Recurse()]` to `[Follow()]` This is more intuitive since the `[Recurse()]` attribute does not instruct the `ITopicMappingService` to be _truly_ recursive; it only works that way if the attribute is _chained_. The semantics of `[Follow()]` make this more clear. While I was at it, I also refactored the `Relationships` property to use an automatic implementation. --- .../ViewModels/CircularTopicViewModel.cs | 4 ++-- .../ViewModels/SampleTopicViewModel.cs | 4 ++-- Ignia.Topics.ViewModels/TopicViewModel.cs | 2 +- Ignia.Topics/Ignia.Topics.csproj | 2 +- ...RecurseAttribute.cs => FollowAttribute.cs} | 23 ++++++------------- 5 files changed, 13 insertions(+), 22 deletions(-) rename Ignia.Topics/Mapping/{RecurseAttribute.cs => FollowAttribute.cs} (72%) diff --git a/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs index 8afbe1b8..111d72ff 100644 --- a/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs +++ b/Ignia.Topics.Tests/ViewModels/CircularTopicViewModel.cs @@ -20,10 +20,10 @@ namespace Ignia.Topics.Tests.ViewModels { /// public class CircularTopicViewModel { - [Recurse(Relationships.Parents)] + [Follow(Relationships.Parents)] public CircularTopicViewModel Parent { get; set; } - [Recurse(Relationships.Children | Relationships.Parents)] + [Follow(Relationships.Children | Relationships.Parents)] public List Children { get; set; } } //Class diff --git a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs index 1736d8b2..d8353d95 100644 --- a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs +++ b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs @@ -31,10 +31,10 @@ public class SampleTopicViewModel : PageTopicViewModel { public TopicViewModel TopicReference { get; set; } - [Recurse(Relationships.Relationships)] + [Follow(Relationships.Relationships)] public TopicViewModelCollection Children { get; set; } - [Recurse(Relationships.Children)] + [Follow(Relationships.Children)] public TopicViewModelCollection Cousins { get; set; } public TopicViewModelCollection Categories { get; set; } diff --git a/Ignia.Topics.ViewModels/TopicViewModel.cs b/Ignia.Topics.ViewModels/TopicViewModel.cs index cba33fc0..ab7d383a 100644 --- a/Ignia.Topics.ViewModels/TopicViewModel.cs +++ b/Ignia.Topics.ViewModels/TopicViewModel.cs @@ -24,7 +24,7 @@ public class TopicViewModel: ITopicViewModel { public int Id { get; set; } public string Key { get; set; } public string ContentType { get; set; } - [Recurse(Relationships.Parents)] + [Follow(Relationships.Parents)] public TopicViewModel Parent { get; set; } public string UniqueKey { get; set; } public string View { get; set; } diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 5b2975da..0e4f3b43 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -143,10 +143,10 @@ + - diff --git a/Ignia.Topics/Mapping/RecurseAttribute.cs b/Ignia.Topics/Mapping/FollowAttribute.cs similarity index 72% rename from Ignia.Topics/Mapping/RecurseAttribute.cs rename to Ignia.Topics/Mapping/FollowAttribute.cs index 98026fa6..48badbb1 100644 --- a/Ignia.Topics/Mapping/RecurseAttribute.cs +++ b/Ignia.Topics/Mapping/FollowAttribute.cs @@ -7,7 +7,7 @@ namespace Ignia.Topics.Mapping { /*============================================================================================================================ - | ATTRIBUTE: RECURSE + | ATTRIBUTE: FOLLOW \---------------------------------------------------------------------------------------------------------------------------*/ /// /// Instructs the to continue following relationships on that property. Optionally @@ -20,29 +20,24 @@ namespace Ignia.Topics.Mapping { /// to be loaded, but the mapper won't populate their Children (assuming that property is set). /// /// - /// The overrides this behavior. If set, the will + /// The overrides this behavior. If set, the will /// populate the specified on the related topics. By default, it will crawl all /// relationships, but the flag can optionally be used to specify one or multiple /// relationship types, thus providing fine-tune control. /// /// [System.AttributeUsage(System.AttributeTargets.Property)] - public sealed class RecurseAttribute : System.Attribute { - - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private Relationships _relationships = Relationships.All; + public sealed class FollowAttribute : System.Attribute { /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Annotates a property with the by providing an . + /// Annotates a property with the by providing an . /// /// The specific relationships that should be crawled. - public RecurseAttribute(Relationships relationships = Relationships.All) { - _relationships = relationships; + public FollowAttribute(Relationships relationships = Relationships.All) { + Relationships = relationships; } /*========================================================================================================================== @@ -51,11 +46,7 @@ public RecurseAttribute(Relationships relationships = Relationships.All) { /// /// Gets the type(s) of relationships that should be recused over. /// - public Relationships Relationships { - get { - return _relationships; - } - } + public Relationships Relationships { get; } = Relationships.All; } //Class From 4a82666e4734c7bbb336d300e285f79fe0984349 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 15:48:11 -0700 Subject: [PATCH 024/149] Introduced `[Flatten]` attribute The `[Flatten]` attribute allows a collection to include all child objects. Those will then be filtered by target type and any other attributes, such as `[FilterByAttribute]`. If the collection enforces uniqueness (e.g., for a `KeyedCollection` or `Dictionary`) then duplicates will be removed. --- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + Ignia.Topics.Tests/TopicMappingServiceTest.cs | 27 +++++++ .../FlattenChildrenTopicViewModel.cs | 28 +++++++ Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/Mapping/FlattenAttribute.cs | 40 ++++++++++ Ignia.Topics/Mapping/TopicMappingService.cs | 75 +++++++++++++++---- 6 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 Ignia.Topics.Tests/ViewModels/FlattenChildrenTopicViewModel.cs create mode 100644 Ignia.Topics/Mapping/FlattenAttribute.cs diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index d583de26..b13d957b 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -108,6 +108,7 @@ + diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index fad4faea..27962cb8 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -653,6 +653,33 @@ public void TopicMappingService_FilterByAttribute() { } + /*========================================================================================================================== + | TEST: FLATTEN + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a and tests whether the resulting object's property is properly flattened. + /// + [TestMethod] + public void TopicMappingService_Flatten() { + + var mappingService = new TopicMappingService(_topicRepository); + + var topic = TopicFactory.Create("Test", "FlattenChildren"); + + for (var i=0; i<5; i++) { + var childTopic = TopicFactory.Create("Child" + i, "Page", topic); + for (var j=0; j<5; j++) { + TopicFactory.Create("GrandChild" + i + j, "FlattenChildren", childTopic); + } + } + + var target = (FlattenChildrenTopicViewModel)mappingService.Map(topic); + + Assert.AreEqual(25, target.Children.Count); + + } + /*========================================================================================================================== | TEST: CACHING \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.Tests/ViewModels/FlattenChildrenTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/FlattenChildrenTopicViewModel.cs new file mode 100644 index 00000000..52791327 --- /dev/null +++ b/Ignia.Topics.Tests/ViewModels/FlattenChildrenTopicViewModel.cs @@ -0,0 +1,28 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System.Collections.Generic; +using Ignia.Topics.Mapping; +using Ignia.Topics.ViewModels; + +namespace Ignia.Topics.Tests.ViewModels { + + /*============================================================================================================================ + | VIEW MODEL: FLATTEN CHILDREN TOPIC + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides a strongly-typed data transfer object for testing view properties annotated with the . + /// + /// + /// This is a sample class intended for test purposes only; it is not designed for use in a production environment. + /// + public class FlattenChildrenTopicViewModel { + + [Flatten] + public List Children { get; set; } + + } //Class +} //Namespace diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 0e4f3b43..f06cdef2 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -143,6 +143,7 @@ + diff --git a/Ignia.Topics/Mapping/FlattenAttribute.cs b/Ignia.Topics/Mapping/FlattenAttribute.cs new file mode 100644 index 00000000..2e3ce8be --- /dev/null +++ b/Ignia.Topics/Mapping/FlattenAttribute.cs @@ -0,0 +1,40 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ + +namespace Ignia.Topics.Mapping { + + /*============================================================================================================================ + | ATTRIBUTE: FLATTEN + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Instructs the to include all children of topics in a collection into a single, flat + /// list. + /// + /// + /// + /// By default, will populate all items in a collection—and, if the is defined, then also include their specified relationships. The allows all subsequent children to not only be included, but to be elevated to a single + /// list. This can be especially useful when combined with e.g. as well as + /// strongly-typed collections (e.g., of a specific view model type), as it allows a list to provide, effectively, search + /// results. + /// + /// + [System.AttributeUsage(System.AttributeTargets.Property)] + public sealed class FlattenAttribute : System.Attribute { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Annotates a property with the . + /// + public FlattenAttribute() { + } + + } //Class + +} //Namespace diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 0a216077..df985142 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -174,7 +174,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// /// This internal version passes a private cache of mapped objects from this run. This helps prevent problems with /// recursion in case is referred to multiple times (e.g., a Children collection with - /// set to include ). + /// set to include ). /// /// /// The entity to derive the data from. @@ -267,7 +267,7 @@ public object Map(Topic topic, object target, Relationships relationships = Rela /// /// This internal version passes a private cache of mapped objects from this run. This helps prevent problems with /// recursion in case is referred to multiple times (e.g., a Children collection with - /// set to include ). + /// set to include ). /// /// /// The target view model with the properties appropriately mapped. @@ -349,6 +349,7 @@ Dictionary cache var crawlRelationships = Relationships.None; var metadataKey = (string)null; var attributeFilters = new Dictionary(); + var flattenChildren = false; var topicReferenceId = topic.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ @@ -385,13 +386,18 @@ Dictionary cache } /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine recursion settings + | Attributes: Determine follow settings \-----------------------------------------------------------------------------------------------------------------------*/ - var recurseAttribute = (RecurseAttribute)property.GetCustomAttribute(typeof(RecurseAttribute), true); + var recurseAttribute = (FollowAttribute)property.GetCustomAttribute(typeof(FollowAttribute), true); if (recurseAttribute != null) { crawlRelationships = recurseAttribute.Relationships; } + /*------------------------------------------------------------------------------------------------------------------------ + | Attributes: Determine flatten settings + \-----------------------------------------------------------------------------------------------------------------------*/ + flattenChildren = property.GetCustomAttribute(typeof(FlattenAttribute), true) != null; + /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Determine metadata key, if present \-----------------------------------------------------------------------------------------------------------------------*/ @@ -448,7 +454,7 @@ Dictionary cache } //Get source for list - IList listSource = new Topic[] { }; + IList listSource = new Topic[] { }; //Handle children if ( @@ -495,6 +501,15 @@ Dictionary cache listSource = _topicRepository.Load("Root:Configuration:Metadata:" + metadataKey + ":LookupList")?.Children.ToList(); } + //Handle flattening of children + if (flattenChildren) { + List flattenedList = new List(); + foreach (var childTopic in listSource) { + AddChildren(childTopic, flattenedList); + } + listSource = flattenedList; + } + //Ensure list is created var list = (IList)property.GetValue(target, null); if (list == null) { @@ -508,21 +523,32 @@ Dictionary cache if (filterByAttribute.Any(f => !childTopic.Attributes.GetValue(f.Key, "").Equals(f.Value))) { continue; } - if (!childTopic.IsDisabled) { - //Handle scenario where the list type derives from Topic - if (typeof(Topic).IsAssignableFrom(listType)) { - //Ensure the list item derives from the list type (which may be more derived than Topic) - if (listType.IsAssignableFrom(childTopic.GetType())) { + if (childTopic.IsDisabled) { + continue; + } + //Handle scenario where the list type derives from Topic + if (typeof(Topic).IsAssignableFrom(listType)) { + //Ensure the list item derives from the list type (which may be more derived than Topic) + if (listType.IsAssignableFrom(childTopic.GetType())) { + try { list.Add(childTopic); } + catch (ArgumentException exception) { + //Ignore exceptions caused by duplicate keys + } } - //Otherwise, assume the list type is a DTO - else { - var childDto = Map(childTopic, crawlRelationships, cache); - //Ensure the mapped type derives from the list type - if (listType.IsAssignableFrom(childDto.GetType())) { + } + //Otherwise, assume the list type is a DTO + else { + var childDto = Map(childTopic, crawlRelationships, cache); + //Ensure the mapped type derives from the list type + if (listType.IsAssignableFrom(childDto.GetType())) { + try { list.Add(childDto); } + catch (ArgumentException exception) { + //Ignore exceptions caused by duplicate keys + } } } } @@ -562,5 +588,24 @@ Dictionary cache } + /*========================================================================================================================== + | PRIVATE: ADD CHILDREN + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Helper function recursively iterates through children and adds each to a collection. + /// + /// The entity pull the data from. + /// The list of instances to add each child to. + /// Optionally enable including nested topics in the list. + private IList AddChildren(Topic topic, IList topics, bool includeNestedTopics = false) { + if (topic.IsDisabled) return topics; + if (topic.ContentType.Equals("List") && !includeNestedTopics) return topics; + topics.Add(topic); + foreach (var child in topic.Children) { + AddChildren(child, topics); + } + return topics; + } + } //Class } //Namespace From 8a6e2aeebbb4cfbe4f2378eeb2442ffcacf699b3 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 17:37:16 -0700 Subject: [PATCH 025/149] Updated `TopicMappingService` documentation --- Ignia.Topics/Mapping/README.md | 46 +++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/Ignia.Topics/Mapping/README.md b/Ignia.Topics/Mapping/README.md index 4fa67bca..822cd7d0 100644 --- a/Ignia.Topics/Mapping/README.md +++ b/Ignia.Topics/Mapping/README.md @@ -7,12 +7,17 @@ The [`ITopicMappingService`](ITopicMappingService.cs) provides the abstract inte - [Properties](#properties) - [Scalar Values](#scalar-values) - [Collections](#collections) + - [References](#references) - [Parent](#parent) - [Example](#example) - [Attributes](#attributes) - [Example](#example-1) - [Polymorphism](#polymorphism) + - [Filtering](#filtering) + - [Topics](#topics) - [Caching](#caching) + - [Internal Caching](#internal-caching) + - [`CachedTopicMappingService`](#cachedtopicmappingservice) ## `TopicMappingService` The [`TopicMappingService`](TopicMappingService.cs) provides a concrete implementation that is expected to satisfy the requirements of most consumers. This supports the following conventions. @@ -31,9 +36,13 @@ The mapping service will automatically attempt to map any properties on a data t #### Scalar Values If a property is of the type `bool`, `int`, `string`, or `DateTime`, then: - Will pull the value from a parameterless getter method with the same name. - - E.g., if a property is named "Author" it will pull from a `topic.GetAuthor()` method. +- Will pull the value from a property of the same name. - Otherwise, will pull the value from the `topic.Attributes.GetValue()` method. - - E.g., If a property is named `Author`, it will call `topic.Attributes.GetValue("Author")`. + +For example, if a property on a view model is named `Author`, it will automatically look, in order, for: +- `topic.GetAuthor()` +- `topic.Author` +- `topic.Attributes.GetValue("Author")` #### Collections If a property implements `IList` (e.g., `List<>`, `Collection<>`, `TopicViewModelCollection<>`), then: @@ -42,8 +51,15 @@ If a property implements `IList` (e.g., `List<>`, `Collection<>`, `TopicViewMode - Will search, in order, `topic.Relationships`, `topic.IncomingRelationships`, `topic.Children`. - E.g., If a `List<>` property is named `Cousins` then it might match `topic.Relationships.GetTopics("Cousins")`. +#### References +Topic references relate a single topic to another topic. If a property corresponds to an attribute named `{Property}Id`, that identifier refers to a `Topic`, and that `Topic` maps to an object that is assignable to the original property, then the `Topic` will be loaded, mapped, and assigned to that property. For instance, the following property: +``` +public AuthorTopicViewModel Author { get; set; } +``` +Would be mapped to an `AuthorTopicViewModel` if `topic.Attributes.GetValue("AuthorId")` returns the identifier of a `Topic` with a `ContentType` set to `Author`. + #### Parent -If a property is named `Parent`, then the `TopicMappingService` will pull the value from `topic.Parent`. +If a property is named `Parent`, then the `TopicMappingService` will pull the value from `topic.Parent`. This acts as a special version of a [Topic Reference](#references). > *Note:* By default, collections and reference properties of related topics will not be pulled. For instance, if a `TopicViewModel` has a `Children` collection, then the relationships, nested topics, and children of those instances will not be populated. This is meant to constrain the size of the object graph delivered. @@ -83,7 +99,8 @@ To support the mapping, a variety of `Attribute` classes are provided for decora - **`[AttributeKey(key)]`**: Instructs the `TopicMappingService` to use the specified `key` instead of the property name when calling `topic.Attributes.GetValue()`. - **`[FilterByAttribute(key, value)]`**: Ensures that all items in a collection have an attribute named "Key" with a value of "Value"; all else will be excluded. Multiple instances can be stacked. - **`[Relationship(key, type)]`**: For a collection, optionally specifies the name of the key to look for, instead of the property name, and the relationship type, in case the key name is ambiguous. -- **`[Recurse(relationships)]`**: Instructs the code to populate the specified relationships on any view models within a collection. +- **`[Follow(relationships)]`**: Instructs the code to populate the specified relationships on any view models within a collection. +- **`[Flatten]`**: Includes all descendants for every item in the collection. If the collection enforces uniqueness, duplicates will be removed. ### Example The following is an example of a data transfer object that implements the above attributes: @@ -102,15 +119,16 @@ public class CompanyTopicViewModel { public TopicViewModelCollection Countries { get; set; } [Relationship("Companies", RelationshipType.IncomingRelationship)] - [Recurse(Relationships.Children)] + [Follow(Relationships.Children)] public TopicViewModelCollection CaseStudies { get; set; } - [Recurse(Relationships.Relationships)] + [Follow(Relationships.Relationships)] public TopicViewModelCollection Children { get; set; } [Relationship("Employees", RelationshipType.NestedTopics)] [FilterByAttribute("IsActive", "1")] [FilterByAttribute("Role", "Account Manager")] + [Flatten] public TopicViewModelCollection Contacts { get; set; } } @@ -121,20 +139,30 @@ In this example, the properties would map to: - `HideFromDirectory`: An attribute named `IsHidden` or a method named `GetIsHidden()`. If null, will look in `Parent` topics. - `Countries`: Loads all `LookupListItem` instances in the `Root:Configuration:Metadata:Countries` metadata collection. - `CaseStudies`: A collection of `CaseStudy` topics pointing to the current `Company` via a "Companies" relationship. Will load the children of each case study. -- `Children`: A collection of child topics, with all relationships loaded. -- `Contacts`: A list of `Employee` nested topics, filtered by those with `IsActive` set to `1` (`true`) and `Role` set to "Account Manager". +- `Children`: A collection of child topics, with all relationships (but not e.g. grandchildren) loaded. +- `Contacts`: A list of `Employee` nested topics, filtered by those with `IsActive` set to `1` (`true`) and `Role` set to "Account Manager". Includes any descendants of the nested topics that meet the previous criteria. > *Note*: Often times, data transfer objects won't require any attributes. These are only needed if the properties don't follow the built-in conventions and require additional help. For instance, the `[Relationship(…)]` attribute is useful if the relationship key is ambigious between outgoing relationships and incoming relationships. ## Polymorphism If a reference type (e.g., `TopicViewModel Parent`) or a strongly-typed collection property (e.g., `List`) are defined, then any target instances must be assignable by the base type (in these cases, `TopicViewModel`). If they cannot be, then they will not be included; no error will occur. +### Filtering This can be useful for filtering a collection. For instance, if a `CompanyTopicViewModel` includes an `Employees` collection of type `List` then it will only be populated by topics that can be mapped to either `ManagerTopicViewModel` or a derivative (perhaps, `ExecutiveTopicViewModel`). Other types (e.g., `EmployeeTopicViewModel`) will be excluded, even though they might otherwise be referenced by the `Employees` relationship. > *Note:* For this reason, it is recommended that view models use inheritance based on the content type hierarchy. This provides an intuitive mapping to content type definitions, avoids needing to redefine base properties, and allows for polymorphism in assigning derived types. +### Topics +While it's not a best practice, this also works for strongly-typed collections of `Topic` objects. Typically, collections should return view models, but if the collection is strongly-typed to `Topic` (or a derivative) then the source `Topic` will not be mapped, and will be used as-is assuming it implements (or derives from) the target `Topic` type. This can be useful for scenarios where a view needs full access to the object graph (such as the `SitemapController`). In such cases, it is impractical to map the entirety of an object graph, along with all attributes, to a corresponding view model graph, and makes more sense to simply return the `Topic` graph. + ## Caching -By default, the `TopicMappingService` will cache all types discovered that end with `TopicViewModel`, as well as all `PropertyInfo` objects associated with each of those types. That mitigates much of the performance hit associated with the use of reflection. Despite that, simply setting properties—and, especially, on large object graphs—can require a lot of processing time. To mitigate this, OnTopic also offers the [`CachedTopicMappingService`](CachedTopicMappingService.cs) Decorator, which accepts a concrete implementation of an `ITopicMappingService` and provides caching based on `topic.Id`, `Type`, and `Relationships`. Because the cache is based on all three of these, it will differentiate between the results of e.g., +By default, the `TopicMappingService` will cache a reference to all types discovered that end with `TopicViewModel`, as well as all `MemberInfo` objects associated with each of those types. That mitigates much of the performance hit associated with the use of reflection. Despite that, simply setting properties—and, especially, on large object graphs—can require a lot of processing time. To mitigate this, OnTopic also offers two approaches. + +### Internal Caching +When a request is made to `TopicMappingService`, and internal cache is constructed. If any mapping requests refer to a `Topic` that's already been mapped as part of the _current_ object graph, then that object will be returned. This prevents unnecessary duplication of mapping, and also avoids the potential for infinite loops. For instance, if a view model includes `Children`, and those children are set to `[Follow(Relationships.Parents)]`, the `TopicMappingService` will point back to the originally-mapped `Parent` object, instead of mapping a new instance of that `Topic`. + +### `CachedTopicMappingService` +The [`CachedTopicMappingService`](CachedTopicMappingService.cs) Decorator, which accepts a concrete implementation of an `ITopicMappingService`, provides caching across requests based on `topic.Id`, `Type`, and `Relationships`. Because the cache is based on all three of these, it will differentiate between the results of e.g., - `topicMappingService.Map(topic, Relationships.All)` - `topicMappingService.Map(topic, Relationships.Children)` - `topicMappingService.Map(topic, Relationships.Children)` From 529a7b9e86634c3cfe2278ab6f061e33cbfb2fe5 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 17:44:13 -0700 Subject: [PATCH 026/149] Added view model interfaces to documentation --- Ignia.Topics/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/README.md b/Ignia.Topics/README.md index faef3414..05d867aa 100644 --- a/Ignia.Topics/README.md +++ b/Ignia.Topics/README.md @@ -36,4 +36,10 @@ In addition to the above key classes, the `Ignia.Topics` assembly contains a num The following are intended to provide support for the Editor domain objects, `ContentTypeDescriptor` and `AttributeDescriptor`. - **[`ContentTypeDescriptorCollection`](Collections/ContentTypeDescriptorCollection.cs)**: A `KeyedCollection` of `ContentTypeDescriptor` objects keyed by `Id` and `Key`. - **[`AttributeDescriptorCollection`](Collections/AttributeDescriptorCollection.cs)**: A `KeyedCollection` of `AttributeDescriptor` objects keyed by `Id` and `Key`. - \ No newline at end of file + +## View Models +The core Topic library has been designed to be view model agnostic; i.e., view models should be defined for the specific presentation framework (e.g., ASP.NET MVC) and customer. That said, to facilitate reusability of features that work with view models, several interfaces are defined which can be applied as appropriate. These include: +- **[`ITopicViewModel`](ViewModels/ITopicViewModel.cs)**: Includes universal properties such as `Key`, `Id`, and `ContentType`. +- **[`IPageTopicViewModel`](ViewModels/IPageTopicViewModel.cs)**: Includes page-specific properties such as `Title`, `MetaKeywords`, and `WebPath`. +- **[`INavigationTopicViewModel`](ViewModels/INavigationTopicViewModel{T}.cs)**: Includes `IPageTopicViewModel`, `Children`, and an `IsSelected()` view logic handler, for use with navigation menus. + \ No newline at end of file From 00f98f00fb6a37b6237f95a8cdea504be4ddfef0 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 1 May 2018 18:08:15 -0700 Subject: [PATCH 027/149] Added controllers reference --- Ignia.Topics.Web.Mvc/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics.Web.Mvc/README.md b/Ignia.Topics.Web.Mvc/README.md index c9a60860..ee3c7ab1 100644 --- a/Ignia.Topics.Web.Mvc/README.md +++ b/Ignia.Topics.Web.Mvc/README.md @@ -3,6 +3,7 @@ The `Ignia.Topics.Web.Mvc` assembly provides a default implementation for utiliz ### Contents - [Components](#components) +- [Controllers](#controllers) - [View Conventions](#view-conventions) - [View Matching](#view-matching) - [View Locations](#view-locations) @@ -18,6 +19,16 @@ There are three key components at the heart of the MVC implementation. - **`TopicController`**: This is a default controller instance that can be used for _any_ topic path. It will automatically validate that the `Topic` exists, that it is not disabled (`IsDisabled`), and will honor any redirects (e.g., if the `Url` attribute is filled out). Otherwise, it will return `TopicViewResult` based on a view model, view name, and content type. - **`TopicViewEngine`**: The `TopicViewEngine` is called every time a view is requested. It works in conjunction with `TopicViewResult` to identify matching MVC views based on predetermined locations and conventions. These are discussed below. +## Controllers +There are six main controllers that ship with the MVC implementation. In addition to the core **`TopicController`**, these include the following ancillary controllers: +- **`ErrorControllerBase`**: Provides support for `Error`, `NotFound`, and `InternalServer` actions. Can accept any `IPageTopicViewModel` as a generic argument; that will be used as the view model. +- **`FallbackController`**: Used in a [Controller Factory](#controller-factory) as a fallback, in case no other controllers can accept the request. Simply returns a `NotFoundResult` with a predefined message. +- **`LayoutControllerBase`**: Provides support for a navigation menu by automatically mapping the top three tiers of the current namespace (e.g., `Web`, its children, and grandchildren). Can accept any `INavigationTopicViewModel` as a generic argument; that will be used as the view model for each mapped instance. +- **`RedirectController`**: Provides a single `Redirect` action which can be bound to a route such as `/Topic/{ID}/`; this provides support for permanent URLs that are independent of the `GetWebPath()`. +- **`SitemapController`**: Provides a single `Sitemap` action which returns a reference to the `ITopicRepository`, thus allowing a sitemap view to recurse over the entire Topic graph, including all attributes. + +> **Note:** There is not a practical way for MVC to provide routing for generic controllers. As such, these _must_ be subclassed by each implementation. The derived controller needn't do anything outside of provide a specific type reference to the generic base. + ## View Conventions By default, OnTopic matches views based on the current topic's `ContentType` and, if available, `View`. @@ -98,7 +109,7 @@ switch (controllerType.Name) { } ``` -For a complete reference template, see the [`OrganizationNameControllerFactory.cs`](https://gist.github.com/JeremyCaney/6ba4bb0465b7dd1992a7ffdaa1ebf813) Gist. +For a complete reference template, including the ancillary controllers, see the [`OrganizationNameControllerFactory.cs`](https://gist.github.com/JeremyCaney/6ba4bb0465b7dd1992a7ffdaa1ebf813) Gist. > *Note:* The default `TopicController` will automatically identify the current topic (based on e.g. the URL), map the current topic to a corresponding view model (based on [the `TopicMappingService` conventions](../Ignia.Topics/Mapping/)), and then return a corresponding view (based on the [view conventions](#view-conventions)). For most applications, this is enough. If custom mapping rules or additional presentation logic are needed, however, implementors can subclass `TopicController`. From bda37feacf25d08958dc65133fa597a11417eb95 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 2 May 2018 10:54:02 -0700 Subject: [PATCH 028/149] Introduced `GetAttributeValue<>()` helper This reduces the amount of code dedicated to assessing attributes by centralizing the repetitive aspects of the code. --- Ignia.Topics/Mapping/TopicMappingService.cs | 86 ++++++++++----------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index df985142..edcc12e7 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -355,56 +355,24 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value \-----------------------------------------------------------------------------------------------------------------------*/ - var defaultValueAttribute = (DefaultValueAttribute)property.GetCustomAttribute(typeof(DefaultValueAttribute), true); - if (defaultValueAttribute != null) { - property.SetValue(target, defaultValueAttribute.Value); - defaultValue = defaultValueAttribute.Value.ToString(); - } - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine inheritance - \-----------------------------------------------------------------------------------------------------------------------*/ - if (property.GetCustomAttribute(typeof(InheritAttribute), true) != null) { - inheritValue = true; - } - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine attribute key - \-----------------------------------------------------------------------------------------------------------------------*/ - var attributeKeyAttribute = (AttributeKeyAttribute)property.GetCustomAttribute(typeof(AttributeKeyAttribute), true); - if (attributeKeyAttribute != null) { - attributeKey = attributeKeyAttribute.Value; - } + GetAttributeValue( + property, + a => { + property.SetValue(target, a.Value); + defaultValue = a.Value.ToString(); + } + ); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Determine relationship key and type \-----------------------------------------------------------------------------------------------------------------------*/ - var relationshipAttribute = (RelationshipAttribute)property.GetCustomAttribute(typeof(RelationshipAttribute), true); - if (relationshipAttribute != null) { - relationshipKey = relationshipAttribute.Key?? relationshipKey; - relationshipType = relationshipAttribute.Type; - } - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine follow settings - \-----------------------------------------------------------------------------------------------------------------------*/ - var recurseAttribute = (FollowAttribute)property.GetCustomAttribute(typeof(FollowAttribute), true); - if (recurseAttribute != null) { - crawlRelationships = recurseAttribute.Relationships; - } - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine flatten settings - \-----------------------------------------------------------------------------------------------------------------------*/ - flattenChildren = property.GetCustomAttribute(typeof(FlattenAttribute), true) != null; - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine metadata key, if present - \-----------------------------------------------------------------------------------------------------------------------*/ - var metadataAttribute = (MetadataAttribute)property.GetCustomAttribute(typeof(MetadataAttribute), true); - if (metadataAttribute != null) { - metadataKey = metadataAttribute.Key; - } + GetAttributeValue( + property, + a => { + relationshipKey = a.Key ?? relationshipKey; + relationshipType = a.Type; + } + ); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Set attribute filters @@ -416,6 +384,15 @@ Dictionary cache } } + /*------------------------------------------------------------------------------------------------------------------------ + | Attributes: Retrieve basic attributes + \-----------------------------------------------------------------------------------------------------------------------*/ + GetAttributeValue(property, a => inheritValue = true); + GetAttributeValue(property, a => attributeKey = a.Value); + GetAttributeValue(property, a => crawlRelationships = a.Relationships); + GetAttributeValue(property, a => flattenChildren = true); + GetAttributeValue(property, a => metadataKey = a.Key); + /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ @@ -588,6 +565,23 @@ Dictionary cache } + /*========================================================================================================================== + | PRIVATE: GET ATTRIBUTE VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Helper function evaluates an attribute and then, if it exists, executes an to process the + /// results. + /// + /// An type to evaluate. + /// The instance to pull the attribute from. + /// The to execute on the attribute. + private void GetAttributeValue(PropertyInfo property, Action action) where T: Attribute { + var attribute = (T)property.GetCustomAttribute(typeof(T), true); + if (attribute != null) { + action(attribute); + } + } + /*========================================================================================================================== | PRIVATE: ADD CHILDREN \-------------------------------------------------------------------------------------------------------------------------*/ From d34cef08ea19152ba4428da03aaf29363179506d Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 2 May 2018 12:08:05 -0700 Subject: [PATCH 029/149] Removed unused `exception` variable --- Ignia.Topics/Mapping/TopicMappingService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index edcc12e7..07f69844 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -510,7 +510,7 @@ Dictionary cache try { list.Add(childTopic); } - catch (ArgumentException exception) { + catch (ArgumentException) { //Ignore exceptions caused by duplicate keys } } @@ -523,7 +523,7 @@ Dictionary cache try { list.Add(childDto); } - catch (ArgumentException exception) { + catch (ArgumentException) { //Ignore exceptions caused by duplicate keys } } From 205d22d2b12577bddfc65b5c3e36e5d5704f2914 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 2 May 2018 13:39:54 -0700 Subject: [PATCH 030/149] Created `IsCurrentRelationship()` helper method This ends up actually _adding_ lines of code, _but_ it makes the `SetProperty()` method a bit easier to read, _and_ it introduces the (potentially) helpful `RelationshipMap` class, which offers a mapping between corresponding `RelationshipType` and `Relationships` enum values. --- Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/Mapping/RelationshipMap.cs | 50 +++++++++++++++++++++ Ignia.Topics/Mapping/TopicMappingService.cs | 46 ++++++++++++------- 3 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 Ignia.Topics/Mapping/RelationshipMap.cs diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index f06cdef2..cfb83da9 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -148,6 +148,7 @@ + diff --git a/Ignia.Topics/Mapping/RelationshipMap.cs b/Ignia.Topics/Mapping/RelationshipMap.cs new file mode 100644 index 00000000..581d5df1 --- /dev/null +++ b/Ignia.Topics/Mapping/RelationshipMap.cs @@ -0,0 +1,50 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ignia.Topics.Mapping { + + /*============================================================================================================================ + | CLASS: RELATIONSHIP MAP + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides a mapping of the relationship between and . + /// + /// + /// While the and enumerations are distinct, there are times when + /// a single needs to be related to an item in the collection of . + /// This mapping makes that feasible. + /// + static internal class RelationshipMap { + + /*========================================================================================================================== + | STATIC VARIABLES + \-------------------------------------------------------------------------------------------------------------------------*/ + static Dictionary _mappings = null; + + /*========================================================================================================================== + | PROPERTY: MAPPINGS + \-------------------------------------------------------------------------------------------------------------------------*/ + static internal Dictionary Mappings { + get { + if (_mappings == null) { + var mappings = new Dictionary(); + mappings.Add(RelationshipType.Children, Relationships.Children); + mappings.Add(RelationshipType.Relationship, Relationships.Relationships); + mappings.Add(RelationshipType.NestedTopics, Relationships.None); + mappings.Add(RelationshipType.IncomingRelationship, Relationships.IncomingRelationships); + _mappings = mappings; + } + return _mappings; + } + } + + } +} diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 07f69844..f32f1eb8 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -436,38 +436,27 @@ Dictionary cache //Handle children if ( (relationshipKey.Equals("Children") || relationshipType.Equals(RelationshipType.Children)) && - relationships.HasFlag(Relationships.Children) + IsCurrentRelationship(RelationshipType.Children, relationshipType, relationships, listSource) ) { listSource = topic.Children.ToList(); } //Handle (outgoing) relationships - if ( - listSource.Count == 0 && - (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(RelationshipType.Relationship)) && - relationships.HasFlag(Relationships.Relationships) - ) { + if (IsCurrentRelationship(RelationshipType.Relationship, relationshipType, relationships, listSource)) { if (topic.Relationships.Contains(relationshipKey)) { listSource = topic.Relationships.GetTopics(relationshipKey); } } //Handle nested topics, or children corresponding to the property name - if ( - listSource.Count == 0 && - (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(RelationshipType.NestedTopics)) - ) { + if (IsCurrentRelationship(RelationshipType.NestedTopics, relationshipType, relationships, listSource)) { if (topic.Children.Contains(relationshipKey)) { listSource = topic.Children[relationshipKey].Children.ToList(); } } //Handle (incoming) relationships - if ( - listSource.Count == 0 && - (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(RelationshipType.IncomingRelationship)) && - relationships.HasFlag(Relationships.IncomingRelationships) - ) { + if (IsCurrentRelationship(RelationshipType.IncomingRelationship, relationshipType, relationships, listSource)) { if (topic.IncomingRelationships.Contains(relationshipKey)) { listSource = topic.IncomingRelationships.GetTopics(relationshipKey); } @@ -565,6 +554,33 @@ Dictionary cache } + /*========================================================================================================================== + | PRIVATE: IS CURRENT RELATIONSHIP + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Helper function evaluates whether a relationship is the active relationship for the current property. + /// + /// The currently being evaluated. + /// The for the current property. + /// The for the current property. + /// The existing assignment of instances for the relationship. + private bool IsCurrentRelationship( + RelationshipType targetRelationshipType, + RelationshipType sourceRelationshipType, + Relationships sourceRelationships, + IList listSource + ) => ( + listSource.Count == 0 && + ( + sourceRelationshipType.Equals(RelationshipType.Any) || + sourceRelationshipType.Equals(targetRelationshipType) + ) && + ( + RelationshipMap.Mappings[targetRelationshipType].Equals(Relationships.None) || + sourceRelationships.HasFlag(RelationshipMap.Mappings[targetRelationshipType]) + ) + ); + /*========================================================================================================================== | PRIVATE: GET ATTRIBUTE VALUE \-------------------------------------------------------------------------------------------------------------------------*/ From 72fad38b92bcdec514ccb2a765b363c3060dea3a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 13:25:29 -0700 Subject: [PATCH 031/149] Moved attribute settings into `PropertyConfiguration` class This technically increases the code significantly, but almost all of that is in documentation. In exchange, not only does this improve the documentation by complementing the documentation on each attribute, but it also makes it easier to reuse those attributes in other potential implementations of the `ITopicMappingService`. Finally, it reduces the code slightly in the `TopicMappingService` implementation. --- Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/Mapping/PropertyConfiguration.cs | 348 ++++++++++++++++++ Ignia.Topics/Mapping/TopicMappingService.cs | 114 ++---- 3 files changed, 378 insertions(+), 85 deletions(-) create mode 100644 Ignia.Topics/Mapping/PropertyConfiguration.cs diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index cfb83da9..001ff0ed 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -147,6 +147,7 @@ + diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs new file mode 100644 index 00000000..2f29e2f7 --- /dev/null +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -0,0 +1,348 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Ignia.Topics.Collections; + +namespace Ignia.Topics.Mapping { + + /*============================================================================================================================ + | CLASS: PROPERTY ATTRIBUTES + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Evaluates a instance for known , and exposes them through a set of + /// property values. + /// + /// + /// + /// The class is utilized by implementations of to + /// facilitate the mapping of source instances to Data Transfer Objects (DTOs), such as View Models. + /// The attribute values provide hints to the mapping service that help manage how the mapping occurs. + /// + /// + /// For example, by default a property on a DTO class will be mapped to a property or attribute of the same name on the + /// source . If the is attached to a property on the DTO, however, + /// then the will instead use the value defined by that attribute, thus allowing a + /// property on the DTO to be aliased to a different property or attribute name on the source . + /// + /// + public class PropertyConfiguration { + + /*========================================================================================================================== + | PRIVATE VARIABLES + \-------------------------------------------------------------------------------------------------------------------------*/ + readonly PropertyInfo _property = null; + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a instance, exposes a set of properties associated with known + /// instances. + /// + /// The instance to check for values. + public PropertyConfiguration(PropertyInfo property) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Set backing property + \-----------------------------------------------------------------------------------------------------------------------*/ + _property = property; + + /*------------------------------------------------------------------------------------------------------------------------ + | Set default values + \-----------------------------------------------------------------------------------------------------------------------*/ + AttributeKey = property.Name; + DefaultValue = null; + InheritValue = false; + RelationshipKey = AttributeKey; + RelationshipType = RelationshipType.Any; + CrawlRelationships = Relationships.None; + MetadataKey = (string)null; + AttributeFilters = new Dictionary(); + FlattenChildren = false; + + /*------------------------------------------------------------------------------------------------------------------------ + | Attributes: Retrieve basic attributes + \-----------------------------------------------------------------------------------------------------------------------*/ + GetAttributeValue(property, a => DefaultValue = a.Value); + GetAttributeValue(property, a => InheritValue = true); + GetAttributeValue(property, a => AttributeKey = a.Value); + GetAttributeValue(property, a => CrawlRelationships = a.Relationships); + GetAttributeValue(property, a => FlattenChildren = true); + GetAttributeValue(property, a => MetadataKey = a.Key); + + /*------------------------------------------------------------------------------------------------------------------------ + | Attributes: Determine relationship key and type + \-----------------------------------------------------------------------------------------------------------------------*/ + GetAttributeValue( + property, + a => { + RelationshipKey = a.Key ?? RelationshipKey; + RelationshipType = a.Type; + } + ); + + /*------------------------------------------------------------------------------------------------------------------------ + | Attributes: Set attribute filters + \-----------------------------------------------------------------------------------------------------------------------*/ + var filterByAttribute = property.GetCustomAttributes(true); + if (filterByAttribute != null && filterByAttribute.Count() > 0) { + foreach (var filter in filterByAttribute) { + AttributeFilters.Add(filter.Key, filter.Value); + } + } + + } + + /*========================================================================================================================== + | PROPERTY: ATTRIBUTE KEY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The name of the corresponding attribute in the instance. Defaults to the property name + /// on DTO. + /// + /// + /// + /// By default, a property on a DTO class will be mapped to a property or attribute of the same name on the source . If the is attached to a property on the DTO, however, then the + /// will instead use the value defined by that attribute, thus allowing a property on + /// the DTO to be aliased to a different property or attribute name on the source . + /// + /// + /// The property corresponds to the property. It + /// can be assigned by decorating a DTO property with e.g. [AttributeKey("AlternateAttributeKey")]. + /// + /// + public string AttributeKey { get; set; } + + /*========================================================================================================================== + | PROPERTY: DEFAULT VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The default value to use if no corresponding value can be determined from the source . + /// + /// + /// The property corresponds to the property. It can + /// be assigned by decorating a DTO property with e.g. [DefaultValue("DefaultValue")]. + /// + public object DefaultValue { get; set; } + + /*========================================================================================================================== + | PROPERTY: INHERIT VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Determines whether the value should be inherited from if the value cannot be identified + /// on the source . + /// + /// + /// + /// The configuration is only applicable if the value is pulled from the collection. This is the equivalent to calling the method with an InheritFromParent parameter set to + /// True. + /// + /// + /// The property corresponds to the being set on a given + /// property. It can be assigned by decorating a DTO property with e.g. [Inherit]. + /// + /// + public bool InheritValue { get; set; } + + /*========================================================================================================================== + | PROPERTY: RELATIONSHIP KEY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The name of the relationship that a collection should map to. Defaults to the property name on DTO. + /// + /// + /// + /// By default, a collection property on a DTO class will be mapped to a corresponding relationship of the same name. + /// So, for instance, if the property on the DTO class is called Cousins then the will search , , and, finally, for an object named Cousins. + /// If the is set, however, then that value is used instead, thus allowing the property on + /// the DTO to be aliased to a different collection name on the source . + /// + /// + /// The property corresponds to the property. It + /// can be assigned by decorating a DTO property with e.g. [Relationship("AlternateRelationshipKey")]. + /// + /// + public string RelationshipKey { get; set; } + + /*========================================================================================================================== + | PROPERTY: RELATIONSHIP TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Determines the type of relationship that a collection property corresponds to. + /// + /// + /// + /// By default, a collection property on a DTO class will attempt to find a match from, in order, , , and, finally, . + /// If the is set, however, then the will only + /// map the collection to a relationship of that type. This can be valuable when the might + /// be ambiguous between multiple collections. + /// + /// + /// The property corresponds to the property. It + /// can be assigned by decorating a DTO property with e.g. [Relationship("AlternateRelationshipKey", + /// RelationshipType.Children)]. + /// + /// + public RelationshipType RelationshipType { get; set; } + + /*========================================================================================================================== + | PROPERTY: CRAWL RELATIONSHIPS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Determines which relationships, if any, the service should crawl for the current + /// property. + /// + /// + /// + /// By default, the all relationships will be mapped on the target DTO, unless the caller specifies otherwise. On any + /// related DTOs, however, only will be mapped. So, if a mapped DTO has a + /// collection for children, relationships, or even a parent property then any relationships on those DTOs will + /// not be mapped. This behavior can be changed by specifying the flag, which allows + /// one or multiple relationships to be specified for a given property. + /// + /// + /// The property corresponds to the + /// property. It can be assigned by decorating a DTO property with e.g. [Follow(Relationships.Children)]. + /// + /// + public Relationships CrawlRelationships { get; set; } + + /*========================================================================================================================== + | PROPERTY: METADATA KEY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Instructs the to pull in a collection of mapped topics from a corresponding + /// collection in the Root:Configuration:Metadata namespace of the Topic graph. + /// + /// + /// + /// Sometimes, a value will be associated with lookup s found in the + /// Root:Configuration:Metadata namespace of the Topic graph. In these cases, it can be useful to include the + /// full set of metadata on the mapped DTO so the view has access to it. For instance, a DTO may include a States + /// property which includes a full list of all states potentially associated with a , which might be + /// used in the user interface to provide filtering of e.g. child s. + /// + /// + /// The property corresponds to the property. It can be + /// assigned by decorating a DTO property with e.g. [Metadata("States")]. + /// + /// + public string MetadataKey { get; set; } + + /*========================================================================================================================== + | PROPERTY: ATTRIBUTE FILTERS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides a list of Key/Value pairs corresponding to which can optionally + /// be used to filter a collection. + /// + /// + /// + /// By default, all s in a source collection (e.g., ) will be included in + /// a corresponding collection on the DTO (assuming the mapped DTO is compatible with the collection type). If any + /// are set, however, then each will be evaluated to confirm that it + /// satisfies the conditions of those filters. + /// + /// + /// The property corresponds to the and + /// properties. It can be assigned by decorating a DTO property with e.g. + /// [FilterByAttribute("Company", "Ignia")]. Multiple s can be assigned + /// to a single property, thus allowing each item in a collection to be filtered by multiple values. + /// + /// + public Dictionary AttributeFilters { get; } + + /*========================================================================================================================== + | PROPERTY: FLATTEN CHILDREN + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Determines whether all descendants in a collection should be included in the collection. + /// + /// + /// + /// By default, only stored directly in a source collection will be included in the target + /// collection on a DTO class. If the property is set, however, then all descendants of + /// those s are also included. So, for instance, if a Children property is decorated + /// with the , then not only will the be included, but any + /// grandchildren, great-grandchildren, etc. will be included. + /// + /// + /// When is specified, implementations are expected to + /// apply all other constraints. For instance, if the target collection is strongly typed, then only DTOs that exhibit + /// polymorphic compatability with that type will be included (i.e., DTOs of the same or a derived type as the target + /// collection). Similarly, any will be applied to each of the descendants. Finally, if + /// the target collection has a unique constraint (e.g., due to implementing ) + /// then any items that would constitute a duplicate will be filtered out without raising an exception. This + /// can thus allow the method to be used to represent unique search results within a + /// graph. + /// + /// + /// The property corresponds to the being set on a given + /// property. It can be assigned by decorating a DTO property with e.g. [Flatten]. + /// + /// + public bool FlattenChildren { get; set; } + + /*========================================================================================================================== + | METHOD: SATISFIES ATTRIBUTE FILTERS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a , determines whether or not the satisfy all defined on the property. + /// + /// + /// + public bool SatisfiesAttributeFilters(Topic source) { + return (!AttributeFilters.Any(f => !source.Attributes.GetValue(f.Key, "").Equals(f.Value))); + } + + /*========================================================================================================================== + | METHOD: VALIDATE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a target DTO, will automatically identify any attributes that derive from and + /// ensure that their conditions are satisfied. + /// + /// The target DTO to validate the current property on. + public void Validate(object target) { + foreach (ValidationAttribute validator in _property.GetCustomAttributes(typeof(ValidationAttribute))) { + validator.Validate(_property.GetValue(target), _property.Name); + } + } + + /*========================================================================================================================== + | PRIVATE: GET ATTRIBUTE VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Helper function evaluates an attribute and then, if it exists, executes an to process the + /// results. + /// + /// An type to evaluate. + /// The instance to pull the attribute from. + /// The to execute on the attribute. + private void GetAttributeValue(PropertyInfo property, Action action) where T : Attribute { + var attribute = (T)property.GetCustomAttribute(typeof(T), true); + if (attribute != null) { + action(attribute); + } + } + + } //Class +} //Namespace diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index f32f1eb8..f9c13ad4 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -341,58 +341,16 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ var sourceType = topic.GetType(); var targetType = target.GetType(); - var defaultValue = (string)null; - var inheritValue = false; - var attributeKey = property.Name; - var relationshipKey = property.Name; - var relationshipType = RelationshipType.Any; - var crawlRelationships = Relationships.None; - var metadataKey = (string)null; - var attributeFilters = new Dictionary(); - var flattenChildren = false; + var attributes = new PropertyConfiguration(property); var topicReferenceId = topic.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value \-----------------------------------------------------------------------------------------------------------------------*/ - GetAttributeValue( - property, - a => { - property.SetValue(target, a.Value); - defaultValue = a.Value.ToString(); - } - ); - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Determine relationship key and type - \-----------------------------------------------------------------------------------------------------------------------*/ - GetAttributeValue( - property, - a => { - relationshipKey = a.Key ?? relationshipKey; - relationshipType = a.Type; - } - ); - - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Set attribute filters - \-----------------------------------------------------------------------------------------------------------------------*/ - var filterByAttribute = property.GetCustomAttributes(true); - if (filterByAttribute != null && filterByAttribute.Count() > 0) { - foreach (var filter in filterByAttribute) { - attributeFilters.Add(filter.Key, filter.Value); - } + if (attributes.DefaultValue != null) { + property.SetValue(target, attributes.DefaultValue); } - /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Retrieve basic attributes - \-----------------------------------------------------------------------------------------------------------------------*/ - GetAttributeValue(property, a => inheritValue = true); - GetAttributeValue(property, a => attributeKey = a.Value); - GetAttributeValue(property, a => crawlRelationships = a.Relationships); - GetAttributeValue(property, a => flattenChildren = true); - GetAttributeValue(property, a => metadataKey = a.Key); - /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ @@ -408,7 +366,11 @@ Dictionary cache //Otherwise, attempts to get value from topic.Attributes.GetValue({Property}) if (String.IsNullOrEmpty(attributeValue)) { - attributeValue = topic.Attributes.GetValue(attributeKey, defaultValue, inheritValue); + attributeValue = topic.Attributes.GetValue( + attributes.AttributeKey, + attributes.DefaultValue?.ToString(), + attributes.InheritValue + ); } //Sets the value, assuming it is defined @@ -435,40 +397,41 @@ Dictionary cache //Handle children if ( - (relationshipKey.Equals("Children") || relationshipType.Equals(RelationshipType.Children)) && - IsCurrentRelationship(RelationshipType.Children, relationshipType, relationships, listSource) + (attributes.RelationshipKey.Equals("Children") || attributes.RelationshipType.Equals(RelationshipType.Children)) && + IsCurrentRelationship(RelationshipType.Children, attributes.RelationshipType, relationships, listSource) ) { listSource = topic.Children.ToList(); } //Handle (outgoing) relationships - if (IsCurrentRelationship(RelationshipType.Relationship, relationshipType, relationships, listSource)) { - if (topic.Relationships.Contains(relationshipKey)) { - listSource = topic.Relationships.GetTopics(relationshipKey); + if (IsCurrentRelationship(RelationshipType.Relationship, attributes.RelationshipType, relationships, listSource)) { + if (topic.Relationships.Contains(attributes.RelationshipKey)) { + listSource = topic.Relationships.GetTopics(attributes.RelationshipKey); } } //Handle nested topics, or children corresponding to the property name - if (IsCurrentRelationship(RelationshipType.NestedTopics, relationshipType, relationships, listSource)) { - if (topic.Children.Contains(relationshipKey)) { - listSource = topic.Children[relationshipKey].Children.ToList(); + if (IsCurrentRelationship(RelationshipType.NestedTopics, attributes.RelationshipType, relationships, listSource)) { + if (topic.Children.Contains(attributes.RelationshipKey)) { + listSource = topic.Children[attributes.RelationshipKey].Children.ToList(); } } //Handle (incoming) relationships - if (IsCurrentRelationship(RelationshipType.IncomingRelationship, relationshipType, relationships, listSource)) { - if (topic.IncomingRelationships.Contains(relationshipKey)) { - listSource = topic.IncomingRelationships.GetTopics(relationshipKey); + if (IsCurrentRelationship(RelationshipType.IncomingRelationship, attributes.RelationshipType, relationships, listSource)) { + if (topic.IncomingRelationships.Contains(attributes.RelationshipKey)) { + listSource = topic.IncomingRelationships.GetTopics(attributes.RelationshipKey); } } //Handle Metadata relationship - if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(metadataKey)) { - listSource = _topicRepository.Load("Root:Configuration:Metadata:" + metadataKey + ":LookupList")?.Children.ToList(); + if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(attributes.MetadataKey)) { + listSource = _topicRepository.Load("Root:Configuration:Metadata:" + attributes.MetadataKey + ":LookupList")? + .Children.ToList(); } //Handle flattening of children - if (flattenChildren) { + if (attributes.FlattenChildren) { List flattenedList = new List(); foreach (var childTopic in listSource) { AddChildren(childTopic, flattenedList); @@ -486,7 +449,7 @@ Dictionary cache //Validate and populate target collection if (listSource != null) { foreach (Topic childTopic in listSource) { - if (filterByAttribute.Any(f => !childTopic.Attributes.GetValue(f.Key, "").Equals(f.Value))) { + if (!attributes.SatisfiesAttributeFilters(childTopic)) { continue; } if (childTopic.IsDisabled) { @@ -506,7 +469,7 @@ Dictionary cache } //Otherwise, assume the list type is a DTO else { - var childDto = Map(childTopic, crawlRelationships, cache); + var childDto = Map(childTopic, attributes.CrawlRelationships, cache); //Ensure the mapped type derives from the list type if (listType.IsAssignableFrom(childDto.GetType())) { try { @@ -525,9 +488,9 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Property: Parent \-----------------------------------------------------------------------------------------------------------------------*/ - else if (attributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { + else if (attributes.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { if (topic.Parent != null) { - var parent = Map(topic.Parent, crawlRelationships, cache); + var parent = Map(topic.Parent, attributes.CrawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(parent.GetType())) { property.SetValue(target, parent); } @@ -539,7 +502,7 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); - var viewModelReference = Map(topicReference, crawlRelationships, cache); + var viewModelReference = Map(topicReference, attributes.CrawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(viewModelReference.GetType())) { property.SetValue(target, viewModelReference); } @@ -548,9 +511,7 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Validate fields \-----------------------------------------------------------------------------------------------------------------------*/ - foreach (ValidationAttribute validator in property.GetCustomAttributes(typeof(ValidationAttribute))) { - validator.Validate(property.GetValue(target), property.Name); - } + attributes.Validate(target); } @@ -581,23 +542,6 @@ IList listSource ) ); - /*========================================================================================================================== - | PRIVATE: GET ATTRIBUTE VALUE - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Helper function evaluates an attribute and then, if it exists, executes an to process the - /// results. - /// - /// An type to evaluate. - /// The instance to pull the attribute from. - /// The to execute on the attribute. - private void GetAttributeValue(PropertyInfo property, Action action) where T: Attribute { - var attribute = (T)property.GetCustomAttribute(typeof(T), true); - if (attribute != null) { - action(attribute); - } - } - /*========================================================================================================================== | PRIVATE: ADD CHILDREN \-------------------------------------------------------------------------------------------------------------------------*/ From 9d0e8bb2a70290e00ab08091c9fade6af2d3e597 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 15:09:28 -0700 Subject: [PATCH 032/149] Enabled Code Analysis, and implemented recommendations --- .../Ignia.Topics.Data.Caching.csproj | 1 + .../Ignia.Topics.Data.Sql.csproj | 1 + .../Ignia.Topics.ViewModels.csproj | 1 + .../Ignia.Topics.Web.Mvc.csproj | 1 + Ignia.Topics/ContentTypeDescriptor.cs | 18 ++++++------------ Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/InvalidKeyException.cs | 12 ++++++++++++ 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj index 78e5e758..84264b12 100644 --- a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj +++ b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj @@ -66,6 +66,7 @@ False False %28none%29 + true pdbonly diff --git a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj index ec48f436..b27ac1ad 100644 --- a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj +++ b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj @@ -68,6 +68,7 @@ Full %28none%29 0 + true true diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index a6575485..aad63685 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -22,6 +22,7 @@ prompt 4 CS1591 + true pdbonly diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index dc925a0e..978985ad 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -24,6 +24,7 @@ DEBUG;TRACE prompt 4 + true pdbonly diff --git a/Ignia.Topics/ContentTypeDescriptor.cs b/Ignia.Topics/ContentTypeDescriptor.cs index 53a97362..983b0e17 100644 --- a/Ignia.Topics/ContentTypeDescriptor.cs +++ b/Ignia.Topics/ContentTypeDescriptor.cs @@ -73,7 +73,8 @@ public ContentTypeDescriptor( contentType, parent, id - ) { } + ) { + } /*========================================================================================================================== | PROPERTY: DISABLE CHILD TOPICS @@ -183,15 +184,6 @@ public AttributeDescriptorCollection AttributeDescriptors { \-------------------------------------------------------------------------------------------------------------------*/ _attributeDescriptors = new AttributeDescriptorCollection(); - /*-------------------------------------------------------------------------------------------------------------------- - | Validate Attributes collection - \-------------------------------------------------------------------------------------------------------------------*/ - if (!Children.Contains("Attributes") || Children["Attributes"] == null) { - throw new Exception( - "The ContentType '" + Title + "' does not contain a nested topic named 'Attributes' as expected." - ); - } - /*-------------------------------------------------------------------------------------------------------------------- | Get values from self >--------------------------------------------------------------------------------------------------------------------- @@ -202,8 +194,10 @@ public AttributeDescriptorCollection AttributeDescriptors { | SqlTopicDataProvider.cs (lines 408 - 422), where it is used to add Attributes to the null Attributes collection; the | Type property is used for determining whether the Attribute Topic is a Relationships definition or Nested Topic. \-------------------------------------------------------------------------------------------------------------------*/ - foreach (AttributeDescriptor attribute in Children["Attributes"].Children) { - _attributeDescriptors.Add(attribute); + if (Children.Contains("Attributes")) { + foreach (AttributeDescriptor attribute in Children["Attributes"].Children) { + _attributeDescriptors.Add(attribute); + } } /*-------------------------------------------------------------------------------------------------------------------- diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 001ff0ed..9b6259f9 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -70,6 +70,7 @@ False False %28none%29 + true pdbonly diff --git a/Ignia.Topics/InvalidKeyException.cs b/Ignia.Topics/InvalidKeyException.cs index bc6950e7..ddc2b28f 100644 --- a/Ignia.Topics/InvalidKeyException.cs +++ b/Ignia.Topics/InvalidKeyException.cs @@ -4,6 +4,8 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; namespace Ignia.Topics { @@ -17,6 +19,7 @@ namespace Ignia.Topics { /// s are alphanumeric, and may contain hyphens, periods, or underscores. Any other values, such as /// spaces, slashes, or colons, are not permitted and will throw an exception. /// + [Serializable] public class InvalidKeyException: ArgumentException { /*========================================================================================================================== @@ -68,6 +71,15 @@ public InvalidKeyException(string message, string paramName) : base(message, par public InvalidKeyException(string message, string paramName, Exception inner) : base(message, paramName, inner) { } + /// + /// Instantiates a new instance of a class for serlialization. + /// + /// A instance with details about the serialization requirements. + /// A instance with details about the request context. + /// A new instance. + protected InvalidKeyException(SerializationInfo info, StreamingContext context) : base(info, context) { + Contract.Requires(info != null); + } } } From 71eff0a451a6e1f19d9af47cba3085fa6cc48d62 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 15:12:29 -0700 Subject: [PATCH 033/149] Set all projects to use latest C# version By default, projects will use the latest _major_ version of C#. Optionally, they can be configured to use the latest _minor_ version of C#. This allows them to take advantage of features in e.g. C# 7.2 (and, potentially, upcoming minor releases). --- Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj | 1 + Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj | 1 + Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj | 1 + Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj | 1 + Ignia.Topics.Web/Ignia.Topics.Web.csproj | 1 + Ignia.Topics/Ignia.Topics.csproj | 1 + 7 files changed, 7 insertions(+) diff --git a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj index 84264b12..aae37bc2 100644 --- a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj +++ b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj @@ -67,6 +67,7 @@ False %28none%29 true + latest pdbonly diff --git a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj index b27ac1ad..bad0812d 100644 --- a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj +++ b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj @@ -69,6 +69,7 @@ %28none%29 0 true + latest true diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index b13d957b..ced22ca4 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -30,6 +30,7 @@ 4 bin\Debug\Ignia.Topics.Tests.XML CS1591 + latest pdbonly diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index aad63685..6b8983e0 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -23,6 +23,7 @@ 4 CS1591 true + latest pdbonly diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index 978985ad..e826f4dd 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -25,6 +25,7 @@ prompt 4 true + latest pdbonly diff --git a/Ignia.Topics.Web/Ignia.Topics.Web.csproj b/Ignia.Topics.Web/Ignia.Topics.Web.csproj index ab1c40b6..07cbe6ab 100644 --- a/Ignia.Topics.Web/Ignia.Topics.Web.csproj +++ b/Ignia.Topics.Web/Ignia.Topics.Web.csproj @@ -69,6 +69,7 @@ False False %28none%29 + latest pdbonly diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 9b6259f9..374d37b0 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -71,6 +71,7 @@ False %28none%29 true + latest pdbonly From ec804456d95b476f9ac1b6354f173d28dc715471 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 16:52:32 -0700 Subject: [PATCH 034/149] Used shortcut for `SqlCommand` constructors --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 25376a4d..c6f0738c 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -456,11 +456,11 @@ public override Topic Load(int topicId, bool isRecursive = true) { \-----------------------------------------------------------------------------------------------------------------------*/ var topics = new Dictionary(); var connection = new SqlConnection(_connectionString); - var command = new SqlCommand("topics_GetTopics", connection); - - command.CommandType = CommandType.StoredProcedure; - command.CommandTimeout = 120; - SqlDataReader reader = null; + var command = new SqlCommand("topics_GetTopics", connection) { + CommandType = CommandType.StoredProcedure, + CommandTimeout = 120 + }; + var reader = (SqlDataReader)null; try { @@ -595,13 +595,13 @@ public override Topic Load(int topicId, DateTime version) { \-----------------------------------------------------------------------------------------------------------------------*/ var topics = new Dictionary(); var connection = new SqlConnection(_connectionString); - var command = new SqlCommand("topics_GetVersion", connection); + var command = new SqlCommand("topics_GetVersion", connection) { + CommandType = CommandType.StoredProcedure, + CommandTimeout = 120 + }; + var reader = (SqlDataReader)null; command.CommandType = CommandType.StoredProcedure; - command.CommandTimeout = 120; - SqlDataReader reader = null; - - command.CommandType = CommandType.StoredProcedure; try { From aa3e428a64d3c531a3df587da7c325fe7aed5284 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 16:52:49 -0700 Subject: [PATCH 035/149] Moved attributed property to separate "block" --- Ignia.Topics.ViewModels/TopicViewModel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.ViewModels/TopicViewModel.cs b/Ignia.Topics.ViewModels/TopicViewModel.cs index ab7d383a..1273cdcd 100644 --- a/Ignia.Topics.ViewModels/TopicViewModel.cs +++ b/Ignia.Topics.ViewModels/TopicViewModel.cs @@ -24,13 +24,14 @@ public class TopicViewModel: ITopicViewModel { public int Id { get; set; } public string Key { get; set; } public string ContentType { get; set; } - [Follow(Relationships.Parents)] - public TopicViewModel Parent { get; set; } public string UniqueKey { get; set; } public string View { get; set; } public string Title { get; set; } public bool IsHidden { get; set; } public DateTime LastModified { get; set; } + [Follow(Relationships.Parents)] + public TopicViewModel Parent { get; set; } + } //Class } //Namespace From 483ca7a6865be80cf59217a3fa5b186e571ea043 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 16:56:11 -0700 Subject: [PATCH 036/149] Established auto-implemented properties --- .../Controllers/LayoutControllerBase{T}.cs | 6 +-- .../Controllers/TopicController.cs | 6 +-- .../Models/TopicEntityViewModel.cs | 19 ++++----- Ignia.Topics/AttributeValue.cs | 42 +++++-------------- Ignia.Topics/Mapping/AttributeKeyAttribute.cs | 13 +----- .../Mapping/FilterByAttributeAttribute.cs | 22 ++-------- Ignia.Topics/Mapping/MetadataAttribute.cs | 14 +------ Ignia.Topics/Mapping/RelationshipAttribute.cs | 24 +++-------- .../Reflection/MemberInfoCollection{T}.cs | 11 ++--- Ignia.Topics/Reflection/TypeCollection.cs | 30 +++++++------ Ignia.Topics/Repositories/DeleteEventArgs.cs | 12 +----- 11 files changed, 55 insertions(+), 144 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs index 6a69d24a..8d8dd892 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs @@ -72,11 +72,7 @@ ITopicMappingService topicMappingService /// Provides a reference to the Topic Repository in order to gain arbitrary access to the entire topic graph. /// /// The TopicRepository associated with the controller. - protected ITopicRepository TopicRepository { - get { - return _topicRepository; - } - } + protected ITopicRepository TopicRepository => _topicRepository; /*========================================================================================================================== | CURRENT TOPIC diff --git a/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs b/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs index 98618a8e..2e1b66e8 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs @@ -66,11 +66,7 @@ ITopicMappingService topicMappingService /// Provides a reference to the Topic Repository in order to gain arbitrary access to the entire topic graph. /// /// The TopicRepository associated with the controller. - protected ITopicRepository TopicRepository { - get { - return _topicRepository; - } - } + protected ITopicRepository TopicRepository => _topicRepository; /*========================================================================================================================== | CURRENT TOPIC diff --git a/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs b/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs index b93a6961..e85b8d19 100644 --- a/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs +++ b/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs @@ -37,8 +37,15 @@ public class TopicEntityViewModel { /// /// A Topic view model. public TopicEntityViewModel(ITopicRepository topicRepository, Topic topic) { + TopicRepository = topicRepository; Topic = topic; + RootTopic = topic; + + while (RootTopic.Parent != null) { + RootTopic = RootTopic.Parent; + } + } /*========================================================================================================================== @@ -57,17 +64,7 @@ public TopicEntityViewModel(ITopicRepository topicRepository, Topic topic) { /// Returns the root topic associated with the object graph. This can be used to easily find other topics in the tree. /// /// The at the root of the object graph. - public Topic RootTopic { - get { - if (_rootTopic == null) { - _rootTopic = Topic; - while (_rootTopic.Parent != null) { - _rootTopic = _rootTopic.Parent; - } - } - return _rootTopic; - } - } + public Topic RootTopic { get; } /*========================================================================================================================== | PROPERTY: TOPIC diff --git a/Ignia.Topics/AttributeValue.cs b/Ignia.Topics/AttributeValue.cs index cf2afd04..04178f82 100644 --- a/Ignia.Topics/AttributeValue.cs +++ b/Ignia.Topics/AttributeValue.cs @@ -38,11 +38,6 @@ namespace Ignia.Topics { /// public class AttributeValue { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private string _key = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -68,16 +63,15 @@ public AttributeValue(string key, string value, bool isDirty = true) { /*------------------------------------------------------------------------------------------------------------------------ | Validate input \-----------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(!String.IsNullOrWhiteSpace(key)); - TopicFactory.ValidateKey(key); + TopicFactory.ValidateKey(key, false); /*------------------------------------------------------------------------------------------------------------------------ | Set local values \-----------------------------------------------------------------------------------------------------------------------*/ - Key = key; - Value = value; - IsDirty = isDirty; - EnforceBusinessLogic = true; + Key = key; + Value = value; + IsDirty = isDirty; + EnforceBusinessLogic = true; } @@ -120,14 +114,7 @@ internal AttributeValue(string key, string value, bool isDirty, bool enforceBusi /// exception="T:System.ArgumentException"> /// !value.Contains(" ") /// - public string Key { - get => _key; - private set { - Contract.Requires(!String.IsNullOrWhiteSpace(value)); - TopicFactory.ValidateKey(value); - _key = value; - } - } + public string Key { get; } /*========================================================================================================================== | PROPERTY: VALUE @@ -135,10 +122,7 @@ private set { /// /// Gets the current value of the attribute. /// - public string Value { - get; - private set; - } + public string Value { get; } /*========================================================================================================================== | PROPERTY: IS DIRTY @@ -152,10 +136,7 @@ public string Value { /// when is called. Otherwise, it is ignored, /// thus preventing the need to update attributes (or create new versions of attributes) whose values haven't changed. /// - public bool IsDirty { - get; - set; - } + public bool IsDirty { get; set; } /*========================================================================================================================== | PROPERTY: ENFORCE BUSINESS LOGIC @@ -182,10 +163,7 @@ public bool IsDirty { /// exception="T:System.ArgumentException"> /// !value.Contains(" ") /// - internal bool EnforceBusinessLogic { - get; - set; - } + internal bool EnforceBusinessLogic { get; set; } /*========================================================================================================================= | PROPERTY: LAST MODIFIED @@ -193,7 +171,7 @@ internal bool EnforceBusinessLogic { /// /// Read-only reference to the last DateTime the instance was updated. /// - public readonly DateTime LastModified = DateTime.Now; + public DateTime LastModified { get; } = DateTime.Now; } // Class diff --git a/Ignia.Topics/Mapping/AttributeKeyAttribute.cs b/Ignia.Topics/Mapping/AttributeKeyAttribute.cs index 88a3057e..2ab12c31 100644 --- a/Ignia.Topics/Mapping/AttributeKeyAttribute.cs +++ b/Ignia.Topics/Mapping/AttributeKeyAttribute.cs @@ -24,11 +24,6 @@ namespace Ignia.Topics.Mapping { [System.AttributeUsage(System.AttributeTargets.Property)] public sealed class AttributeKeyAttribute : System.Attribute { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private string _attributeKey = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -38,7 +33,7 @@ public sealed class AttributeKeyAttribute : System.Attribute { /// The key value of the attribute associated with the current property. public AttributeKeyAttribute(string attributeKey) { TopicFactory.ValidateKey(attributeKey, false); - _attributeKey = attributeKey; + Value = attributeKey; } /*========================================================================================================================== @@ -47,11 +42,7 @@ public AttributeKeyAttribute(string attributeKey) { /// /// Gets the value of the attribute key. /// - public string Value { - get { - return _attributeKey; - } - } + public string Value { get; } } //Class diff --git a/Ignia.Topics/Mapping/FilterByAttributeAttribute.cs b/Ignia.Topics/Mapping/FilterByAttributeAttribute.cs index 483c73ca..1e16e349 100644 --- a/Ignia.Topics/Mapping/FilterByAttributeAttribute.cs +++ b/Ignia.Topics/Mapping/FilterByAttributeAttribute.cs @@ -21,12 +21,6 @@ namespace Ignia.Topics.Mapping { [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple=true, Inherited=true)] public sealed class FilterByAttributeAttribute : System.Attribute { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private string _attributeKey = null; - private string _attributeValue = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -38,8 +32,8 @@ public sealed class FilterByAttributeAttribute : System.Attribute { /// The value of the attribute to filter by. public FilterByAttributeAttribute(string attributeKey, string attributeValue) { TopicFactory.ValidateKey(attributeKey, false); - _attributeKey = attributeKey; - _attributeValue = attributeValue; + Key = attributeKey; + Value = attributeValue; } /*========================================================================================================================== @@ -48,11 +42,7 @@ public FilterByAttributeAttribute(string attributeKey, string attributeValue) { /// /// Gets the attribute key. /// - public string Key { - get { - return _attributeKey; - } - } + public string Key { get; } /*========================================================================================================================== | PROPERTY: VALUE @@ -60,11 +50,7 @@ public string Key { /// /// Gets the value of the attribute. /// - public string Value { - get { - return _attributeValue; - } - } + public string Value { get; } } //Class diff --git a/Ignia.Topics/Mapping/MetadataAttribute.cs b/Ignia.Topics/Mapping/MetadataAttribute.cs index f36f1074..74e014bc 100644 --- a/Ignia.Topics/Mapping/MetadataAttribute.cs +++ b/Ignia.Topics/Mapping/MetadataAttribute.cs @@ -21,11 +21,6 @@ namespace Ignia.Topics.Mapping { [System.AttributeUsage(System.AttributeTargets.Property)] public sealed class MetadataAttribute : System.Attribute { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private string _key = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -35,7 +30,7 @@ public sealed class MetadataAttribute : System.Attribute { /// The key represents the name of the Metadata topic that should be mapped to. public MetadataAttribute(string key) { TopicFactory.ValidateKey(key, false); - _key = key; + Key = key; } /*========================================================================================================================== @@ -44,12 +39,7 @@ public MetadataAttribute(string key) { /// /// Gets the value of the key. /// - public string Key { - get { - return _key; - } - } + public string Key { get; } } //Class - } //Namespace diff --git a/Ignia.Topics/Mapping/RelationshipAttribute.cs b/Ignia.Topics/Mapping/RelationshipAttribute.cs index 2554f32f..16f8b79e 100644 --- a/Ignia.Topics/Mapping/RelationshipAttribute.cs +++ b/Ignia.Topics/Mapping/RelationshipAttribute.cs @@ -30,12 +30,6 @@ namespace Ignia.Topics.Mapping { [System.AttributeUsage(System.AttributeTargets.Property)] public sealed class RelationshipAttribute : System.Attribute { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private string _key = null; - private RelationshipType _type = RelationshipType.Any; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -47,8 +41,8 @@ public sealed class RelationshipAttribute : System.Attribute { /// Optional. The type of collection the relationship is associated with. public RelationshipAttribute(string key, RelationshipType type = RelationshipType.Any) { TopicFactory.ValidateKey(key, false); - _key = key; - _type = type; + Key = key; + Type = type; } /// @@ -56,7 +50,7 @@ public RelationshipAttribute(string key, RelationshipType type = RelationshipTyp /// /// Optional. The type of collection the relationship is associated with. public RelationshipAttribute(RelationshipType type = RelationshipType.Any) { - _type = type; + Type = type; } /*========================================================================================================================== @@ -65,11 +59,7 @@ public RelationshipAttribute(RelationshipType type = RelationshipType.Any) { /// /// Gets the value of the relationship key. /// - public string Key { - get { - return _key; - } - } + public string Key { get; } /*========================================================================================================================== | PROPERTY: TYPE @@ -77,11 +67,7 @@ public string Key { /// /// Gets the value of the relationship type. /// - public RelationshipType Type { - get { - return _type; - } - } + public RelationshipType Type { get; } } //Class diff --git a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs index f04d069f..bcc434f3 100644 --- a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs +++ b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs @@ -20,11 +20,6 @@ namespace Ignia.Topics.Reflection { /// internal class MemberInfoCollection : KeyedCollection where T : MemberInfo { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - Type _type = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -35,7 +30,7 @@ internal class MemberInfoCollection : KeyedCollection where T : Me /// The associated with the collection. internal MemberInfoCollection(Type type) : base(StringComparer.OrdinalIgnoreCase) { Contract.Requires(type != null); - _type = type; + Type = type; foreach ( var member in type.GetMembers( @@ -60,7 +55,7 @@ in type.GetMembers( internal MemberInfoCollection(Type type, IEnumerable members) : base(StringComparer.OrdinalIgnoreCase) { Contract.Requires(type != null); Contract.Requires(members != null); - _type = type; + Type = type; foreach (var member in members) { Add(member); } @@ -72,7 +67,7 @@ internal MemberInfoCollection(Type type, IEnumerable members) : base(StringCo /// /// Returns the type associated with this collection. /// - internal Type Type => _type; + internal Type Type { get; } /*========================================================================================================================== | OVERRIDE: GET KEY FOR ITEM diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 1d7107cf..9ac7c5ed 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -26,6 +26,22 @@ internal class TypeCollection : KeyedCollection { static List _settableTypes = null; private Type _attributeFlag = null; + /*========================================================================================================================== + | CONSTRUCTOR (STATIC) + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Initializes static properties on . + /// + static TypeCollection() { + SettableTypes = new List { + typeof(bool), + typeof(int), + typeof(string), + typeof(DateTime) + }; + } + + /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -364,19 +380,7 @@ private object GetValueObject(Type type, string value) { /// /// A list of types that are allowed to be set using . /// - internal List SettableTypes { - get { - if (_settableTypes == null) { - _settableTypes = new List { - typeof(bool), - typeof(int), - typeof(string), - typeof(DateTime) - }; - } - return _settableTypes; - } - } + static internal List SettableTypes { get; } /*========================================================================================================================== | OVERRIDE: GET KEY FOR ITEM diff --git a/Ignia.Topics/Repositories/DeleteEventArgs.cs b/Ignia.Topics/Repositories/DeleteEventArgs.cs index f2ca65c3..aa189f24 100644 --- a/Ignia.Topics/Repositories/DeleteEventArgs.cs +++ b/Ignia.Topics/Repositories/DeleteEventArgs.cs @@ -15,11 +15,6 @@ namespace Ignia.Topics.Repositories { /// public class DeleteEventArgs : EventArgs { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private Topic _topic = null; - /*========================================================================================================================== | CONSTRUCTOR: TAXONOMY DELETE EVENT ARGS \-------------------------------------------------------------------------------------------------------------------------*/ @@ -28,7 +23,7 @@ public class DeleteEventArgs : EventArgs { /// /// The topic. public DeleteEventArgs(Topic topic) : base() { - _topic = topic; + Topic = topic; } /*========================================================================================================================== @@ -37,10 +32,7 @@ public DeleteEventArgs(Topic topic) : base() { /// /// Getter that returns the Topic object associated with the event /// - public Topic Topic { - get => _topic; - set => _topic = value; - } + public Topic Topic { get; set; } } // Class From aba54bc2a339fb656512fc73d75e830e37dff0e9 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 16:59:22 -0700 Subject: [PATCH 037/149] Implemented `readonly` variables Quite a few private fields in the library are actually `readonly` and can be marked as such. This will help avoid abuse, and may provide minor performance benefits. --- Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs | 2 +- Ignia.Topics.Web.Mvc/Controllers/SitemapController.cs | 2 +- Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs | 6 +++--- Ignia.Topics.Web.Mvc/TopicViewResult.cs | 4 ++-- Ignia.Topics/Collections/AttributeValueCollection.cs | 2 +- Ignia.Topics/Collections/NamedTopicCollection.cs | 2 +- Ignia.Topics/Collections/RelatedTopicCollection.cs | 4 ++-- Ignia.Topics/Collections/TopicCollection{T}.cs | 2 +- Ignia.Topics/TopicFactory.cs | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs b/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs index c7f33d84..3c27aa37 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs @@ -26,7 +26,7 @@ public class RedirectController : Controller { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - private ITopicRepository _topicRepository = null; + private readonly ITopicRepository _topicRepository = null; /*========================================================================================================================== | CONSTRUCTOR diff --git a/Ignia.Topics.Web.Mvc/Controllers/SitemapController.cs b/Ignia.Topics.Web.Mvc/Controllers/SitemapController.cs index 14e1773f..13d799c2 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/SitemapController.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/SitemapController.cs @@ -21,7 +21,7 @@ public class SitemapController : Controller { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - private ITopicRepository _topicRepository = null; + private readonly ITopicRepository _topicRepository = null; /*========================================================================================================================== | CONSTRUCTOR diff --git a/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs b/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs index 39684162..d9e49429 100644 --- a/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs +++ b/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs @@ -27,9 +27,9 @@ public class MvcTopicRoutingService : ITopicRoutingService { /*============================================================================================================================ | PRIVATE VARIABLES \---------------------------------------------------------------------------------------------------------------------------*/ - private ITopicRepository _topicRepository = null; - private RouteData _routes = null; - private Uri _uri = null; + private readonly ITopicRepository _topicRepository = null; + private readonly RouteData _routes = null; + private readonly Uri _uri = null; private Topic _topic = null; /*========================================================================================================================== diff --git a/Ignia.Topics.Web.Mvc/TopicViewResult.cs b/Ignia.Topics.Web.Mvc/TopicViewResult.cs index ac1dc3e3..c75f28cc 100644 --- a/Ignia.Topics.Web.Mvc/TopicViewResult.cs +++ b/Ignia.Topics.Web.Mvc/TopicViewResult.cs @@ -22,8 +22,8 @@ public class TopicViewResult : ViewResult { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - string _contentType = ""; - string _topicView = ""; + readonly string _contentType = ""; + readonly string _topicView = ""; /*========================================================================================================================== | CONSTRUCTOR diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index a27c9cfa..106f42cc 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -27,8 +27,8 @@ public class AttributeValueCollection : KeyedCollection /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - private Topic _associatedTopic = null; static TypeCollection _typeCache = new TypeCollection(typeof(AttributeSetterAttribute)); + private readonly Topic _associatedTopic = null; private int _setCounter = 0; /*========================================================================================================================== diff --git a/Ignia.Topics/Collections/NamedTopicCollection.cs b/Ignia.Topics/Collections/NamedTopicCollection.cs index 75947e0d..03e484b9 100644 --- a/Ignia.Topics/Collections/NamedTopicCollection.cs +++ b/Ignia.Topics/Collections/NamedTopicCollection.cs @@ -24,7 +24,7 @@ public class NamedTopicCollection: TopicCollection { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - string _name = ""; + readonly string _name = ""; /*========================================================================================================================== | CONSTRUCTOR diff --git a/Ignia.Topics/Collections/RelatedTopicCollection.cs b/Ignia.Topics/Collections/RelatedTopicCollection.cs index 043112d1..2aed07d3 100644 --- a/Ignia.Topics/Collections/RelatedTopicCollection.cs +++ b/Ignia.Topics/Collections/RelatedTopicCollection.cs @@ -21,8 +21,8 @@ public class RelatedTopicCollection : KeyedCollection: KeyedCollection, IEnumerable wher /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - Topic _parent = null; + readonly Topic _parent = null; /*========================================================================================================================== | CONSTRUCTOR diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index 6ba777ef..4ca38d0d 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -23,7 +23,7 @@ public static class TopicFactory { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static Dictionary _typeLookup = new Dictionary(); + static readonly Dictionary _typeLookup = new Dictionary(); /*========================================================================================================================== | METHOD: GET TOPIC TYPE From 1d32cb5e0c09eb50d9378cae20ff2e22f33ebc4e Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:00:33 -0700 Subject: [PATCH 038/149] Minor formatting updates --- Ignia.Topics/Collections/AttributeValueCollection.cs | 6 +++++- Ignia.Topics/Collections/RelatedTopicCollection.cs | 5 ++++- Ignia.Topics/Repositories/ITopicRepositoryContract.cs | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index 106f42cc..c63cc52b 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -24,10 +24,14 @@ namespace Ignia.Topics.Collections { /// public class AttributeValueCollection : KeyedCollection { + /*========================================================================================================================== + | STATIC VARIABLES + \-------------------------------------------------------------------------------------------------------------------------*/ + static readonly TypeCollection _typeCache = new TypeCollection(typeof(AttributeSetterAttribute)); + /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static TypeCollection _typeCache = new TypeCollection(typeof(AttributeSetterAttribute)); private readonly Topic _associatedTopic = null; private int _setCounter = 0; diff --git a/Ignia.Topics/Collections/RelatedTopicCollection.cs b/Ignia.Topics/Collections/RelatedTopicCollection.cs index 2aed07d3..b1d800a1 100644 --- a/Ignia.Topics/Collections/RelatedTopicCollection.cs +++ b/Ignia.Topics/Collections/RelatedTopicCollection.cs @@ -219,7 +219,10 @@ public void SetTopic(string scope, Topic topic, bool isIncoming) { \-----------------------------------------------------------------------------------------------------------------------*/ if (!isIncoming) { if (_isIncoming) { - throw new ArgumentException("You are attempting to set an incoming relationship on a RelatedTopicCollection that is not flagged as IsIncoming", "isIncoming"); + throw new ArgumentException( + "You are attempting to set an incoming relationship on a RelatedTopicCollection that is not flagged as IsIncoming", + nameof(isIncoming) + ); } topic.IncomingRelationships.SetTopic(scope, _parent, true); } diff --git a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs index fe7c7d94..896ba0ad 100644 --- a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs +++ b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs @@ -88,7 +88,10 @@ public Topic Load(int topicId, DateTime version) { | Validate return value \-----------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(version.Date < DateTime.Now, "The version requested must be a valid historical date."); - Contract.Requires(version.Date > new DateTime(2014, 12, 9), "The version is expected to have been created since version support was introduced into the topic library."); + Contract.Requires( + version.Date > new DateTime(2014, 12, 9), + "The version is expected to have been created since version support was introduced into the topic library." + ); /*------------------------------------------------------------------------------------------------------------------------ | Provide dummy return value From 0a3ffad2c9b4f839ab518df23648bf32d938e730 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:01:00 -0700 Subject: [PATCH 039/149] Converted to auto-implemented properties --- Ignia.Topics/Repositories/MoveEventArgs.cs | 20 ++++---------------- Ignia.Topics/Repositories/RenameEventArgs.cs | 12 ++---------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Ignia.Topics/Repositories/MoveEventArgs.cs b/Ignia.Topics/Repositories/MoveEventArgs.cs index d845565f..852532df 100644 --- a/Ignia.Topics/Repositories/MoveEventArgs.cs +++ b/Ignia.Topics/Repositories/MoveEventArgs.cs @@ -19,12 +19,6 @@ namespace Ignia.Topics.Repositories { /// public class MoveEventArgs : EventArgs { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private Topic _topic = null; - private Topic _target = null; - /*========================================================================================================================== | CONSTRUCTOR: TAXONOMY MOVE EVENT ARGS \-------------------------------------------------------------------------------------------------------------------------*/ @@ -49,8 +43,8 @@ public MoveEventArgs(Topic topic, Topic target) { Contract.Requires(topic != null, "topic"); Contract.Requires(target != null, "target"); Contract.Requires(topic != target, "The topic cannot be its own parent."); - _topic = topic; - _target = target; + Topic = topic; + Target = target; } /*========================================================================================================================== @@ -59,10 +53,7 @@ public MoveEventArgs(Topic topic, Topic target) { /// /// Gets or sets the Topic object associated with the event. /// - public Topic Topic { - get => _topic; - set => _topic = value; - } + public Topic Topic { get; set; } /*========================================================================================================================== | PROPERTY: TARGET @@ -70,10 +61,7 @@ public Topic Topic { /// /// Gets or sets the new parent that the topic will be moved to. /// - public Topic Target { - get => _target; - set => _target = value; - } + public Topic Target { get; set; } } // Class diff --git a/Ignia.Topics/Repositories/RenameEventArgs.cs b/Ignia.Topics/Repositories/RenameEventArgs.cs index cb8ae99b..22a74ae1 100644 --- a/Ignia.Topics/Repositories/RenameEventArgs.cs +++ b/Ignia.Topics/Repositories/RenameEventArgs.cs @@ -15,11 +15,6 @@ namespace Ignia.Topics.Repositories { /// public class RenameEventArgs : EventArgs { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private Topic _topic = null; - /*========================================================================================================================== | CONSTRUCTOR: TAXONOMY RENAME EVENT ARGS \-------------------------------------------------------------------------------------------------------------------------*/ @@ -37,7 +32,7 @@ public RenameEventArgs() { } /// /// The topic object associated with the rename event. public RenameEventArgs(Topic topic) { - _topic = topic; + Topic = topic; } /*========================================================================================================================== @@ -49,10 +44,7 @@ public RenameEventArgs(Topic topic) { /// /// The topic. /// - public Topic Topic { - get => _topic; - set => _topic = value; - } + public Topic Topic { get; } } // Class From 57e41b49cab384b1833777091256e91fce3acdb8 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:02:32 -0700 Subject: [PATCH 040/149] Established `static` constructors Initialized `static` fields inside constructor so that they can be marked as `readonly`. --- Ignia.Topics/Mapping/RelationshipMap.cs | 29 ++++++----- Ignia.Topics/Mapping/TopicMappingService.cs | 56 ++++++++++++--------- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/Ignia.Topics/Mapping/RelationshipMap.cs b/Ignia.Topics/Mapping/RelationshipMap.cs index 581d5df1..55447847 100644 --- a/Ignia.Topics/Mapping/RelationshipMap.cs +++ b/Ignia.Topics/Mapping/RelationshipMap.cs @@ -25,26 +25,25 @@ namespace Ignia.Topics.Mapping { static internal class RelationshipMap { /*========================================================================================================================== - | STATIC VARIABLES + | CONSTRUCTOR (STATIC) \-------------------------------------------------------------------------------------------------------------------------*/ - static Dictionary _mappings = null; + static RelationshipMap() { + + var mappings = new Dictionary { + { RelationshipType.Children, Relationships.Children }, + { RelationshipType.Relationship, Relationships.Relationships }, + { RelationshipType.NestedTopics, Relationships.None }, + { RelationshipType.IncomingRelationship, Relationships.IncomingRelationships } + }; + + Mappings = mappings; + + } /*========================================================================================================================== | PROPERTY: MAPPINGS \-------------------------------------------------------------------------------------------------------------------------*/ - static internal Dictionary Mappings { - get { - if (_mappings == null) { - var mappings = new Dictionary(); - mappings.Add(RelationshipType.Children, Relationships.Children); - mappings.Add(RelationshipType.Relationship, Relationships.Relationships); - mappings.Add(RelationshipType.NestedTopics, Relationships.None); - mappings.Add(RelationshipType.IncomingRelationship, Relationships.IncomingRelationships); - _mappings = mappings; - } - return _mappings; - } - } + static internal Dictionary Mappings { get; } } } diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index f9c13ad4..bfd6129f 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -29,14 +29,45 @@ public class TopicMappingService : ITopicMappingService { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static Dictionary _typeLookup = null; - static TypeCollection _typeCache = new TypeCollection(); + static readonly Dictionary _typeLookup = null; + static readonly TypeCollection _typeCache = new TypeCollection(); /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ readonly ITopicRepository _topicRepository = null; + /*========================================================================================================================== + | CONSTRUCTOR (STATIC) + \-------------------------------------------------------------------------------------------------------------------------*/ + #pragma warning disable CA1810 // Initialize reference type static fields inline + /// + /// Establishes static variables for the . + /// + static TopicMappingService() { + + /*---------------------------------------------------------------------------------------------------------------------- + | Ensure cache is populated + \---------------------------------------------------------------------------------------------------------------------*/ + var typeLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); + var matchedTypes = AppDomain + .CurrentDomain + .GetAssemblies() + .SelectMany(t => t.GetTypes()) + .Where(t => t.IsClass && t.Name.EndsWith("TopicViewModel", StringComparison.InvariantCultureIgnoreCase)) + .OrderBy(t => t.Namespace.Equals("Ignia.Topics.ViewModels")) + .ToList(); + foreach (var type in matchedTypes) { + var associatedContentType = type.Name.Replace("TopicViewModel", ""); + if (!typeLookup.ContainsKey(associatedContentType)) { + typeLookup.Add(associatedContentType, type); + } + } + _typeLookup = typeLookup; + + } + #pragma warning restore CA1810 // Initialize reference type static fields inline + /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -96,27 +127,6 @@ private static Type GetViewModelType(string contentType) { Contract.Ensures(Contract.Result() != null); TopicFactory.ValidateKey(contentType); - /*---------------------------------------------------------------------------------------------------------------------- - | Ensure cache is populated - \---------------------------------------------------------------------------------------------------------------------*/ - if (_typeLookup == null) { - var typeLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); - var matchedTypes = AppDomain - .CurrentDomain - .GetAssemblies() - .SelectMany(t => t.GetTypes()) - .Where(t => t.IsClass && t.Name.EndsWith("TopicViewModel", StringComparison.InvariantCultureIgnoreCase)) - .OrderBy(t => t.Namespace.Equals("Ignia.Topics.ViewModels")) - .ToList(); - foreach (var type in matchedTypes) { - var associatedContentType = type.Name.Replace("TopicViewModel", ""); - if (!typeLookup.ContainsKey(associatedContentType)) { - typeLookup.Add(associatedContentType, type); - } - } - _typeLookup = typeLookup; - } - /*---------------------------------------------------------------------------------------------------------------------- | Return cached entry \---------------------------------------------------------------------------------------------------------------------*/ From a59128eb2b23f74305071812526c80bcb9e3595b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:03:33 -0700 Subject: [PATCH 041/149] Moved initialization inside constructor This is just a minor formatting preference to avoid the line width from being violated while being able to maintain 32 character width columns. --- Ignia.Topics/AttributeDescriptor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/AttributeDescriptor.cs b/Ignia.Topics/AttributeDescriptor.cs index 4c716906..851a4ad6 100644 --- a/Ignia.Topics/AttributeDescriptor.cs +++ b/Ignia.Topics/AttributeDescriptor.cs @@ -44,7 +44,7 @@ public class AttributeDescriptor : Topic { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - private Dictionary _configuration = new Dictionary(); + private Dictionary _configuration = null; /*========================================================================================================================== | CONSTRUCTOR @@ -79,7 +79,9 @@ public AttributeDescriptor( contentType, parent, id - ) { } + ) { + _configuration = new Dictionary(); + } /*========================================================================================================================== | PROPERTY: TYPE From 33aaeef9a1988acdf4dbd4c364a50c51818d22b2 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:04:30 -0700 Subject: [PATCH 042/149] Converted methods to use Lamda expressions --- Ignia.Topics/Mapping/TopicMappingService.cs | 11 ++++------- Ignia.Topics/Reflection/TypeCollection.cs | 5 ++--- .../Repositories/ITopicRepositoryContract.cs | 14 +------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index bfd6129f..0d4bb621 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -162,10 +162,8 @@ private static Type GetViewModelType(string contentType) { /// The entity to derive the data from. /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. - public object Map(Topic topic, Relationships relationships = Relationships.All) { - return Map(topic, relationships, new Dictionary()); - - } + public object Map(Topic topic, Relationships relationships = Relationships.All) => + Map(topic, relationships, new Dictionary()); /// /// Given a topic, will identify any View Models named, by convention, "{ContentType}TopicViewModel" and populate them @@ -263,9 +261,8 @@ private object Map(Topic topic, Relationships relationships, Dictionary /// The target view model with the properties appropriately mapped. /// - public object Map(Topic topic, object target, Relationships relationships = Relationships.All) { - return Map(topic, target, relationships, new Dictionary()); - } + public object Map(Topic topic, object target, Relationships relationships = Relationships.All) => + Map(topic, target, relationships, new Dictionary()); /// /// Given a topic and an instance of a DTO, will populate the DTO according to the default mapping rules. diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 9ac7c5ed..424b9a61 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -82,9 +82,8 @@ internal MemberInfoCollection GetMembers(Type type) { /// If the collection cannot be found locally, it will be created. /// /// The type for which the members should be retrieved. - internal MemberInfoCollection GetMembers(Type type) where T: MemberInfo { - return new MemberInfoCollection(type, GetMembers(type).Where(m => typeof(T).IsAssignableFrom(m.GetType())).Cast()); - } + internal MemberInfoCollection GetMembers(Type type) where T: MemberInfo => + new MemberInfoCollection(type, GetMembers(type).Where(m => typeof(T).IsAssignableFrom(m.GetType())).Cast()); /*========================================================================================================================== | METHOD: GET MEMBER diff --git a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs index 896ba0ad..42c1298a 100644 --- a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs +++ b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs @@ -58,19 +58,7 @@ public abstract class ITopicRepositoryContract : ITopicRepository { /// The topic key. /// Determines whether or not to recurse through and load a topic's children. /// A topic object. - public Topic Load(string topicKey = null, bool isRecursive = true) { - - /*------------------------------------------------------------------------------------------------------------------------ - | Validate return value - \-----------------------------------------------------------------------------------------------------------------------*/ - //TopicFactory.ValidateKey(topicKey, true); - - /*------------------------------------------------------------------------------------------------------------------------ - | Provide dummy return value - \-----------------------------------------------------------------------------------------------------------------------*/ - return null; - - } + public Topic Load(string topicKey = null, bool isRecursive = true) => null; /// /// Loads a specific version of a topic based on its version. From 4ff137e3522ca194e27091ad2682130194e209e3 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:04:53 -0700 Subject: [PATCH 043/149] Used implicit variables where possible --- Ignia.Topics/Mapping/TopicMappingService.cs | 4 ++-- Ignia.Topics/Reflection/TypeCollection.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 0d4bb621..2ea53479 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -439,7 +439,7 @@ Dictionary cache //Handle flattening of children if (attributes.FlattenChildren) { - List flattenedList = new List(); + var flattenedList = new List(); foreach (var childTopic in listSource) { AddChildren(childTopic, flattenedList); } @@ -455,7 +455,7 @@ Dictionary cache //Validate and populate target collection if (listSource != null) { - foreach (Topic childTopic in listSource) { + foreach (var childTopic in listSource) { if (!attributes.SatisfiesAttributeFilters(childTopic)) { continue; } diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 424b9a61..e68922f3 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -172,7 +172,7 @@ internal bool SetPropertyValue(object target, string name, string value) { Contract.Assume(property != null); - object valueObject = GetValueObject(property.PropertyType, value); + var valueObject = GetValueObject(property.PropertyType, value); if (valueObject == null) { return false; @@ -271,7 +271,7 @@ internal bool SetMethodValue(object target, string name, string value) { Contract.Assume(method != null); - object valueObject = GetValueObject(method.GetParameters().First().ParameterType, value); + var valueObject = GetValueObject(method.GetParameters().First().ParameterType, value); if (valueObject == null) { return false; From 9ae7a1b148227ed9eccea7a8a962eaa14bdb4dee Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 17:05:20 -0700 Subject: [PATCH 044/149] Converted stateless methods to `static` --- Ignia.Topics/Mapping/TopicMappingService.cs | 2 +- Ignia.Topics/Reflection/TypeCollection.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 2ea53479..cdd98fc7 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -532,7 +532,7 @@ Dictionary cache /// The for the current property. /// The for the current property. /// The existing assignment of instances for the relationship. - private bool IsCurrentRelationship( + private static bool IsCurrentRelationship( RelationshipType targetRelationshipType, RelationshipType sourceRelationshipType, Relationships sourceRelationships, diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index e68922f3..5001e3e5 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -334,7 +334,7 @@ internal object GetMethodValue(object target, string name, Type targetType = nul /// Determines whether a given type is settable, either assuming the list of , or provided a /// specific . /// - private bool IsSettableType(Type sourceType, Type targetType = null) { + private static bool IsSettableType(Type sourceType, Type targetType = null) { if (targetType != null) { return sourceType.Equals(targetType); @@ -349,7 +349,7 @@ private bool IsSettableType(Type sourceType, Type targetType = null) { /// /// Converts a string value to an object of the target type. /// - private object GetValueObject(Type type, string value) { + private static object GetValueObject(Type type, string value) { var valueObject = (object)null; From ec311e4c8a01214096cd82f8a4cbf1e726376e26 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 19:08:30 -0700 Subject: [PATCH 045/149] Updated `Product`, `Title`, `Description`, and `Version` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `AssemblyVersion` is now set to include a build number, which I established to be 1735—735 for the current number of commits in GitHub, and 1000 as a ballpark representation of all of the builds prior to porting to GitHub. --- .../Properties/AssemblyInfo.cs | 53 ++++++++---------- .../Properties/AssemblyInfo.cs | 54 ++++++++----------- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 54 ++++++++----------- .../Properties/AssemblyInfo.cs | 54 ++++++++----------- .../Properties/AssemblyInfo.cs | 54 ++++++++----------- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 54 ++++++++----------- Ignia.Topics/Properties/AssemblyInfo.cs | 47 +++++----------- 7 files changed, 152 insertions(+), 218 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index f51fefcf..9f087d92 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -1,36 +1,29 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.Data.Caching")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.Data.Caching")] -[assembly: AssemblyCopyright("Copyright © 2017")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2018 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("OnTopic Cached Repository")] +[assembly: AssemblyDescription("Provides a caching decorator for ITopicRepository implementations.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] +[assembly: AssemblyConfiguration("")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 47ec470f..126f19ae 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -1,36 +1,28 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.Data.Sql")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.Data.Sql")] -[assembly: AssemblyCopyright("Copyright © 2017")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2018 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia SQL Server Repository")] +[assembly: AssemblyDescription("Provides Microsoft SQL Server support for persisting the OnTopic graph to a database.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index cac244f3..a72527ae 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,28 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.Tests")] -[assembly: AssemblyCopyright("Copyright © 2015")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2018 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia OnTopic Unit Tests")] +[assembly: AssemblyDescription("Provides unit tests for the OnTopic library.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index a4d7debf..29fc68f0 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -1,36 +1,28 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.ViewModels")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.ViewModels")] -[assembly: AssemblyCopyright("Copyright © 2018")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2018 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia OnTopic View Models")] +[assembly: AssemblyDescription("Provides view models that map to the factory default content type schemas.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 8fbe89bb..202e482a 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -1,36 +1,28 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.Web.Mvc")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.Web.Mvc")] -[assembly: AssemblyCopyright("Copyright © 2017")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2015 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia OnTopic MVC Library")] +[assembly: AssemblyDescription("Provides presentation-layer support for the ASP.NET MVC Framework.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 5b64f7c4..8ad2ddee 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -1,36 +1,28 @@ -using System.Reflection; +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Ignia.Topics.Web")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ignia.Topics.Web")] -[assembly: AssemblyCopyright("Copyright © 2017")] +/*============================================================================================================================== +| DEFINE ASSEMBLY ATTRIBUTES +>=============================================================================================================================== +| Declare and define attributes used in the compiling of the finished assembly. +\-----------------------------------------------------------------------------------------------------------------------------*/ +[assembly: AssemblyCompany("Ignia, LLC")] +[assembly: AssemblyCopyright("Copyright © 2015 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia OnTopic WebForms Library")] +[assembly: AssemblyDescription("Deprecated. Provides backward compatibility for the ASP.NET WebForms Framework.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f03124b3..ff805e84 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -1,47 +1,28 @@ -/*=========================================================================================================================== -| COPYRIGHT (C) 2004-2014 IGNIA, LLC. NOT LICENSED FOR REDISTRIBUTION. -\--------------------------------------------------------------------------------------------------------------------------*/ - -/*=========================================================================================================================== -| IGNIA TOPICS LIBRARY -| - +/*============================================================================================================================== | Author Ignia, LLC -| Client Ignia -| Project Topics -| -| Purpose A content management system (CMS) based on structured data. -| ->============================================================================================================================ -| Revisions Date Author Comments -| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -| 03.24.09 Casey Margell Created initial version. -| 08.28.10 Jeremy Caney Released version 2.0. -| 08.14.14 Katherine Trunkey Updated for version 3.0 -\--------------------------------------------------------------------------------------------------------------------------*/ - -/*=========================================================================================================================== -| DECLARE NAMESPACE REFERENCES -\--------------------------------------------------------------------------------------------------------------------------*/ +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -/*=========================================================================================================================== +/*============================================================================================================================== | DEFINE ASSEMBLY ATTRIBUTES ->============================================================================================================================ +>=============================================================================================================================== | Declare and define attributes used in the compiling of the finished assembly. -\--------------------------------------------------------------------------------------------------------------------------*/ +\-----------------------------------------------------------------------------------------------------------------------------*/ [assembly: AssemblyCompany("Ignia, LLC")] -[assembly: AssemblyCopyright("Ignia, LLC. All rights reserved.")] -[assembly: AssemblyProduct("Ignia Topics Library")] -[assembly: AssemblyTitle("Ignia Topics Library")] +[assembly: AssemblyCopyright("Copyright © 2018 Ignia, LLC")] +[assembly: AssemblyProduct("Ignia OnTopic Library")] +[assembly: AssemblyTitle("Ignia OnTopic Library")] [assembly: AssemblyDescription("Libraries for supporting Ignia Topics, a content management system (CMS) based on structured, hierarchical data.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: InternalsVisibleTo("Ignia.Topics.Tests")] +[assembly: AssemblyCulture("en")] +[assembly: AssemblyVersion("3.5.1735.0")] +[assembly: AssemblyFileVersion("3.5.1735.0")] [assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] +[assembly: AssemblyConfiguration("")] \ No newline at end of file From 1804bcf89719878ed0b45799a7bdbc61c38eb381 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 19:10:07 -0700 Subject: [PATCH 046/149] Configuration of Precision Infinity's Automatic Versions plugin Precision Infinity's Automatic Versions plugin for Visual Studio allows us to automatically increment the build number. (I've configured the Major and Minor releases to require manual updating.) --- Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index 6b8983e0..3a8b2bfa 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -12,6 +12,16 @@ v4.5 512 + True + False + False + False + False + + + False + SettingsVersion + None true From a58b0c616ffc730a116f92432cff4c7511aa060f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 19:17:29 -0700 Subject: [PATCH 047/149] Reintroduced `InternalsVisibleTo` attribute --- Ignia.Topics/Properties/AssemblyInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index ff805e84..bd2a2ed3 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,6 +22,7 @@ [assembly: AssemblyCulture("en")] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From d91316b046061b49612545cfcf07f0775289bfbc Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 19:19:09 -0700 Subject: [PATCH 048/149] Removed unnecessary variable These were previously set to use an auto-implemented properties. --- Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs | 5 ----- Ignia.Topics/Reflection/TypeCollection.cs | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs b/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs index e85b8d19..9c2ebb24 100644 --- a/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs +++ b/Ignia.Topics.Web.Mvc/Models/TopicEntityViewModel.cs @@ -24,11 +24,6 @@ namespace Ignia.Topics.Web.Mvc.Models { /// public class TopicEntityViewModel { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - private Topic _rootTopic = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 5001e3e5..0de9d4ad 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -23,8 +23,7 @@ internal class TypeCollection : KeyedCollection { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static List _settableTypes = null; - private Type _attributeFlag = null; + private readonly Type _attributeFlag = null; /*========================================================================================================================== | CONSTRUCTOR (STATIC) From 02a233f195960923b43eaba191bdb3372a5aca40 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 20:39:22 -0700 Subject: [PATCH 049/149] Fixed `AssemblyInfo` files For reasons that aren't fully isolated (possibly the unnecessarily-specified culture?) the previous changes to the `AssemblyInfo.cs` files prevented Code Analysis from working properly. This update fixes that issue. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 8 ++++---- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 7 +++---- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 7 +++---- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 8 ++++---- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 7 +++---- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 7 +++---- Ignia.Topics/Properties/AssemblyInfo.cs | 6 +++--- 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 9f087d92..fb515dd0 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,12 +17,13 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("OnTopic Cached Repository")] [assembly: AssemblyDescription("Provides a caching decorator for ITopicRepository implementations.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] -[assembly: AssemblyConfiguration("")] + diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 126f19ae..12268e48 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,11 +17,11 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia SQL Server Repository")] [assembly: AssemblyDescription("Provides Microsoft SQL Server support for persisting the OnTopic graph to a database.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index a72527ae..06f67a31 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,11 +17,11 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia OnTopic Unit Tests")] [assembly: AssemblyDescription("Provides unit tests for the OnTopic library.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 29fc68f0..4e2e2c09 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,11 +17,12 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia OnTopic View Models")] [assembly: AssemblyDescription("Provides view models that map to the factory default content type schemas.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file + diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 202e482a..79b39166 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,11 +17,11 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia OnTopic MVC Library")] [assembly: AssemblyDescription("Provides presentation-layer support for the ASP.NET MVC Framework.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 8ad2ddee..ff527582 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ \=============================================================================================================================*/ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /*============================================================================================================================== @@ -18,11 +17,11 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia OnTopic WebForms Library")] [assembly: AssemblyDescription("Deprecated. Provides backward compatibility for the ASP.NET WebForms Framework.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index bd2a2ed3..92a912ad 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -18,12 +18,12 @@ [assembly: AssemblyProduct("Ignia OnTopic Library")] [assembly: AssemblyTitle("Ignia OnTopic Library")] [assembly: AssemblyDescription("Libraries for supporting Ignia Topics, a content management system (CMS) based on structured, hierarchical data.")] +[assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("en")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1735.0")] [assembly: AssemblyFileVersion("3.5.1735.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] -[assembly: System.Runtime.InteropServices.ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] -[assembly: AssemblyConfiguration("")] \ No newline at end of file From 49cb2839c7d2b0cbf8ec6e189b3101c7de0e5360 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 21:09:45 -0700 Subject: [PATCH 050/149] Fixed various spelling errors across files I suppose I should have installed a spellchecker in Visual Studio a long time ago. --- .../CachedTopicRepository.cs | 6 +++--- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Data.Sql.Database/README.md | 4 ++-- .../Stored Procedures/topics_CreateTopic.sql | 2 +- .../Stored Procedures/topics_GetTopics.sql | 2 +- .../Stored Procedures/topics_MoveTopic.sql | 4 ++-- .../Stored Procedures/topics_UpdateTopic.sql | 2 +- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 20 +++++++++---------- .../AttributeValueCollectionTest.cs | 4 ++-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- .../TestDoubles/FakeTopicRepository.cs | 6 +++--- Ignia.Topics.Tests/TopicTest.cs | 2 +- .../ContentItemTopicViewModel.cs | 2 +- .../ContentListTopicViewModel.cs | 2 +- .../IndexTopicViewModel.cs | 2 +- Ignia.Topics.ViewModels/ItemTopicViewModel.cs | 2 +- .../LookupListItemTopicViewModel.cs | 2 +- .../PageGroupTopicViewModel.cs | 2 +- Ignia.Topics.ViewModels/PageTopicViewModel.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 ++-- .../SectionTopicViewModel.cs | 2 +- .../SlideTopicViewModel.cs | 2 +- .../SlideshowTopicViewModel.cs | 2 +- Ignia.Topics.ViewModels/TopicViewModel.cs | 2 +- .../TopicViewModelCollection.cs | 2 +- .../VideoTopicViewModel.cs | 2 +- .../Controllers/RedirectController.cs | 2 +- .../MvcTopicRoutingService.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 ++-- .../Configuration/SourceElement.cs | 8 ++++---- .../Configuration/TopicsSection.cs | 2 +- .../Editor/AttributeTypeControl.cs | 6 +++--- Ignia.Topics.Web/Editor/FilePath.cs | 6 +++--- Ignia.Topics.Web/Editor/IEditControl.cs | 6 +++--- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- .../WebFormsTopicRoutingService.cs | 6 +++--- Ignia.Topics/AttributeDescriptor.cs | 6 +++--- Ignia.Topics/AttributeSetterAttribute.cs | 2 +- .../Collections/AttributeValueCollection.cs | 6 +++--- Ignia.Topics/ContentTypeDescriptor.cs | 4 ++-- Ignia.Topics/InvalidKeyException.cs | 2 +- Ignia.Topics/Mapping/FollowAttribute.cs | 2 +- Ignia.Topics/Mapping/MetadataAttribute.cs | 2 +- Ignia.Topics/Mapping/PropertyConfiguration.cs | 2 +- Ignia.Topics/Mapping/README.md | 6 +++--- Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/README.md | 2 +- Ignia.Topics/Repositories/ITopicRepository.cs | 4 ++-- .../Repositories/ITopicRepositoryContract.cs | 4 ++-- Ignia.Topics/Repositories/MoveEventArgs.cs | 2 +- .../Repositories/TopicRepositoryBase.cs | 4 ++-- Ignia.Topics/Topic.cs | 10 +++++----- Ignia.Topics/TopicFactory.cs | 2 +- README.md | 2 +- 55 files changed, 103 insertions(+), 103 deletions(-) diff --git a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs index f6d8d15b..dd987518 100644 --- a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs +++ b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs @@ -64,7 +64,7 @@ public CachedTopicRepository(ITopicRepository dataProvider) : base() { | METHOD: LOAD \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -98,7 +98,7 @@ public override Topic Load(int topicId, bool isRecursive = true) { } /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified key name. + /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. @@ -211,7 +211,7 @@ private Topic GetTopic(Topic sourceTopic, string uniqueKey) { | Provide implicit root >------------------------------------------------------------------------------------------------------------------------- | ###NOTE JJC080313: While a root topic is required by the data structure, it should be implicit from the perspective of - | the calling application. A developer should be able to call GetTopic("Namepace:TopicPath") to get to a topic, without + | the calling application. A developer should be able to call GetTopic("Namespace:TopicPath") to get to a topic, without | needing to be aware of the root. \-----------------------------------------------------------------------------------------------------------------------*/ if ( diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index fb515dd0..c805eb48 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql.Database/README.md b/Ignia.Topics.Data.Sql.Database/README.md index 7a2ed894..f7d21856 100644 --- a/Ignia.Topics.Data.Sql.Database/README.md +++ b/Ignia.Topics.Data.Sql.Database/README.md @@ -5,7 +5,7 @@ The `Ignia.Topics.Data.Sql.Database` provides a default schema for supporting th ## Tables The following is a summary of the most relevant tables. -- **[`topics_Topics`](dbo/Tables/topics_Topics.sql)**: Represents the core hiearchy of topics, encoded in a nested set format. +- **[`topics_Topics`](dbo/Tables/topics_Topics.sql)**: Represents the core hierarchy of topics, encoded in a nested set format. - **[`topics_TopicAttributes`](dbo/Tables/topics_Topics.sql)**: Represents key/value pairs of topic attributes, including historical versions. - **[`topics_Blob`](dbo/Tables/topics_Blob.sql)**: Represents an XML-based blob of non-indexed attributes, which are too long for `topics_TopicAttributes`. - **[`topics_Relationships`](dbo/Tables/topics_Relationships.sql)**: Represents relationships between topics, segmented by namespace. @@ -24,7 +24,7 @@ The following is a summary of the most relevant stored procedures. - **[`topics_CreateTopic`](dbo/Stored%20Procedures/topics_CreateTopic.sql)**: Creates a new topic based on a `@ParentId`, an array of `@Attributes`, and an XML `@Blob`. Returns a new `@@Identity`. - **[`topics_DeleteTopic`](dbo/Stored%20Procedures/topics_DeleteTopic.sql)**: Deletes an existing topic based on a `@Id`. - **[`topics_MoveTopic`](dbo/Stored%20Procedures/topics_MoveTopic.sql)**: Moves an existing topic based on an `@Id`, `@ParentId`, and `@SiblingId`. -- **[`topics_UpdateTopic`](dbo/Stored%20Procedures/topics_UpdateTopic.sql)**: Updates an existing topic based on an `@Id`, an array of `@Attributes`, and a `@Blob`. Optionally deletes all relationships; these will need to be readded using `topics_PersistRelations`. Old attributes are persisted as previous versions. +- **[`topics_UpdateTopic`](dbo/Stored%20Procedures/topics_UpdateTopic.sql)**: Updates an existing topic based on an `@Id`, an array of `@Attributes`, and a `@Blob`. Optionally deletes all relationships; these will need to be re-added using `topics_PersistRelations`. Old attributes are persisted as previous versions. - **[`topics_PersistRelations`](dbo/Stored%20Procedures/topics_PersistRelations.sql)**: Associates a relationship with a topic based on a `@Source_TopicId`, array of `@Target_TopicIds`, and `@RelationshipTypeID` (which can be any string label). ## Views diff --git a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_CreateTopic.sql b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_CreateTopic.sql index 70381535..6029ee19 100644 --- a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_CreateTopic.sql +++ b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_CreateTopic.sql @@ -3,7 +3,7 @@ -- -- Purpose Creates a new topic. -- --- History Casey Margell 04062009 Initial Creation baseaed on code from Celko's Sql For Smarties. +-- History Casey Margell 04062009 Initial Creation based on code from Celko's SQL For Smarties. -- Jeremy Caney 05282010 Reformatted code and refactored identifiers for improved readability. -- Katherine Trunkey 08142014 Updated topic_TopicAttributes insertion script to use uncommon, multi-character delimiters -- rather than a colon and semicolon in order to provide better escaping safety for @Attributes. diff --git a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_GetTopics.sql b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_GetTopics.sql index cc078912..6121f03e 100644 --- a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_GetTopics.sql +++ b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_GetTopics.sql @@ -4,7 +4,7 @@ -- Purpose Gets the tree of current topics rooted FROM the provided TopicID. If no TopicID is provided then the sproc returns everything -- under the topic with the lowest id. -- --- History Casey Margell 04062009 Initial Creation baseaed on code FROM Celko's Sql For Smarties. +-- History Casey Margell 04062009 Initial Creation based on code FROM Celko's SQL For Smarties. -- Jeremy Caney 07192009 Added support for AttributeID lookup. -- Jeremy Caney 05282010 Reformatted code AND refactored identifiers for improved readability. -- Jeremy Caney 06072010 Added support for blob fields. diff --git a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_MoveTopic.sql b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_MoveTopic.sql index 7454ce9c..9529864f 100644 --- a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_MoveTopic.sql +++ b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_MoveTopic.sql @@ -8,7 +8,7 @@ -- Hedley Robertson 07062010 Added support for SiblingID (Ordering) -- Hedley Robertson 08122010 Inline test cases, debugging statements and check for re-parenting; -- now avoids moving item to start of SET when sibling move requested --- and parentid has not changed +-- and ParentId has not changed -- Hedley Robertson 08172010 Rebuilt with externalized MoveSubTree function -- Jeremy Caney 09222014 Updated logic for ParentID attribute to be based on Key, not ID -- Jeremy Caney 12092017 Refactored based on Celko's alternative formulation. @@ -124,7 +124,7 @@ SET @Offset = -- MOVE SOURCE RANGE TO INSERTION POINT ----------------------------------------------------------------------------------------------------------------------------------------------- -- The basic idea behind moving nodes is that we're going to a) shift the target subtree (@Parent) by the delta (@Offset) between its original --- position (@OriginalLeft, @OriginalRight) and the the target location (@InsertionPoint), while b) closing the gap left behind by shifting all +-- position (@OriginalLeft, @OriginalRight) and the target location (@InsertionPoint), while b) closing the gap left behind by shifting all -- intermediate nodes by the width of the target subtree (@OriginalRange). ----------------------------------------------------------------------------------------------------------------------------------------------- -- EXAMPLE: If we're moving a target subtree of width 12 down 26 nodes, then we'd a) subtract 26 (the @Offset) from all nodes between RangeLeft diff --git a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_UpdateTopic.sql b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_UpdateTopic.sql index c56a7cc0..34413691 100644 --- a/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_UpdateTopic.sql +++ b/Ignia.Topics.Data.Sql.Database/dbo/Stored Procedures/topics_UpdateTopic.sql @@ -3,7 +3,7 @@ -- -- Purpose Used to update the attributes of a provided node -- --- History Casey Margell 04062009 Initial Creation baseaed on code from Celko's Sql For Smarties +-- History Casey Margell 04062009 Initial Creation based on code from Celko's SQL For Smarties -- Jeremy Caney 05282010 Reformatted code and refactored identifiers for improved readability. -- Katherine Trunkey 08052014 Added parameter for Version (datetime). Updated procedure to always create new attribute -- records rather than deleting the existing attributes for the topic and recreating them. diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 12268e48..c585fb4a 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index c6f0738c..c3e554de 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -357,7 +357,7 @@ private static void SetVersionHistory(SqlDataReader reader, Dictionary - /// Loads a topic (and, optionally, all of its descendents) based on the specified topic key. + /// Loads a topic (and, optionally, all of its descendants) based on the specified topic key. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. @@ -433,7 +433,7 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { } /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic's unique identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -772,7 +772,7 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f /*------------------------------------------------------------------------------------------------------------------------ | Establish attribute strings \-----------------------------------------------------------------------------------------------------------------------*/ - // Strings are immutable, use a stringbuilder to save memory + // Strings are immutable, use a StringBuilder to save memory var attributes = new StringBuilder(); var nullAttributes = new StringBuilder(); var blob = new StringBuilder(); @@ -908,7 +908,7 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f } /*------------------------------------------------------------------------------------------------------------------------ - | Catch excewption + | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (Exception ex) { throw new Exception("Failed to save Topic " + topic.Key + " (" + topic.Id + ") via " + _connectionString + ": " + ex.Message); @@ -1183,9 +1183,9 @@ private static string PersistRelations(Topic topic, SqlConnection connection, bo | METHOD: CREATE RELATIONSHIPS BLOB \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Internal helper function to build string of related xml nodes for each scope of related items in model. + /// Internal helper function to build string of related XML nodes for each scope of related items in model. /// - /// The topic object for which to create the relationsihps. + /// The topic object for which to create the relationships. /// The blob string. /// topic != null private static string CreateRelationshipsBlob(Topic topic) { @@ -1196,7 +1196,7 @@ private static string CreateRelationshipsBlob(Topic topic) { Contract.Requires(topic != null, "The topic must not be null."); /*------------------------------------------------------------------------------------------------------------------------ - | Create blog string container + | Create blob string container \-----------------------------------------------------------------------------------------------------------------------*/ var blob = new StringBuilder(""); @@ -1259,7 +1259,7 @@ private static SqlDbType ConvDbType(string sqlDbType) { | METHOD: ADD SQL PARAMETER \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Wrapper function that adds a SQL paramter to a command object. + /// Wrapper function that adds a SQL parameter to a command object. /// /// The SQL command object. /// The SQL parameter. @@ -1273,7 +1273,7 @@ SqlDbType sqlDbType ) => AddSqlParameter(commandObject, sqlParameter, fieldValue, sqlDbType, ParameterDirection.Input, -1); /// - /// Adds a SQL paramter to a command object, additionally setting the specified parameter direction. + /// Adds a SQL parameter to a command object, additionally setting the specified parameter direction. /// /// The SQL command object. /// The SQL parameter. @@ -1287,7 +1287,7 @@ SqlDbType sqlDbType ) => AddSqlParameter(commandObject, sqlParameter, null, sqlDbType, paramDirection, -1); /// - /// Adds a SQL paramter to a command object, additionally setting the specified SQL data length for the field. + /// Adds a SQL parameter to a command object, additionally setting the specified SQL data length for the field. /// /// The SQL command object. /// The SQL parameter. diff --git a/Ignia.Topics.Tests/AttributeValueCollectionTest.cs b/Ignia.Topics.Tests/AttributeValueCollectionTest.cs index 1737ed9a..7af5937b 100644 --- a/Ignia.Topics.Tests/AttributeValueCollectionTest.cs +++ b/Ignia.Topics.Tests/AttributeValueCollectionTest.cs @@ -178,7 +178,7 @@ public void AttributeValueCollection_SetValue_BackdoorTest() { /// Attempts to violate the business logic by bypassing the property setter; ensures that business logic is enforced. /// [TestMethod] - [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a backdoor, without routing it through the Key property.")] + [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a back door, without routing it through the Key property.")] public void AttributeValueCollection_EnforceBusinessLogicTest() { var topic = TopicFactory.Create("Test", "Container"); topic.Attributes.SetValue("Key", "# ?"); @@ -191,7 +191,7 @@ public void AttributeValueCollection_EnforceBusinessLogicTest() { /// Attempts to violate the business logic by bypassing SetValue() entirely; ensures that business logic is enforced. /// [TestMethod] - [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a backdoor, without routing it through the Key property.")] + [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a back door, without routing it through the Key property.")] public void AttributeValueCollection_EnforceBusinessLogic_BackdoorTest() { var topic = TopicFactory.Create("Test", "Container"); topic.Attributes.Remove("Key"); diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 06f67a31..811f2e5f 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index 578a46e5..9e9e2e9a 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -54,7 +54,7 @@ public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { | METHOD: LOAD \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -64,7 +64,7 @@ public override Topic Load(int topicId, bool isRecursive = true) { } /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified key name. + /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. @@ -228,7 +228,7 @@ public override void Delete(Topic topic, bool isRecursive = false) { | METHOD: CREATE FAKE DATA \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Creates a collection of fake data that loosely mimics a barebones database. + /// Creates a collection of fake data that loosely mimics a bare bones database. /// private void CreateFakeData() { diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index bd6eba84..7dc2be0e 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -56,7 +56,7 @@ public void Topic_Change_IdTest() { | TEST: IS (CONTENT) TYPE OF \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Associates a new topic with several contnet types, and confirms that the topic is reported as a type of those content + /// Associates a new topic with several content types, and confirms that the topic is reported as a type of those content /// types. /// [TestMethod] diff --git a/Ignia.Topics.ViewModels/ContentItemTopicViewModel.cs b/Ignia.Topics.ViewModels/ContentItemTopicViewModel.cs index 3cae4a69..7db78ae1 100644 --- a/Ignia.Topics.ViewModels/ContentItemTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/ContentItemTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class ContentItemTopicViewModel: ItemTopicViewModel { diff --git a/Ignia.Topics.ViewModels/ContentListTopicViewModel.cs b/Ignia.Topics.ViewModels/ContentListTopicViewModel.cs index 1cdf702d..44a6a181 100644 --- a/Ignia.Topics.ViewModels/ContentListTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/ContentListTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class ContentListTopicViewModel: PageTopicViewModel { diff --git a/Ignia.Topics.ViewModels/IndexTopicViewModel.cs b/Ignia.Topics.ViewModels/IndexTopicViewModel.cs index 57d1ea77..5e42d4b7 100644 --- a/Ignia.Topics.ViewModels/IndexTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/IndexTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class IndexTopicViewModel: PageTopicViewModel { diff --git a/Ignia.Topics.ViewModels/ItemTopicViewModel.cs b/Ignia.Topics.ViewModels/ItemTopicViewModel.cs index 4e0814f4..8baebda6 100644 --- a/Ignia.Topics.ViewModels/ItemTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/ItemTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class ItemTopicViewModel : TopicViewModel { diff --git a/Ignia.Topics.ViewModels/LookupListItemTopicViewModel.cs b/Ignia.Topics.ViewModels/LookupListItemTopicViewModel.cs index f4185f60..b3b69384 100644 --- a/Ignia.Topics.ViewModels/LookupListItemTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/LookupListItemTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class LookupListItemTopicViewModel: ItemTopicViewModel { diff --git a/Ignia.Topics.ViewModels/PageGroupTopicViewModel.cs b/Ignia.Topics.ViewModels/PageGroupTopicViewModel.cs index abd4391e..40989063 100644 --- a/Ignia.Topics.ViewModels/PageGroupTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/PageGroupTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class PageGroupTopicViewModel : SectionTopicViewModel { diff --git a/Ignia.Topics.ViewModels/PageTopicViewModel.cs b/Ignia.Topics.ViewModels/PageTopicViewModel.cs index 15080511..b6ce06d1 100644 --- a/Ignia.Topics.ViewModels/PageTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/PageTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class PageTopicViewModel: TopicViewModel, IPageTopicViewModel { diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 4e2e2c09..d3614877 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.ViewModels/SectionTopicViewModel.cs b/Ignia.Topics.ViewModels/SectionTopicViewModel.cs index 417fe36f..7301c3a6 100644 --- a/Ignia.Topics.ViewModels/SectionTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/SectionTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class SectionTopicViewModel : TopicViewModel { diff --git a/Ignia.Topics.ViewModels/SlideTopicViewModel.cs b/Ignia.Topics.ViewModels/SlideTopicViewModel.cs index 4dbbb467..36a3b1f3 100644 --- a/Ignia.Topics.ViewModels/SlideTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/SlideTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class SlideTopicViewModel: ContentItemTopicViewModel { diff --git a/Ignia.Topics.ViewModels/SlideshowTopicViewModel.cs b/Ignia.Topics.ViewModels/SlideshowTopicViewModel.cs index 7b91e79d..9b12f040 100644 --- a/Ignia.Topics.ViewModels/SlideshowTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/SlideshowTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class SlideshowTopicViewModel: ContentListTopicViewModel { diff --git a/Ignia.Topics.ViewModels/TopicViewModel.cs b/Ignia.Topics.ViewModels/TopicViewModel.cs index 1273cdcd..934853a5 100644 --- a/Ignia.Topics.ViewModels/TopicViewModel.cs +++ b/Ignia.Topics.ViewModels/TopicViewModel.cs @@ -16,7 +16,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class TopicViewModel: ITopicViewModel { diff --git a/Ignia.Topics.ViewModels/TopicViewModelCollection.cs b/Ignia.Topics.ViewModels/TopicViewModelCollection.cs index b7e399d0..e62f9979 100644 --- a/Ignia.Topics.ViewModels/TopicViewModelCollection.cs +++ b/Ignia.Topics.ViewModels/TopicViewModelCollection.cs @@ -20,7 +20,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class TopicViewModelCollection: KeyedCollection where TItem: ITopicViewModel { diff --git a/Ignia.Topics.ViewModels/VideoTopicViewModel.cs b/Ignia.Topics.ViewModels/VideoTopicViewModel.cs index 5fbb15bd..2fb14dea 100644 --- a/Ignia.Topics.ViewModels/VideoTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/VideoTopicViewModel.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.ViewModels { /// /// /// Typically, view models should be created as part of the presentation layer. The namespace contains - /// default implementations that can be used directly, used as base classes, or overwritten at the presentative level. They + /// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They /// are supplied for convenience to model factory default settings for out-of-the-box content types. /// public class VideoTopicViewModel: PageTopicViewModel { diff --git a/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs b/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs index 3c27aa37..387bd197 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/RedirectController.cs @@ -18,7 +18,7 @@ namespace Ignia.Topics.Web.Mvc.Controllers { /// Typically, a page is requested based on the value, which is a hash of /// its . When a is moved to a different location in the topic graph, /// however, its will return a different value, corresponding to its new location. To allow - /// permanent references to page, therefore, the the accepts paths based on the accepts paths based on the , which is expected to be stable for the lifetime of a entity. /// public class RedirectController : Controller { diff --git a/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs b/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs index d9e49429..bed63bf3 100644 --- a/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs +++ b/Ignia.Topics.Web.Mvc/MvcTopicRoutingService.cs @@ -37,7 +37,7 @@ public class MvcTopicRoutingService : ITopicRoutingService { \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Initializes a new instance of the class based on a URL instance, a fully qualified - /// path to the views Directory, and, optionally, the expected filename suffix fo each view file. + /// path to the views Directory, and, optionally, the expected filename suffix of each view file. /// public MvcTopicRoutingService( ITopicRepository topicRepository, diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 79b39166..2c530816 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Configuration/SourceElement.cs b/Ignia.Topics.Web/Configuration/SourceElement.cs index 7082a753..5d944270 100644 --- a/Ignia.Topics.Web/Configuration/SourceElement.cs +++ b/Ignia.Topics.Web/Configuration/SourceElement.cs @@ -193,7 +193,7 @@ public static string GetValue(SourceElement element) { | METHOD: IS ENABLED \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Looks up a source element at a given location and based on a specified parent configuration elemnt and the target + /// Looks up a source element at a given location and based on a specified parent configuration element and the target /// element's key, identifies the source value, and verifies whether the element is available, enabled, or set to true. /// /// The parent . @@ -204,7 +204,7 @@ public static string GetValue(SourceElement element) { public static bool IsEnabled(ConfigurationElement parent, string key) => IsEnabled(parent, key, true); /// - /// Looks up a source element at a given location and based on a specified parent configuration elemnt and the target + /// Looks up a source element at a given location and based on a specified parent configuration element and the target /// element's key, identifies the source value, and verifies whether the element is available, enabled, or set to true. /// /// The parent . @@ -218,7 +218,7 @@ public static bool IsEnabled(ConfigurationElement parent, string key, bool evalu } /// - /// Looks up a source element at a given location and based on a specified parent configuration elemnt collection and + /// Looks up a source element at a given location and based on a specified parent configuration element collection and /// the target element's key, identifies the source value, and verifies whether the element is available, enabled, or /// set to true. /// @@ -230,7 +230,7 @@ public static bool IsEnabled(ConfigurationElement parent, string key, bool evalu public static bool IsEnabled(ConfigurationElementCollection parent, string key) => IsEnabled(parent, key, false); /// - /// Looks up a source element at a given location and based on a specified parent configuration elemnt collection and + /// Looks up a source element at a given location and based on a specified parent configuration element collection and /// the target element's key, identifies the source value, and verifies whether the element is available, enabled, or /// set to true. /// diff --git a/Ignia.Topics.Web/Configuration/TopicsSection.cs b/Ignia.Topics.Web/Configuration/TopicsSection.cs index 6ad6d408..93b28213 100644 --- a/Ignia.Topics.Web/Configuration/TopicsSection.cs +++ b/Ignia.Topics.Web/Configuration/TopicsSection.cs @@ -20,7 +20,7 @@ public class TopicsSection : ConfigurationSection { | FACTORY METHOD: GET CONFIG \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets the topics element from the web.config as the configuation section. + /// Gets the topics element from the web.config as the configuration section. /// /// The topics section of the implementing client's configuration. public static TopicsSection GetConfig() { diff --git a/Ignia.Topics.Web/Editor/AttributeTypeControl.cs b/Ignia.Topics.Web/Editor/AttributeTypeControl.cs index 4f9401aa..0fa450d7 100644 --- a/Ignia.Topics.Web/Editor/AttributeTypeControl.cs +++ b/Ignia.Topics.Web/Editor/AttributeTypeControl.cs @@ -31,7 +31,7 @@ public AttributeTypeControl() : base() { } | PROPERTY: INHERITED VALUE \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets or sets the the value of a control, as inherited from any Topic pointers. + /// Gets or sets the value of a control, as inherited from any Topic pointers. /// /// /// If this value is set, then the control should not be marked as required, as it is inheriting its value from a @@ -46,7 +46,7 @@ public AttributeTypeControl() : base() { } /// Gets or sets the value of a control, ignoring inheritance. /// /// - /// The value should not be set to an inherited value, as otherwise, that value will end up being duplicatd in the local + /// The value should not be set to an inherited value, as otherwise, that value will end up being duplicated in the local /// Topic. /// public virtual string Value { get; set; } @@ -60,7 +60,7 @@ public AttributeTypeControl() : base() { } /// /// This allows the type to retrieve configuration or extended attributes specifically from an attribute, as opposed to /// having them set via the DefaultValues attribute. This allows the Attribute Content Type to be subclassed, thus - /// permitting more user-friendly interfaces to be developed for particular attributes when using the Oroborus + /// permitting more user-friendly interfaces to be developed for particular attributes when using the Oroboros /// Configuration. For instance, if an attribute-related control includes a property named Color, then an attribute could /// be added to that Attribute's Content Type allowing the color to be defined, as opposed to the publisher needing to /// know how to set the Color attribute using the DefaultValue attribute. diff --git a/Ignia.Topics.Web/Editor/FilePath.cs b/Ignia.Topics.Web/Editor/FilePath.cs index 86848164..bc41f206 100644 --- a/Ignia.Topics.Web/Editor/FilePath.cs +++ b/Ignia.Topics.Web/Editor/FilePath.cs @@ -55,7 +55,7 @@ public static string GetPath( if (topic == null || String.IsNullOrEmpty(attributeKey)) return ""; /*------------------------------------------------------------------------------------------------------------------------ - | Build configured file path string base on values and settings paramters passed to the method + | Build configured file path string base on values and settings parameters passed to the method \-----------------------------------------------------------------------------------------------------------------------*/ string filePath = ""; string relativePath = null; @@ -73,7 +73,7 @@ public static string GetPath( } /*------------------------------------------------------------------------------------------------------------------------ - | Add topic keys (directory names) between the start topic and the end topic based on the topic's webpath property + | Add topic keys (directory names) between the start topic and the end topic based on the topic's WebPath property \-----------------------------------------------------------------------------------------------------------------------*/ if (startTopic != null) { Contract.Assume( @@ -101,7 +101,7 @@ public static string GetPath( filePath += relativePath; /*------------------------------------------------------------------------------------------------------------------------ - | Replace path slashes with backslahes if the resulting file path value uses a UNC or basic file path format + | Replace path slashes with backslashes if the resulting file path value uses a UNC or basic file path format \-----------------------------------------------------------------------------------------------------------------------*/ if (filePath.IndexOf("\\") >= 0) { filePath = filePath.Replace("/", "\\"); diff --git a/Ignia.Topics.Web/Editor/IEditControl.cs b/Ignia.Topics.Web/Editor/IEditControl.cs index 56d9933f..0bf03f7e 100644 --- a/Ignia.Topics.Web/Editor/IEditControl.cs +++ b/Ignia.Topics.Web/Editor/IEditControl.cs @@ -22,7 +22,7 @@ public interface IEditControl { | PROPERTY: INHERITED VALUE \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets or sets the the value of a control, as inherited from any Topic pointers. + /// Gets or sets the value of a control, as inherited from any Topic pointers. /// /// /// If this value is set, then the control should not be marked as required, as it is inheriting its value from a @@ -37,7 +37,7 @@ public interface IEditControl { /// Gets or sets the value of a control, ignoring inheritance. /// /// - /// The value should not be set to an inherited value, as otherwise, that value will end up being duplicatd in the local + /// The value should not be set to an inherited value, as otherwise, that value will end up being duplicated in the local /// Topic. /// string Value { get; set; } @@ -51,7 +51,7 @@ public interface IEditControl { /// /// This allows the type to retrieve configuration or extended attributes specifically from an attribute, as opposed to /// having them set via the DefaultValues attribute. This allows the Attribute Content Type to be subclassed, thus - /// permitting more user-friendly interfaces to be developed for particular attributes when using the Oroborus + /// permitting more user-friendly interfaces to be developed for particular attributes when using the Oroboros /// Configuration. For instance, if an attribute-related control includes a property named Color, then an attribute could /// be added to that Attribute's Content Type allowing the color to be defined, as opposed to the publisher needing to /// know how to set the Color attribute using the DefaultValue attribute. diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index ff527582..35d3bd53 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics.Web/WebFormsTopicRoutingService.cs b/Ignia.Topics.Web/WebFormsTopicRoutingService.cs index 7f07c4a0..9f776030 100644 --- a/Ignia.Topics.Web/WebFormsTopicRoutingService.cs +++ b/Ignia.Topics.Web/WebFormsTopicRoutingService.cs @@ -59,7 +59,7 @@ public class WebFormsTopicRoutingService : ITopicRoutingService { \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Initializes a new instance of the class based on a URL instance, a fully qualified - /// path to the views Directory, and, optionally, the expected filename suffix fo each view file. + /// path to the views Directory, and, optionally, the expected filename suffix of each view file. /// /// /// Because the is distributed with, and intended exclusively for use with, the ASP.NET @@ -188,7 +188,7 @@ public string View { >------------------------------------------------------------------------------------------------------------------------- | ### TODO JJC071617: This introduces an unwanted dependency on HttpContext. While this is possible, it may introduce | incompatibilities with the context (e.g., do ASP.NET MVC and ASP.NET Web Forms expose the same HttpContext objects? - | Preferrably, this would be handled by each environment as appropriate, but that might muddle the logic. + | Preferably, this would be handled by each environment as appropriate, but that might muddle the logic. \-----------------------------------------------------------------------------------------------------------------------*/ if (viewName == null && _headers["Accept"] != null) { var acceptHeaders = _headers["Accept"].ToString(); @@ -257,7 +257,7 @@ private List Views { var subDirectories = viewsDirectoryInfo.GetDirectories("*", SearchOption.AllDirectories); /*-------------------------------------------------------------------------------------------------------------------- - | Disvoer all view templates available via the configured path + | Discover all view templates available via the configured path \-------------------------------------------------------------------------------------------------------------------*/ // Get top-level (generic) view files foreach (var file in viewsDirectoryInfo.GetFiles(searchPattern, searchOption)) { diff --git a/Ignia.Topics/AttributeDescriptor.cs b/Ignia.Topics/AttributeDescriptor.cs index 851a4ad6..6fe8b4a9 100644 --- a/Ignia.Topics/AttributeDescriptor.cs +++ b/Ignia.Topics/AttributeDescriptor.cs @@ -59,7 +59,7 @@ public class AttributeDescriptor : Topic { /// correctly save new topics to the database. When the parameter is set, however, the property is set to false on as well as on , since it is assumed these are being set to the same values currently used in the - /// persistance store. + /// persistence store. /// /// A string representing the key for the new topic instance. /// A string representing the key of the target content type. @@ -87,7 +87,7 @@ public AttributeDescriptor( | PROPERTY: TYPE \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets or sets the filename refence to the Attribute Type control associate with the Topic object. + /// Gets or sets the filename reference to the Attribute Type control associate with the Topic object. /// /// /// The type attribute maps to the name of a control, directive, or partial view in the editor representing the specific @@ -270,7 +270,7 @@ public string DefaultValue { /// /// Attributes that are needed to provide indexes, sitemaps, navigation, etc. should be indexed so that they're always /// available in memory without requiring an additional database query. These increase the memory requirements of the - /// application, but reduce the number of database roundtrips required for topics that are accessed outside of a single + /// application, but reduce the number of database round-trips required for topics that are accessed outside of a single /// page. For instance, the title and description of a topic may be cross-referenced on other pages or as part of the /// navigation, and should thus be indexed. /// diff --git a/Ignia.Topics/AttributeSetterAttribute.cs b/Ignia.Topics/AttributeSetterAttribute.cs index c95781a6..0fe61157 100644 --- a/Ignia.Topics/AttributeSetterAttribute.cs +++ b/Ignia.Topics/AttributeSetterAttribute.cs @@ -21,7 +21,7 @@ namespace Ignia.Topics { /// to see if a property with the same name as the attribute key exists, and whether that property is decorated with the /// (i.e., [AttributeSetter]). If it is, then the update will be /// routed through that property. This ensures that business logic is enforced by local properties, instead of allowing - /// business logic to be potentially bypassedby writing directly to the collection. + /// business logic to be potentially bypassed by writing directly to the collection. /// /// /// As an example, the property is adorned with the . As a diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index c63cc52b..5ba39355 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -63,7 +63,7 @@ internal AttributeValueCollection(Topic parentTopic) : base(StringComparer.Invar /// does not support inheritFromParent or inheritFromDerived (which otherwise default to true). /// /// The string identifier for the . - /// True if if the attribute value is marked as dirty; otherwise false. + /// True if the attribute value is marked as dirty; otherwise false. public bool IsDirty(string name) { if (!Contains(name)) { return false; @@ -171,7 +171,7 @@ private string GetValue(string name, string defaultValue, bool inheritFromParent } /*------------------------------------------------------------------------------------------------------------------------ - | Finaly, return default + | Finally, return default \-----------------------------------------------------------------------------------------------------------------------*/ return defaultValue; @@ -232,7 +232,7 @@ out var result \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Gets a named attribute from the Attributes dictionary with a specified default value, an optional setting for enabling - /// of inheritance, and an optional setting for searching through derived topics for values. Return as a datetime. + /// of inheritance, and an optional setting for searching through derived topics for values. Return as a DateTime. /// /// The string identifier for the . /// A string value to which to fall back in the case the value is not found. diff --git a/Ignia.Topics/ContentTypeDescriptor.cs b/Ignia.Topics/ContentTypeDescriptor.cs index 983b0e17..86402540 100644 --- a/Ignia.Topics/ContentTypeDescriptor.cs +++ b/Ignia.Topics/ContentTypeDescriptor.cs @@ -53,7 +53,7 @@ public class ContentTypeDescriptor : Topic { /// correctly save new topics to the database. When the parameter is set, however, the property is set to false on as well as on , since it is assumed these are being set to the same values currently used in the - /// persistance store. + /// persistence store. /// /// A string representing the key for the new topic instance. /// A string representing the key of the target content type. @@ -106,7 +106,7 @@ public bool DisableChildTopics { | PROPERTY: PERMITTED CONTENT TYPES \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides a readonly collection of what types of content types are permitted to be created as children of instances of + /// Provides a read-only collection of what types of content types are permitted to be created as children of instances of /// this content type. /// /// diff --git a/Ignia.Topics/InvalidKeyException.cs b/Ignia.Topics/InvalidKeyException.cs index ddc2b28f..8bab105c 100644 --- a/Ignia.Topics/InvalidKeyException.cs +++ b/Ignia.Topics/InvalidKeyException.cs @@ -72,7 +72,7 @@ public InvalidKeyException(string message, string paramName, Exception inner) : } /// - /// Instantiates a new instance of a class for serlialization. + /// Instantiates a new instance of a class for serialization. /// /// A instance with details about the serialization requirements. /// A instance with details about the request context. diff --git a/Ignia.Topics/Mapping/FollowAttribute.cs b/Ignia.Topics/Mapping/FollowAttribute.cs index 48badbb1..ca90d69c 100644 --- a/Ignia.Topics/Mapping/FollowAttribute.cs +++ b/Ignia.Topics/Mapping/FollowAttribute.cs @@ -44,7 +44,7 @@ public FollowAttribute(Relationships relationships = Relationships.All) { | PROPERTY: RELATIONSHIPS \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets the type(s) of relationships that should be recused over. + /// Gets the type(s) of relationships that should be recursed over. /// public Relationships Relationships { get; } = Relationships.All; diff --git a/Ignia.Topics/Mapping/MetadataAttribute.cs b/Ignia.Topics/Mapping/MetadataAttribute.cs index 74e014bc..8166f3c0 100644 --- a/Ignia.Topics/Mapping/MetadataAttribute.cs +++ b/Ignia.Topics/Mapping/MetadataAttribute.cs @@ -13,7 +13,7 @@ namespace Ignia.Topics.Mapping { /// Flags that a property should be mapped to a list of metadata available in the Configuration namespace. /// /// - /// In the Topic Editor, the TopicLookup allows editors to select values from dropdown lists representing topics. + /// In the Topic Editor, the TopicLookup allows editors to select values from drop-down lists representing topics. /// Those topics, by default, are stored in the Configuration:Metadata namespace. The metadata attribute allows a /// strongly-typed reference to be created, thus pulling either a reference to a specific topic (in the case of a single /// value property) or a collection of the metadata (in the case of a collection). diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 2f29e2f7..ae69437f 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -286,7 +286,7 @@ public PropertyConfiguration(PropertyInfo property) { /// /// When is specified, implementations are expected to /// apply all other constraints. For instance, if the target collection is strongly typed, then only DTOs that exhibit - /// polymorphic compatability with that type will be included (i.e., DTOs of the same or a derived type as the target + /// polymorphic compatibility with that type will be included (i.e., DTOs of the same or a derived type as the target /// collection). Similarly, any will be applied to each of the descendants. Finally, if /// the target collection has a unique constraint (e.g., due to implementing ) /// then any items that would constitute a duplicate will be filtered out without raising an exception. This diff --git a/Ignia.Topics/Mapping/README.md b/Ignia.Topics/Mapping/README.md index 822cd7d0..e56e296c 100644 --- a/Ignia.Topics/Mapping/README.md +++ b/Ignia.Topics/Mapping/README.md @@ -142,7 +142,7 @@ In this example, the properties would map to: - `Children`: A collection of child topics, with all relationships (but not e.g. grandchildren) loaded. - `Contacts`: A list of `Employee` nested topics, filtered by those with `IsActive` set to `1` (`true`) and `Role` set to "Account Manager". Includes any descendants of the nested topics that meet the previous criteria. -> *Note*: Often times, data transfer objects won't require any attributes. These are only needed if the properties don't follow the built-in conventions and require additional help. For instance, the `[Relationship(…)]` attribute is useful if the relationship key is ambigious between outgoing relationships and incoming relationships. +> *Note*: Often times, data transfer objects won't require any attributes. These are only needed if the properties don't follow the built-in conventions and require additional help. For instance, the `[Relationship(…)]` attribute is useful if the relationship key is ambiguous between outgoing relationships and incoming relationships. ## Polymorphism If a reference type (e.g., `TopicViewModel Parent`) or a strongly-typed collection property (e.g., `List`) are defined, then any target instances must be assignable by the base type (in these cases, `TopicViewModel`). If they cannot be, then they will not be included; no error will occur. @@ -167,7 +167,7 @@ The [`CachedTopicMappingService`](CachedTopicMappingService.cs) Decorator, which - `topicMappingService.Map(topic, Relationships.Children)` - `topicMappingService.Map(topic, Relationships.Children)` -To implement the caching decorator, use the following construction as a Singleton lifecycle in your composer: +To implement the caching decorator, use the following construction as a Singleton lifestyle in your composer: ``` var topicRepository = new SqlTopicRepository(…); var topicMappingService = new TopicMappingService(topicRepository); @@ -176,4 +176,4 @@ var cachedTopicMappingService = new CachedTopicMappingService(topicMappingServic > *Note:* Be aware that the `CachedTopicMappingService` may take up considerable memory, depending on how many permutations of mapped objects the application has. This is especially true since it caches each unique object graph; no effort is made to centralize references to e.g. relationships that reference the same object instance. -> *Note:* The `CachedTopicMappingService` makes no effort to validate or evict cache entries. Topics whose values change during the lifetyime of the `CachedTopicMappingService` will not be reflected in the mapped responses. \ No newline at end of file +> *Note:* The `CachedTopicMappingService` makes no effort to validate or evict cache entries. Topics whose values change during the lifetime of the `CachedTopicMappingService` will not be reflected in the mapped responses. \ No newline at end of file diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 92a912ad..0f2632c5 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1735.0")] -[assembly: AssemblyFileVersion("3.5.1735.0")] +[assembly: AssemblyVersion("3.5.1736.0")] +[assembly: AssemblyFileVersion("3.5.1736.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/README.md b/Ignia.Topics/README.md index 05d867aa..23d17440 100644 --- a/Ignia.Topics/README.md +++ b/Ignia.Topics/README.md @@ -26,7 +26,7 @@ Out of the box, the OnTopic library contains two specially derived topics for su In addition to the above key classes, the `Ignia.Topics` assembly contains a number of specialized collections. These include: - **[`TopicCollection{T}`](Collections/TopicCollection{T}.cs)**: A `KeyedCollection` of a `Topic` (or derivative) keyed by `Id` and `Key`. - **[`TopicCollection`](Collections/TopicCollection.cs)**: A `KeyedCollection` of `Topic` keyed by `Id` and `Key`. - - **[`NamedTopicCollection`](Collections/NamedTopicCollection.cs)**: Proviedes a unique name to a `TopicCollection` so it can be keyed as part of a collection-of-collections. + - **[`NamedTopicCollection`](Collections/NamedTopicCollection.cs)**: Provides a unique name to a `TopicCollection` so it can be keyed as part of a collection-of-collections. - **[`ReadOnlyTopicCollection{T}`](Collections/ReadOnlyTopicCollection{T}.cs)**: A read-only `KeyedCollection` of a `Topic` (or derivative) keyed by `Id` and `Key`. - **[`ReadOnlyTopicCollection`](Collections/ReadOnlyTopicCollection.cs)**: A read-only `KeyedCollection` of `Topic` keyed by `Id` and `Key`. - **[`RelatedTopicCollection`](Collections/RelatedTopicCollection.cs)**: A `KeyedCollection` of `NamedTopicCollection` objects, keyed by `Name`, thus providing a collection-of-collections. diff --git a/Ignia.Topics/Repositories/ITopicRepository.cs b/Ignia.Topics/Repositories/ITopicRepository.cs index 4b1cce84..3e49111c 100644 --- a/Ignia.Topics/Repositories/ITopicRepository.cs +++ b/Ignia.Topics/Repositories/ITopicRepository.cs @@ -49,7 +49,7 @@ public interface ITopicRepository { \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -57,7 +57,7 @@ public interface ITopicRepository { Topic Load(int topicId, bool isRecursive = true); /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified key name. + /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. diff --git a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs index 42c1298a..5fcbcce7 100644 --- a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs +++ b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs @@ -45,7 +45,7 @@ public abstract class ITopicRepositoryContract : ITopicRepository { \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -53,7 +53,7 @@ public abstract class ITopicRepositoryContract : ITopicRepository { public Topic Load(int topicId, bool isRecursive = true) => null; /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified key name. + /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. diff --git a/Ignia.Topics/Repositories/MoveEventArgs.cs b/Ignia.Topics/Repositories/MoveEventArgs.cs index 852532df..fcf99673 100644 --- a/Ignia.Topics/Repositories/MoveEventArgs.cs +++ b/Ignia.Topics/Repositories/MoveEventArgs.cs @@ -29,7 +29,7 @@ public MoveEventArgs() { } /// /// Initializes a new instance of the class and sets the and - /// propreties based on the specified objects. + /// properties based on the specified objects. /// /// The topic object associated with the move event. /// The parent topic object targeted by the move event. diff --git a/Ignia.Topics/Repositories/TopicRepositoryBase.cs b/Ignia.Topics/Repositories/TopicRepositoryBase.cs index 4adf4c13..822e39e7 100644 --- a/Ignia.Topics/Repositories/TopicRepositoryBase.cs +++ b/Ignia.Topics/Repositories/TopicRepositoryBase.cs @@ -47,7 +47,7 @@ public abstract class TopicRepositoryBase : ITopicRepository { | METHOD: LOAD \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified unique identifier. + /// Loads a topic (and, optionally, all of its descendants) based on the specified unique identifier. /// /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. @@ -55,7 +55,7 @@ public abstract class TopicRepositoryBase : ITopicRepository { public abstract Topic Load(int topicId, bool isRecursive = true); /// - /// Loads a topic (and, optionally, all of its descendents) based on the specified key name. + /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. /// /// The topic key. /// Determines whether or not to recurse through and load a topic's children. diff --git a/Ignia.Topics/Topic.cs b/Ignia.Topics/Topic.cs index 2e5ba29d..7d14c216 100644 --- a/Ignia.Topics/Topic.cs +++ b/Ignia.Topics/Topic.cs @@ -43,7 +43,7 @@ public class Topic { /// cref="ContentType"/> will be set to , which is required in order to correctly save /// new topics to the database. When the parameter is set, however, the property is set to falseon and , as - /// it is assumed these are being set to the same values currently used in the persistance store. + /// it is assumed these are being set to the same values currently used in the persistence store. /// /// A string representing the key for the new topic instance. /// A string representing the key of the target content type. @@ -115,7 +115,7 @@ public int Id { /// /// /// While topics may be represented as a network graph via relationships, they are physically stored and primarily - /// represented via a hierarchy. As such, each topic may have at most a single parent. Note that the the root node will + /// represented via a hierarchy. As such, each topic may have at most a single parent. Note that the root node will /// have a null parent. /// /// @@ -330,7 +330,7 @@ public string Description { /// The value is stored in the database as a string (Attribute) value, but converted to DateTime for use in the system. It /// is important to note that the last modified attribute is not tied to the system versioning (which operates at an /// attribute level) nor is it guaranteed to be correct for auditing purposes; for example, the author may explicitly - /// overwrite this value for various reasons (such as backdating a webpage). + /// overwrite this value for various reasons (such as backdating a web page). /// /// /// !string.IsNullOrWhiteSpace(value.ToString()) @@ -348,7 +348,7 @@ public DateTime LastModified { | METHOD: SET PARENT \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Changes the current while simultenaously ensuring that the sort order of the topics is + /// Changes the current while simultaneously ensuring that the sort order of the topics is /// maintained, assuming a is set. /// /// @@ -357,7 +357,7 @@ public DateTime LastModified { /// parent.Children.LastOrDefault(). /// /// The to move this under. - /// The to mvoe this to the right of. + /// The to move this to the right of. public void SetParent(Topic parent, Topic sibling = null) { /*------------------------------------------------------------------------------------------------------------------------ diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index 4ca38d0d..f9d69aba 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -165,7 +165,7 @@ public static Topic Create(string key, string contentType, Topic parent = null) /// /// When the parameter is set the property is set to /// false on as well as on , since it is assumed these are - /// being set to the same values currently used in the persistance store. + /// being set to the same values currently used in the persistence store. /// /// A string representing the key for the new topic instance. /// A string representing the key of the target content type. diff --git a/README.md b/README.md index e31ec932..20155146 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Specifically, it attempts to ensure that the responsibilities of the developer, - **Backend developers** have access to *data repositories*, *services*, and a rich *domain model* in C# for consuming the structured data and implementing any *business logic* via code. - **Designers and graphic producers** have access to light-weight *views* based on purpose-built *view models*, thus allowing them to focus exclusively on presentation concerns, without any platform-specific scaffolding. -This is contrasted to most traditional CMSs, which attempt to coordinate all of these via an editor by exposing design responsibilities (via themes, templates, and layouts) as well as development responsibilities (via plugins or components). This works well for a small project without distinct design or development resources, but introduces a lot of complexity for larger teams with established responsibilities. +This is contrasted to most traditional CMSs, which attempt to coordinate all of these via an editor by exposing design responsibilities (via themes, templates, and layouts) as well as development responsibilities (via plug-ins or components). This works well for a small project without distinct design or development resources, but introduces a lot of complexity for larger teams with established responsibilities. ### Multi-Device Optimized In addition, OnTopic is optimized for multi-client/multi-device scenarios since the content editor focuses exclusively on structured data. This allows entirely distinct presentation layers to be established. For instance, the same content can be accessed by an iOS app, a website, and even a web-based API for third-party consumption. By contrast, most CMSs are designed for one client only: a website (which may be mobile-friendly via responsive templates.) From a3b983785fcb7ad9295b5efb892a55f6d985310d Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 3 May 2018 21:19:07 -0700 Subject: [PATCH 051/149] [Breaking] Removed optional `type` parameter from parameter The .NET Framework Design Guidelines recommend against having optional parameters in the constructor of `Attribute`s. Instead, it is recommended that optional parameters be specified via settable properties. Updated the `FollowAttribute` as well as the documentation and one implementation to adhere to this. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Mapping/FollowAttribute.cs | 4 ++-- Ignia.Topics/Mapping/README.md | 4 ++-- Ignia.Topics/Mapping/RelationshipAttribute.cs | 9 +++------ Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- 11 files changed, 22 insertions(+), 25 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index c805eb48..dece4557 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index c585fb4a..90fc0587 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 811f2e5f..eefbf3d6 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs index d8353d95..2eab92d9 100644 --- a/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs +++ b/Ignia.Topics.Tests/ViewModels/SampleTopicViewModel.cs @@ -41,7 +41,7 @@ public class SampleTopicViewModel : PageTopicViewModel { public Collection Related { get; set; } - [Relationship("AmbiguousRelationship", RelationshipType.IncomingRelationship)] + [Relationship("AmbiguousRelationship", Type=RelationshipType.IncomingRelationship)] public TopicViewModelCollection RelationshipAlias { get; set; } } //Class diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index d3614877..259aea25 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 2c530816..52c4d49d 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 35d3bd53..5e6602e5 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/FollowAttribute.cs b/Ignia.Topics/Mapping/FollowAttribute.cs index ca90d69c..bdf8fc2a 100644 --- a/Ignia.Topics/Mapping/FollowAttribute.cs +++ b/Ignia.Topics/Mapping/FollowAttribute.cs @@ -36,7 +36,7 @@ public sealed class FollowAttribute : System.Attribute { /// Annotates a property with the by providing an . /// /// The specific relationships that should be crawled. - public FollowAttribute(Relationships relationships = Relationships.All) { + public FollowAttribute(Relationships relationships) { Relationships = relationships; } @@ -46,7 +46,7 @@ public FollowAttribute(Relationships relationships = Relationships.All) { /// /// Gets the type(s) of relationships that should be recursed over. /// - public Relationships Relationships { get; } = Relationships.All; + public Relationships Relationships { get; } } //Class diff --git a/Ignia.Topics/Mapping/README.md b/Ignia.Topics/Mapping/README.md index e56e296c..0ecababf 100644 --- a/Ignia.Topics/Mapping/README.md +++ b/Ignia.Topics/Mapping/README.md @@ -118,14 +118,14 @@ public class CompanyTopicViewModel { [Metadata("Countries")] public TopicViewModelCollection Countries { get; set; } - [Relationship("Companies", RelationshipType.IncomingRelationship)] + [Relationship("Companies", Type=RelationshipType.IncomingRelationship)] [Follow(Relationships.Children)] public TopicViewModelCollection CaseStudies { get; set; } [Follow(Relationships.Relationships)] public TopicViewModelCollection Children { get; set; } - [Relationship("Employees", RelationshipType.NestedTopics)] + [Relationship("Employees", Type=RelationshipType.NestedTopics)] [FilterByAttribute("IsActive", "1")] [FilterByAttribute("Role", "Account Manager")] [Flatten] diff --git a/Ignia.Topics/Mapping/RelationshipAttribute.cs b/Ignia.Topics/Mapping/RelationshipAttribute.cs index 16f8b79e..715fdeaf 100644 --- a/Ignia.Topics/Mapping/RelationshipAttribute.cs +++ b/Ignia.Topics/Mapping/RelationshipAttribute.cs @@ -34,15 +34,12 @@ public sealed class RelationshipAttribute : System.Attribute { | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Annotates a property with the by providing an . Optionally - /// specifies the as well. + /// Annotates a property with the by providing an . /// /// The key value of the relationships associated with the current property. - /// Optional. The type of collection the relationship is associated with. - public RelationshipAttribute(string key, RelationshipType type = RelationshipType.Any) { + public RelationshipAttribute(string key) { TopicFactory.ValidateKey(key, false); Key = key; - Type = type; } /// @@ -67,7 +64,7 @@ public RelationshipAttribute(RelationshipType type = RelationshipType.Any) { /// /// Gets the value of the relationship type. /// - public RelationshipType Type { get; } + public RelationshipType Type { get; set; } } //Class diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 0f2632c5..ec9283fd 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1736.0")] -[assembly: AssemblyFileVersion("3.5.1736.0")] +[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1739.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From d43929d30fb43ba468a2118a0736ae0f2dab51c6 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 09:08:21 -0700 Subject: [PATCH 052/149] Extended documentation Use GhostDoc to help fill out missing pieces such as and elements. It also reordered some of the elements. --- Ignia.Topics/Topic.cs | 147 ++++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/Ignia.Topics/Topic.cs b/Ignia.Topics/Topic.cs index 7d14c216..f5546be6 100644 --- a/Ignia.Topics/Topic.cs +++ b/Ignia.Topics/Topic.cs @@ -93,8 +93,14 @@ public Topic(string key, string contentType, Topic parent, int id = -1) { /// /// Gets or sets the topic's integer identifier according to the data provider. /// + /// + /// The unique identifier for the . + /// + /// + /// The value of this topic has already been set to " + _id + "; it cannot be changed. + /// /// - /// value > 0 + /// value > 0 /// public int Id { get => _id; @@ -113,6 +119,9 @@ public int Id { /// /// Reference to the parent topic of this node, allowing code to traverse topics as a linked list. /// + /// + /// The current 's parent . + /// /// /// While topics may be represented as a network graph via relationships, they are physically stored and primarily /// represented via a hierarchy. As such, each topic may have at most a single parent. Note that the root node will @@ -137,8 +146,11 @@ public Topic Parent { | PROPERTY: CHILDREN \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides a keyed collection of child instances associated with the current . + /// Provides a keyed collection of child instances associated with the current . /// + /// + /// The children of the current . + /// public TopicCollection Children { get; } /*========================================================================================================================== @@ -149,10 +161,13 @@ public Topic Parent { /// /// /// Each topic is associated with a content type. The content type determines which attributes are displayed in the Topics - /// Editor (via the property). The content type also determines, - /// by default, which view is rendered by the (assuming the value isn't + /// Editor (via the property). The content type also determines, + /// by default, which view is rendered by the (assuming the value isn't /// overwritten down the pipe). /// + /// + /// The key of the current 's . + /// public string ContentType { get => Attributes.GetValue("ContentType"); set => SetAttributeValue("ContentType", value); @@ -164,12 +179,16 @@ public string ContentType { /// /// Gets or sets the topic's Key attribute, the primary text identifier for the topic. /// + /// + /// The current 's key, which is guaranteed to be unique among its siblings. + /// /// /// value != null /// /// + /// exception="T:System.ArgumentException" + /// > /// !value.Contains(" ") /// [AttributeSetter] @@ -196,14 +215,18 @@ public string Key { /// Gets or sets the topic's original key. /// /// - /// The original key is automatically set by when its value is updated (assuming the original key isn't - /// already set). This is, in turn, used by the to represent the original value, - /// and thus allow the (or derived providers) from updating the data store - /// appropriately. + /// The original key is automatically set by when its value is updated (assuming the original key isn't + /// already set). This is, in turn, used by the to represent the original + /// value, and thus allow the (or derived providers) from updating the data + /// store appropriately. /// + /// + /// The key, as represented in the persistence layer. + /// /// + /// exception="T:System.ArgumentException" + /// > /// !value?.Contains(" ")?? true /// internal string OriginalKey { @@ -225,15 +248,19 @@ internal string OriginalKey { /// Gets or sets the View attribute, representing the default view to be used for the topic. /// /// - /// This value can be set via the query string (via the class), via the Accepts header - /// (also via the class), on the topic itself (via this property). By default, it will - /// be set to the name of the ; e.g., if the Content Type is "Page", then the view will be - /// "Page". This will cause the to look for a view at, for instance, + /// This value can be set via the query string (via the class), via the Accepts header + /// (also via the class), on the topic itself (via this property). By default, it will + /// be set to the name of the ; e.g., if the Content Type is "Page", then the view will be + /// "Page". This will cause the to look for a view at, for instance, /// /Common/Templates/Page/Page.aspx. /// + /// + /// The view, as specified by the current . + /// /// + /// exception="T:System.ArgumentException" + /// > /// !value?.Contains(" ")?? true /// [AttributeSetter] @@ -252,6 +279,9 @@ public string View { /// /// Gets or sets whether the current topic is hidden. /// + /// + /// true if this instance is hidden; otherwise, false. + /// [AttributeSetter] public bool IsHidden { get => Attributes.GetBoolean("IsHidden", false); @@ -264,6 +294,9 @@ public bool IsHidden { /// /// Gets or sets whether the current topic is disabled. /// + /// + /// true if this instance is disabled; otherwise, false. + /// [AttributeSetter] public bool IsDisabled { get => Attributes.GetBoolean("IsDisabled", false); @@ -281,6 +314,9 @@ public bool IsDisabled { /// If an item is not marked as IsVisible, then the item will not be visible independent of whether showDisabled is set. /// /// Determines whether or not items marked as IsDisabled should be displayed. + /// + /// true if the is visible; otherwise, false. + /// public bool IsVisible(bool showDisabled = false) => !IsHidden && (showDisabled || !IsDisabled); /*========================================================================================================================== @@ -290,10 +326,13 @@ public bool IsDisabled { /// Gets or sets the Title attribute, which represents the friendly name of the topic. /// /// - /// While the may not contain, for instance, spaces or symbols, there are no restrictions on what + /// While the may not contain, for instance, spaces or symbols, there are no restrictions on what /// characters can be used in the title. For this reason, it provides the default public value for referencing topics. If - /// the title is not set, then this property falls back to the topic's . + /// the title is not set, then this property falls back to the topic's . /// + /// + /// The current 's title. + /// /// /// !string.IsNullOrWhiteSpace(value) /// @@ -312,6 +351,9 @@ public string Title { /// The Description attribute is primarily used by the editor to display help content for an attribute topic, noting /// how the attribute is used, what is the expected input format or value, etc. /// + /// + /// The current 's description. + /// /// /// !string.IsNullOrWhiteSpace(value) /// @@ -332,6 +374,9 @@ public string Description { /// attribute level) nor is it guaranteed to be correct for auditing purposes; for example, the author may explicitly /// overwrite this value for various reasons (such as backdating a web page). /// + /// + /// The date that the current was last modified. + /// /// /// !string.IsNullOrWhiteSpace(value.ToString()) /// @@ -348,16 +393,21 @@ public DateTime LastModified { | METHOD: SET PARENT \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Changes the current while simultaneously ensuring that the sort order of the topics is - /// maintained, assuming a is set. + /// Changes the current while simultaneously ensuring that the sort order of the topics is + /// maintained, assuming a is set. /// /// - /// If no is provided, then the item is added to the beginning of the collection. If - /// the intent is to add it to the end of the collection, then set the to e.g. + /// If no is provided, then the item is added to the beginning of the collection. If + /// the intent is to add it to the end of the collection, then set the to e.g. /// parent.Children.LastOrDefault(). /// - /// The to move this under. - /// The to move this to the right of. + /// The to move this under. + /// The to move this to the right of. + /// parent - A descendant cannot be its own parent. + /// + /// Duplicate key when setting Parent property: the topic with the name '" + Key + "' already exists in the '" + + /// parent.Key + "' topic. + /// public void SetParent(Topic parent, Topic sibling = null) { /*------------------------------------------------------------------------------------------------------------------------ @@ -413,6 +463,7 @@ public void SetParent(Topic parent, Topic sibling = null) { /// The value for the UniqueKey property is a collated, colon-delimited representation of the topic and its parent(s). /// Example: "Root:Configuration:ContentTypes:Page". /// + /// The unique key of the current . public string GetUniqueKey() { /*------------------------------------------------------------------------------------------------------------------------ @@ -451,6 +502,7 @@ public string GetUniqueKey() { /// Note: If the topic root is not bound to the root of the site, this needs to specifically accounted for in any views /// that reference the web path (e.g., by providing a prefix). /// + /// The HTTP-based path to the current . public string GetWebPath() { Contract.Ensures(Contract.Result() != null); var uniqueKey = GetUniqueKey().Replace("Root:", "/").Replace(":", "/") + "/"; @@ -474,13 +526,14 @@ public string GetWebPath() { /// /// Derived topics allow attribute values to be inherited from another topic. When a derived topic is configured via the /// TopicId attribute key, values from that topic are used when the method unable to find a local value for the attribute. + /// Boolean)" /> method unable to find a local value for the attribute. /// /// /// Be aware that while multiple levels of derived topics can be configured, the method defaults to a maximum level of five "hops". + /// cref="AttributeValueCollection.GetValue(String, Boolean)" /> method defaults to a maximum level of five "hops". /// /// + /// The that values should be derived from, if not otherwise available. /// /// value != this /// @@ -512,11 +565,12 @@ public Topic DerivedTopic { /// significant extensibility. /// /// - /// Attributes are stored via an class which, in addition to the Attribute Key and Value, - /// also track other metadata for the attribute, such as the version (via the - /// property) and whether it has been persisted to the database or not (via the + /// Attributes are stored via an class which, in addition to the Attribute Key and Value, + /// also track other metadata for the attribute, such as the version (via the + /// property) and whether it has been persisted to the database or not (via the /// property). /// + /// The current 's attributes. public AttributeValueCollection Attributes { get; } /*========================================================================================================================== @@ -526,10 +580,11 @@ public Topic DerivedTopic { /// A façade for accessing related topics based on a scope name; can be used for tags, related topics, etc. /// /// - /// The relationships property exposes a with child topics representing named relationships (e.g., + /// The relationships property exposes a with child topics representing named relationships (e.g., /// "Related" for related topics); those child topics in turn have child topics representing references to each related /// topic, thus allowing the topic hierarchy to be represented as a network graph. /// + /// The current 's relationships. public RelatedTopicCollection Relationships { get; } /*=========================================================================================================================== @@ -539,11 +594,12 @@ public Topic DerivedTopic { /// A façade for accessing related topics based on a scope name; can be used for tags, related topics, etc. /// /// - /// The incoming relationships property provides a reverse index of the property, in order to + /// The incoming relationships property provides a reverse index of the property, in order to /// indicate which topics point to the current topic. This can be useful for traversing the topic tree as a network graph. /// This is of particular use for tags, where the current topic represents a tag, and the incoming relationships represents /// all topics associated with that tag. /// + /// The current 's incoming relationships. public RelatedTopicCollection IncomingRelationships { get; } /*========================================================================================================================== @@ -553,9 +609,10 @@ public Topic DerivedTopic { /// Provides a collection of dates representing past versions of the topic, which can be rolled back to. /// /// - /// It is expected that this collection will be populated by the (or one of + /// It is expected that this collection will be populated by the (or one of /// its derived providers). /// + /// The current 's version history. public List VersionHistory { get; } #endregion @@ -566,37 +623,41 @@ public Topic DerivedTopic { | METHOD: SET ATTRIBUTE VALUE \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Protected helper method that either adds a new object or updates the value of an existing - /// one, depending on whether that value already exists. + /// Protected helper method that either adds a new object or updates the value of an + /// existing one, depending on whether that value already exists. /// /// /// When an attribute value is set and a corresponding, writable property exists on the topic, that property will be - /// called by the AttributeValueCollection.This is intended to enforce local business logic, and prevent callers from - /// introducing invalid data.To prevent a redirect loop, however, local properties need to inform the - /// AttributeValueCollection that the business logic has already been enforced.To do that, they must either call - /// SetValue() with the enforceBusinessLogic flag set to false, or, if they're in a separate assembly, call this overload. + /// called by the . This is intended to enforce local business logic, and prevent + /// callers from introducing invalid data.To prevent a redirect loop, however, local properties need to inform the + /// that the business logic has already been enforced. To do that, they must either + /// call with the + /// enforceBusinessLogic flag set to false, or, if they're in a separate assembly, call this overload. /// /// The string identifier for the AttributeValue. /// The text value for the AttributeValue. /// - /// Specified whether the value should be marked as . By default, it will be marked as - /// dirty if the value is new or has changed from a previous value. By setting this parameter, that behavior is + /// Specified whether the value should be marked as . By default, it will be marked + /// as dirty if the value is new or has changed from a previous value. By setting this parameter, that behavior is /// overwritten to accept whatever value is submitted. This can be used, for instance, to prevent an update from being - /// persisted to the data store on . + /// persisted to the data store on . /// /// + /// exception="T:System.ArgumentNullException" + /// > /// !String.IsNullOrWhiteSpace(key) /// /// + /// exception="T:System.ArgumentNullException" + /// > /// !String.IsNullOrWhiteSpace(value) /// /// + /// exception="T:System.ArgumentException" + /// > /// !value.Contains(" ") /// protected void SetAttributeValue(string key, string value, bool? isDirty = null) { From 35274c2fd0eca6f2a9005f17becb57cb0cac4e83 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:28:42 -0700 Subject: [PATCH 053/149] Reintroduced `ParentTest` This had originally been disabled due to a dependency on `TopicRepository` from `Parent`. That restriction has long since been removed. --- Ignia.Topics.Tests/TopicTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index 7dc2be0e..bb3fafeb 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -98,8 +98,7 @@ public void Topic_Set_ParentTest() { /// /// Changes the parent of a topic and ensures it is correctly reflected in the object model. /// - // ### TODO JJC20150816: This invokes dependencies on the TopicDataProvider and, in turn, the Configuration namespace. This - // is going to call for the creation of mocks and dependency injection before it will pass. In the meanwhile, it is disabled. + [TestMethod] public void Topic_Change_ParentTest() { var sourceParent = TopicFactory.Create("SourceParent", "ContentTypeDescriptor"); From 4cdc2377cf3542693506d8f585907a7a767f821d Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:33:16 -0700 Subject: [PATCH 054/149] Marked stateless private methods as `static` --- Ignia.Topics.Web.Mvc/TopicViewEngine.cs | 2 +- Ignia.Topics.Web/Editor/FilePath.cs | 16 ++++++++-------- Ignia.Topics.Web/TopicsRouteHandler.cs | 2 +- Ignia.Topics/Mapping/PropertyConfiguration.cs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/TopicViewEngine.cs b/Ignia.Topics.Web.Mvc/TopicViewEngine.cs index bf7223fb..7d9450ba 100644 --- a/Ignia.Topics.Web.Mvc/TopicViewEngine.cs +++ b/Ignia.Topics.Web.Mvc/TopicViewEngine.cs @@ -156,7 +156,7 @@ public override ViewEngineResult FindView(ControllerContext controllerContext, s /// The requested name of the view. /// The list of path format patterns. /// Determines whether the request is appropriate for caching. - private List GetSearchPaths(ControllerContext controllerContext, string viewName, string[] locationFormats, bool useCache) { + private static List GetSearchPaths(ControllerContext controllerContext, string viewName, string[] locationFormats, bool useCache) { /*------------------------------------------------------------------------------------------------------------------------ | Establish variables diff --git a/Ignia.Topics.Web/Editor/FilePath.cs b/Ignia.Topics.Web/Editor/FilePath.cs index bc41f206..8c6d700c 100644 --- a/Ignia.Topics.Web/Editor/FilePath.cs +++ b/Ignia.Topics.Web/Editor/FilePath.cs @@ -37,7 +37,7 @@ public FilePath() { } /// Boolean indicator as to whether to include the endpoint/leaf topic in the path. /// The assembled topic keys at which to end the path string. /// A constructed file path. - public static string GetPath( + public string GetPath( Topic topic, string attributeKey, bool includeLeafTopic = true, @@ -47,7 +47,7 @@ public static string GetPath( /*---------------------------------------------------------------------------------------------------------------------- | Validate return value \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Ensures(Contract.Result() != null); + Contract.Ensures(Contract.Result() != null); /*------------------------------------------------------------------------------------------------------------------------ | Only process the path if both topic and attribtueKey are provided @@ -57,10 +57,10 @@ public static string GetPath( /*------------------------------------------------------------------------------------------------------------------------ | Build configured file path string base on values and settings parameters passed to the method \-----------------------------------------------------------------------------------------------------------------------*/ - string filePath = ""; - string relativePath = null; - Topic startTopic = topic; - Topic endTopic = includeLeafTopic? topic : topic.Parent; + var filePath = ""; + var relativePath = (string)null; + var startTopic = topic; + var endTopic = includeLeafTopic? topic : topic.Parent; /*------------------------------------------------------------------------------------------------------------------------ | Crawl up the topics tree to find file path values set at a higher level @@ -87,8 +87,8 @@ public static string GetPath( | Perform path truncation based on topics included in TruncatePathAtTopic \-----------------------------------------------------------------------------------------------------------------------*/ if (truncatePathAtTopic != null) { - foreach (string truncationTopic in truncatePathAtTopic) { - int truncateTopicLocation = relativePath.IndexOf(truncationTopic, StringComparison.InvariantCultureIgnoreCase); + foreach (var truncationTopic in truncatePathAtTopic) { + var truncateTopicLocation = relativePath.IndexOf(truncationTopic, StringComparison.InvariantCultureIgnoreCase); if (truncateTopicLocation >= 0) { relativePath = relativePath.Substring(0, truncateTopicLocation + truncationTopic.Length + 1); } diff --git a/Ignia.Topics.Web/TopicsRouteHandler.cs b/Ignia.Topics.Web/TopicsRouteHandler.cs index 840125f0..ebf8651e 100644 --- a/Ignia.Topics.Web/TopicsRouteHandler.cs +++ b/Ignia.Topics.Web/TopicsRouteHandler.cs @@ -41,7 +41,7 @@ public TopicsRouteHandler() { } /// Gets the expected location for View files; attempts to retrieve the value from the configuration /// section, but defaults to ~/Common/Templates/. /// - private string ViewsPath { + private static string ViewsPath { get { var viewsPath = "~/Common/Templates/"; var topicsSection = (TopicsSection)ConfigurationManager.GetSection("topics"); diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index ae69437f..bb213125 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -337,7 +337,7 @@ public void Validate(object target) { /// An type to evaluate. /// The instance to pull the attribute from. /// The to execute on the attribute. - private void GetAttributeValue(PropertyInfo property, Action action) where T : Attribute { + private static void GetAttributeValue(PropertyInfo property, Action action) where T : Attribute { var attribute = (T)property.GetCustomAttribute(typeof(T), true); if (attribute != null) { action(attribute); From a35437f5a34350361ecc3edaff31202893f8cf44 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:42:15 -0700 Subject: [PATCH 055/149] Implemented expression bodied members --- .../TestDoubles/FakeTopicRepository.cs | 17 ++----- .../Configuration/EditorElement.cs | 18 ++------ .../Configuration/PageTypeElement.cs | 6 +-- .../PageTypeElementCollection.cs | 14 ++---- .../Configuration/SourceElement.cs | 36 ++++----------- .../Configuration/SourceElementCollection.cs | 8 +--- .../Configuration/TopicsSection.cs | 45 ++++--------------- .../Configuration/VersioningElement.cs | 6 +-- .../Configuration/ViewsElement.cs | 6 +-- .../WebFormsTopicRoutingService.cs | 6 +-- .../Collections/NamedTopicCollection.cs | 11 +---- .../Collections/ReadOnlyTopicCollection{T}.cs | 6 +-- .../Mapping/CachedTopicMappingService.cs | 5 +-- Ignia.Topics/Mapping/PropertyConfiguration.cs | 5 +-- 14 files changed, 40 insertions(+), 149 deletions(-) diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index 9e9e2e9a..02a09df0 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -46,9 +46,7 @@ public FakeTopicRepository() : base() { /// /// Retrieves a collection of Content Type Descriptor objects from the configuration section of the data provider. /// - public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { - throw new NotImplementedException(); - } + public override ContentTypeDescriptorCollection GetContentTypeDescriptors() => throw new NotImplementedException(); /*========================================================================================================================== | METHOD: LOAD @@ -59,9 +57,7 @@ public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. /// A topic object. - public override Topic Load(int topicId, bool isRecursive = true) { - throw new NotImplementedException(); - } + public override Topic Load(int topicId, bool isRecursive = true) => throw new NotImplementedException(); /// /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. @@ -215,14 +211,7 @@ public override void Move(Topic topic, Topic target, Topic sibling) { /// topic != null /// topic /// Failed to delete Topic topic.Key (topic.Id): ex.Message - public override void Delete(Topic topic, bool isRecursive = false) { - - /*------------------------------------------------------------------------------------------------------------------------ - | Delete from memory - \-----------------------------------------------------------------------------------------------------------------------*/ - base.Delete(topic, isRecursive); - - } + public override void Delete(Topic topic, bool isRecursive = false) => base.Delete(topic, isRecursive); /*========================================================================================================================== | METHOD: CREATE FAKE DATA diff --git a/Ignia.Topics.Web/Configuration/EditorElement.cs b/Ignia.Topics.Web/Configuration/EditorElement.cs index 507e3ab3..93866147 100644 --- a/Ignia.Topics.Web/Configuration/EditorElement.cs +++ b/Ignia.Topics.Web/Configuration/EditorElement.cs @@ -34,11 +34,7 @@ public class EditorElement : ConfigurationElement { /// Gets whether the (CMS) editor is enabled as defined by the configuration attribute. /// [ConfigurationProperty("enabled", DefaultValue = "True", IsRequired = false)] - public bool Enabled { - get { - return Convert.ToBoolean(this["enabled"], CultureInfo.InvariantCulture); - } - } + public bool Enabled => Convert.ToBoolean(this["enabled"], CultureInfo.InvariantCulture); /*========================================================================================================================== | PROPRTY: LOCATION @@ -47,11 +43,7 @@ public bool Enabled { /// Gets the website location of the (CMS) editor as defined by the configuration attribute. /// [ConfigurationProperty("location", IsRequired=false)] - public string Location { - get { - return this["source"] as string; - } - } + public string Location => this["source"] as string; /*========================================================================================================================== | ELEMENT: ADMIN @@ -60,11 +52,7 @@ public string Location { /// Gets the admin element, which describes administrative rights on the system. /// [ConfigurationProperty("admin")] - public SourceElement Admin { - get { - return this["admin"] as SourceElement; - } - } + public SourceElement Admin => this["admin"] as SourceElement; } // Class diff --git a/Ignia.Topics.Web/Configuration/PageTypeElement.cs b/Ignia.Topics.Web/Configuration/PageTypeElement.cs index 0a22b858..bfcad1a1 100644 --- a/Ignia.Topics.Web/Configuration/PageTypeElement.cs +++ b/Ignia.Topics.Web/Configuration/PageTypeElement.cs @@ -46,11 +46,7 @@ public string Name { /// [TypeConverter(typeof(TypeNameConverter))] [ConfigurationProperty("type", IsRequired = false)] - public Type Type { - get { - return this["type"] as Type; - } - } + public Type Type => this["type"] as Type; } // Class diff --git a/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs b/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs index 99236a7b..b21c2bca 100644 --- a/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs +++ b/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs @@ -32,9 +32,7 @@ public class PageTypeElementCollection : ConfigurationElementCollection { /// value != null /// public PageTypeElement this[int index] { - get { - return base.BaseGet(index) as PageTypeElement; - } + get => base.BaseGet(index) as PageTypeElement; set { Contract.Requires(value != null, "The value from the getter must not be null."); if (base.BaseGet(index) != null) { @@ -51,11 +49,7 @@ public PageTypeElement this[int index] { /// Gets the default page type (e.g., ). /// [ConfigurationProperty("default", DefaultValue="TopicPage", IsRequired = false)] - public string Default { - get { - return (string)base["default"]; - } - } + public string Default => (string)base["default"]; /*========================================================================================================================== | METHOD: CREATE NEW ELEMENT @@ -64,9 +58,7 @@ public string Default { /// Creates a new . /// /// A new instance of a . - protected override ConfigurationElement CreateNewElement() { - return new PageTypeElement(); - } + protected override ConfigurationElement CreateNewElement() => new PageTypeElement(); /*========================================================================================================================== | METHOD: GET ELEMENT KEY diff --git a/Ignia.Topics.Web/Configuration/SourceElement.cs b/Ignia.Topics.Web/Configuration/SourceElement.cs index 5d944270..06b588c3 100644 --- a/Ignia.Topics.Web/Configuration/SourceElement.cs +++ b/Ignia.Topics.Web/Configuration/SourceElement.cs @@ -34,12 +34,8 @@ public class SourceElement : ConfigurationElement { /// /// Gets the source for the configuration setting. /// - [ConfigurationProperty("source", DefaultValue="QueryString", IsRequired=true, IsKey=true)] - public string Source { - get { - return this["source"] as string; - } - } + [ConfigurationProperty("source", DefaultValue = "QueryString", IsRequired = true, IsKey = true)] + public string Source => this["source"] as string; /*========================================================================================================================== | ATTRIBUTE: ENABLED @@ -48,11 +44,7 @@ public string Source { /// Gets a value indicating whether this is enabled. /// [ConfigurationProperty("enabled", DefaultValue="True", IsRequired=false)] - public bool Enabled { - get { - return Convert.ToBoolean(this["enabled"], CultureInfo.InvariantCulture); - } - } + public bool Enabled => Convert.ToBoolean(this["enabled"], CultureInfo.InvariantCulture); /*========================================================================================================================== | ATTRIBUTE: LOCATION @@ -61,11 +53,7 @@ public bool Enabled { /// Gets the location attribute value. /// [ConfigurationProperty("location", IsRequired=false)] - public string Location { - get { - return this["location"] as string; - } - } + public string Location => this["location"] as string; /*========================================================================================================================== | ATTRIBUTE: TRUSTED @@ -74,11 +62,7 @@ public string Location { /// Gets a value indicating whether this is trusted. /// [ConfigurationProperty("trusted", DefaultValue="False", IsRequired=false)] - public bool Trusted { - get { - return Convert.ToBoolean(this["trusted"], CultureInfo.InvariantCulture); - } - } + public bool Trusted => Convert.ToBoolean(this["trusted"], CultureInfo.InvariantCulture); /*========================================================================================================================== | METHOD: GET ELEMENT @@ -213,9 +197,8 @@ public static string GetValue(SourceElement element) { /// /// A boolean value representing whether or not the source is available, enabled or set to true. /// - public static bool IsEnabled(ConfigurationElement parent, string key, bool evaluateValue) { - return IsEnabled(GetElement(parent, key), evaluateValue); - } + public static bool IsEnabled(ConfigurationElement parent, string key, bool evaluateValue) => + IsEnabled(GetElement(parent, key), evaluateValue); /// /// Looks up a source element at a given location and based on a specified parent configuration element collection and @@ -240,9 +223,8 @@ public static bool IsEnabled(ConfigurationElement parent, string key, bool evalu /// /// Boolean value representing whether or not the source is available, enabled or set to true. /// - public static bool IsEnabled(ConfigurationElementCollection parent, string key, bool evaluateValue) { - return IsEnabled(GetElement(parent, key), evaluateValue); - } + public static bool IsEnabled(ConfigurationElementCollection parent, string key, bool evaluateValue) => + IsEnabled(GetElement(parent, key), evaluateValue); /// /// Looks up a source element at a given location, identifies the source value, and verifies whether the element is diff --git a/Ignia.Topics.Web/Configuration/SourceElementCollection.cs b/Ignia.Topics.Web/Configuration/SourceElementCollection.cs index 361a0dce..5bcfb895 100644 --- a/Ignia.Topics.Web/Configuration/SourceElementCollection.cs +++ b/Ignia.Topics.Web/Configuration/SourceElementCollection.cs @@ -33,9 +33,7 @@ public class SourceElementCollection : ConfigurationElementCollection { /// value != null /// public SourceElement this[int index] { - get { - return base.BaseGet(index) as SourceElement; - } + get => base.BaseGet(index) as SourceElement; set { Contract.Requires(value != null, "The value from the getter must not be null."); if (base.BaseGet(index) != null) { @@ -52,9 +50,7 @@ public SourceElement this[int index] { /// Creates a new . /// /// A new instance of a . - protected override ConfigurationElement CreateNewElement() { - return new SourceElement(); - } + protected override ConfigurationElement CreateNewElement() => new SourceElement(); /*========================================================================================================================== | METHOD: GET ELEMENT KEY diff --git a/Ignia.Topics.Web/Configuration/TopicsSection.cs b/Ignia.Topics.Web/Configuration/TopicsSection.cs index 93b28213..89d4be86 100644 --- a/Ignia.Topics.Web/Configuration/TopicsSection.cs +++ b/Ignia.Topics.Web/Configuration/TopicsSection.cs @@ -23,9 +23,7 @@ public class TopicsSection : ConfigurationSection { /// Gets the topics element from the web.config as the configuration section. /// /// The topics section of the implementing client's configuration. - public static TopicsSection GetConfig() { - return ConfigurationManager.GetSection("topics") as TopicsSection; - } + public static TopicsSection GetConfig() => ConfigurationManager.GetSection("topics") as TopicsSection; /*========================================================================================================================== | ATTRIBUTE: ROOT TOPIC NAMESPACE @@ -35,12 +33,8 @@ public static TopicsSection GetConfig() { /// [ConfigurationProperty("rootTopicNamespace", DefaultValue = "Root")] public string RootTopicNamespace { - get { - return (string)this["rootTopicNamespace"]; - } - set { - this["rootTopicNamespace"] = value; - } + get => (string)this["rootTopicNamespace"]; + set => this["rootTopicNamespace"] = value; } /*========================================================================================================================== @@ -51,12 +45,8 @@ public string RootTopicNamespace { /// [ConfigurationProperty("topicDelimiter", DefaultValue = "/")] public string TopicDelimiter { - get { - return (string)this["topicDelimiter"]; - } - set { - this["topicDelimiter"] = value; - } + get => (string)this["topicDelimiter"]; + set => this["topicDelimiter"] = value; } /*========================================================================================================================== @@ -66,11 +56,7 @@ public string TopicDelimiter { /// Gets the value of the element from the configuration. /// [ConfigurationProperty("versioning")] - public VersioningElement Versioning { - get { - return this["versioning"] as VersioningElement; - } - } + public VersioningElement Versioning => this["versioning"] as VersioningElement; /*========================================================================================================================== | ELEMENT: EDITOR @@ -79,11 +65,7 @@ public VersioningElement Versioning { /// Gets the value of the element from the configuration. /// [ConfigurationProperty("editor")] - public EditorElement Editor { - get { - return this["editor"] as EditorElement; - } - } + public EditorElement Editor => this["editor"] as EditorElement; /*========================================================================================================================== | ELEMENT: VIEWS @@ -92,11 +74,7 @@ public EditorElement Editor { /// Gets the value of the element from the configuration. /// [ConfigurationProperty("views")] - public ViewsElement Views { - get { - return this["views"] as ViewsElement; - } - } + public ViewsElement Views => this["views"] as ViewsElement; /*========================================================================================================================== | COLLECTION: PAGE TYPES @@ -104,12 +82,7 @@ public ViewsElement Views { /// /// Gets the value of the element from the configuration. /// - [ConfigurationProperty("pageTypes")] - public PageTypeElementCollection PageTypes { - get { - return this["pageTypes"] as PageTypeElementCollection; - } - } + public PageTypeElementCollection GetPageTypes() => this["pageTypes"] as PageTypeElementCollection; } // Class diff --git a/Ignia.Topics.Web/Configuration/VersioningElement.cs b/Ignia.Topics.Web/Configuration/VersioningElement.cs index c10653ef..7c2a988a 100644 --- a/Ignia.Topics.Web/Configuration/VersioningElement.cs +++ b/Ignia.Topics.Web/Configuration/VersioningElement.cs @@ -32,11 +32,7 @@ public class VersioningElement : ConfigurationElement { /// Gets the draft mode attribute value from the configuration element. /// [ConfigurationProperty("draftMode")] - public SourceElement DraftMode { - get { - return this["draftMode"] as SourceElement; - } - } + public SourceElement DraftMode => this["draftMode"] as SourceElement; } // Class diff --git a/Ignia.Topics.Web/Configuration/ViewsElement.cs b/Ignia.Topics.Web/Configuration/ViewsElement.cs index 84ca3a4e..a5acde5f 100644 --- a/Ignia.Topics.Web/Configuration/ViewsElement.cs +++ b/Ignia.Topics.Web/Configuration/ViewsElement.cs @@ -25,11 +25,7 @@ public class ViewsElement : ConfigurationElement { /// Gets the path attribute value from the configuration element. /// [ConfigurationProperty("path", IsRequired=false)] - public string Path { - get { - return this["path"] as string; - } - } + public string Path => this["path"] as string; } // Class diff --git a/Ignia.Topics.Web/WebFormsTopicRoutingService.cs b/Ignia.Topics.Web/WebFormsTopicRoutingService.cs index 9f776030..65a001ad 100644 --- a/Ignia.Topics.Web/WebFormsTopicRoutingService.cs +++ b/Ignia.Topics.Web/WebFormsTopicRoutingService.cs @@ -362,11 +362,7 @@ public bool IsValidView(string contentType, string viewName, out string matchedV /// object. /// /// An instance of key/value pairs. - private NameValueCollection QueryParameters { - get { - return HttpUtility.ParseQueryString(_uri.Query); - } - } + private NameValueCollection QueryParameters => HttpUtility.ParseQueryString(_uri.Query); } // Class diff --git a/Ignia.Topics/Collections/NamedTopicCollection.cs b/Ignia.Topics/Collections/NamedTopicCollection.cs index 03e484b9..95e23ce9 100644 --- a/Ignia.Topics/Collections/NamedTopicCollection.cs +++ b/Ignia.Topics/Collections/NamedTopicCollection.cs @@ -21,11 +21,6 @@ namespace Ignia.Topics.Collections { /// public class NamedTopicCollection: TopicCollection { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - readonly string _name = ""; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -35,7 +30,7 @@ public class NamedTopicCollection: TopicCollection { /// Provides a name for the collection, used to identify different collections. /// Optionally seeds the collection with an optional list of topic references. public NamedTopicCollection(string name = "", IEnumerable topics = null) : base() { - _name = name; + Name = name; if (topics != null) { CopyTo(topics.ToArray(), 0); } @@ -51,9 +46,7 @@ public NamedTopicCollection(string name = "", IEnumerable topics = null) /// The Name property is optional, and primary intended to differentiate multiple /// instances being referenced in a single collection, such as the . /// - public string Name { - get => _name; - } + public string Name { get; } } //Class diff --git a/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs b/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs index ba3dced2..2daf9979 100644 --- a/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs +++ b/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs @@ -70,11 +70,7 @@ public static ReadOnlyTopicCollection FromList(IList innerCollection) { /// Retrieves an by key. /// /// The topic key. - public Topic this[string key] { - get { - return _innerCollection[key]; - } - } + public Topic this[string key] => _innerCollection[key]; } //Class diff --git a/Ignia.Topics/Mapping/CachedTopicMappingService.cs b/Ignia.Topics/Mapping/CachedTopicMappingService.cs index 6176b6a2..4d304166 100644 --- a/Ignia.Topics/Mapping/CachedTopicMappingService.cs +++ b/Ignia.Topics/Mapping/CachedTopicMappingService.cs @@ -176,9 +176,8 @@ private object CacheViewModel(string contentType, object viewModel, TupleThe type of the target object that the will be mapped to. /// The relationships the mapping will follow, if any. /// A representing the unique cache key. - private static Tuple GetCacheKey(int topicId, Type type, Relationships relationships) { - return new Tuple(topicId, type, relationships); - } + private static Tuple GetCacheKey(int topicId, Type type, Relationships relationships) => + new Tuple(topicId, type, relationships); } //Class } //Namespace diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index bb213125..450a436c 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -309,9 +309,8 @@ public PropertyConfiguration(PropertyInfo property) { /// /// /// - public bool SatisfiesAttributeFilters(Topic source) { - return (!AttributeFilters.Any(f => !source.Attributes.GetValue(f.Key, "").Equals(f.Value))); - } + public bool SatisfiesAttributeFilters(Topic source) => + !AttributeFilters.Any(f => !source.Attributes.GetValue(f.Key, "").Equals(f.Value)); /*========================================================================================================================== | METHOD: VALIDATE From 9af7dae9a60394c31aa394f425a4aaa51c9d5aaf Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:42:43 -0700 Subject: [PATCH 056/149] Streamlined construction of view model --- .../Controllers/ErrorControllerBase{T}.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs index 5e70fe9b..580a440f 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs @@ -120,13 +120,14 @@ public virtual T CreateErrorViewModel(string key, string title) { /*------------------------------------------------------------------------------------------------------------------------ | Instantiate model \-----------------------------------------------------------------------------------------------------------------------*/ - var viewModel = new T(); - viewModel.Key = key; - viewModel.WebPath = "/Error/" + key; - viewModel.ContentType = "Page"; - viewModel.Title = title; - viewModel.MetaKeywords = ""; - viewModel.MetaDescription = ""; + var viewModel = new T { + Key = key, + WebPath = "/Error/" + key, + ContentType = "Page", + Title = title, + MetaKeywords = "", + MetaDescription = "" + }; /*------------------------------------------------------------------------------------------------------------------------ | Return the view From 79bfdfd25647a8377638b2b70f24a3ccceaa116c Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:44:10 -0700 Subject: [PATCH 057/149] Suppressed static warnings --- Ignia.Topics/Repositories/ITopicRepositoryContract.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs index 5fcbcce7..4e2c7b21 100644 --- a/Ignia.Topics/Repositories/ITopicRepositoryContract.cs +++ b/Ignia.Topics/Repositories/ITopicRepositoryContract.cs @@ -116,6 +116,7 @@ public int Save(Topic topic, bool isRecursive = false, bool isDraft = false) { } + /*========================================================================================================================== | METHOD: MOVE \-------------------------------------------------------------------------------------------------------------------------*/ @@ -129,11 +130,13 @@ public int Save(Topic topic, bool isRecursive = false, bool isDraft = false) { /// /// topic != null /// + #pragma warning disable CA1822 // Mark members as static public void Move(Topic topic, Topic target) { Contract.Requires(target != topic); Contract.Requires(topic != null, "topic"); Contract.Requires(target != null, "target"); } + #pragma warning restore CA1822 // Mark members as static /// /// Interface method that supports moving a topic from one position to another. @@ -167,6 +170,7 @@ public void Move(Topic topic, Topic target, Topic sibling) { /// /// topic != null /// topic + #pragma warning disable IDE0022 // Use expression body for methods public void Delete(Topic topic, bool isRecursive) { /*------------------------------------------------------------------------------------------------------------------------ @@ -175,6 +179,7 @@ public void Delete(Topic topic, bool isRecursive) { Contract.Requires(topic != null, "topic"); } + #pragma warning restore IDE0022 // Use expression body for methods /*========================================================================================================================== | METHOD: ROLLBACK From e8b16b658b2a745e13219d72afdd3bdc467377d0 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:45:26 -0700 Subject: [PATCH 058/149] Simplified syntax --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs | 2 +- Ignia.Topics.Web/Configuration/SourceElementCollection.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/PropertyConfiguration.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index dece4557..14822cdc 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 90fc0587..0b04877d 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index eefbf3d6..316698f5 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 259aea25..8c47dcc3 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 52c4d49d..46d176f0 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs b/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs index b21c2bca..5771ff9a 100644 --- a/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs +++ b/Ignia.Topics.Web/Configuration/PageTypeElementCollection.cs @@ -38,7 +38,7 @@ public PageTypeElement this[int index] { if (base.BaseGet(index) != null) { base.BaseRemoveAt(index); } - this.BaseAdd(index, value); + BaseAdd(index, value); } } diff --git a/Ignia.Topics.Web/Configuration/SourceElementCollection.cs b/Ignia.Topics.Web/Configuration/SourceElementCollection.cs index 5bcfb895..92fddb8f 100644 --- a/Ignia.Topics.Web/Configuration/SourceElementCollection.cs +++ b/Ignia.Topics.Web/Configuration/SourceElementCollection.cs @@ -39,7 +39,7 @@ public SourceElement this[int index] { if (base.BaseGet(index) != null) { base.BaseRemoveAt(index); } - this.BaseAdd(index, value); + BaseAdd(index, value); } } diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 5e6602e5..11b66b27 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 450a436c..2e1f7b8e 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -148,7 +148,7 @@ public PropertyConfiguration(PropertyInfo property) { /// /// The configuration is only applicable if the value is pulled from the collection. This is the equivalent to calling the method with an InheritFromParent parameter set to + /// cref="AttributeValueCollection.GetValue(String, Boolean)"/> method with an InheritFromParent parameter set to /// True. /// /// diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index ec9283fd..4f5d967e 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1739.0")] +[assembly: AssemblyFileVersion("3.5.1740.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From dff6b7c76f2ecbd598a4c386d5d90c285bde7107 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 12:45:37 -0700 Subject: [PATCH 059/149] Removed trailing spaces --- Ignia.Topics.Web/Configuration/EditorElement.cs | 4 ++-- Ignia.Topics.Web/Configuration/PageTypeElement.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics.Web/Configuration/EditorElement.cs b/Ignia.Topics.Web/Configuration/EditorElement.cs index 93866147..64173eee 100644 --- a/Ignia.Topics.Web/Configuration/EditorElement.cs +++ b/Ignia.Topics.Web/Configuration/EditorElement.cs @@ -13,7 +13,7 @@ namespace Ignia.Topics.Web.Configuration { | CLASS: EDITOR ELEMENT \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides a custom which represents the (default: OnTopic) editor configuration. + /// Provides a custom which represents the (default: OnTopic) editor configuration. /// /// /// @@ -31,7 +31,7 @@ public class EditorElement : ConfigurationElement { | PROPRTY: ENABLED \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Gets whether the (CMS) editor is enabled as defined by the configuration attribute. + /// Gets whether the (CMS) editor is enabled as defined by the configuration attribute. /// [ConfigurationProperty("enabled", DefaultValue = "True", IsRequired = false)] public bool Enabled => Convert.ToBoolean(this["enabled"], CultureInfo.InvariantCulture); diff --git a/Ignia.Topics.Web/Configuration/PageTypeElement.cs b/Ignia.Topics.Web/Configuration/PageTypeElement.cs index bfcad1a1..74bfbead 100644 --- a/Ignia.Topics.Web/Configuration/PageTypeElement.cs +++ b/Ignia.Topics.Web/Configuration/PageTypeElement.cs @@ -14,7 +14,7 @@ namespace Ignia.Topics.Web.Configuration { | CLASS: PAGE TYPE ELEMENT \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides a custom which represents a page type (default: + /// Provides a custom which represents a page type (default: /// ) as developed for the application. /// /// From 547713b1ea671a03a142b036e87666bc13ca0d49 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 16:15:39 -0700 Subject: [PATCH 060/149] Updated XmlDoc to use Framework Type --- Ignia.Topics.Tests/TypeCollectionTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index 0658e286..6f37f5c7 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -125,7 +125,7 @@ public void TypeCollection_SetPropertyTest() { \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Establishes a and confirms that a value can be properly set using the - /// method. + /// method. /// [TestMethod] public void TypeCollection_SetMethodTest() { From ff7eed5ebd84996392eabe33d5134eb4dfb278e3 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 16:17:27 -0700 Subject: [PATCH 061/149] Flipped logic for `SatisfiesAttributeFilters()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of saying `!…Any(…!Equals(…))` it now says ``…All(…Equals(…))`. This is logically equivalent, but avoids the double negative and is thus easier to assess. --- Ignia.Topics/Mapping/PropertyConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 2e1f7b8e..2b9ebf94 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -310,7 +310,7 @@ public PropertyConfiguration(PropertyInfo property) { /// /// public bool SatisfiesAttributeFilters(Topic source) => - !AttributeFilters.Any(f => !source.Attributes.GetValue(f.Key, "").Equals(f.Value)); + AttributeFilters.All(f => source.Attributes.GetValue(f.Key, "").Equals(f.Value)); /*========================================================================================================================== | METHOD: VALIDATE From 0d0812c0854dfe70c5819a85a7f1b15ec2e9d05b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 4 May 2018 17:49:24 -0700 Subject: [PATCH 062/149] Removed unnecessary `using` statements --- Ignia.Topics/Mapping/TopicMappingService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index cdd98fc7..8ff82e6e 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -6,8 +6,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; From a1843cc1c38aa2c7b7fc0d92d40c4a26b4fd8521 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 12:05:16 -0700 Subject: [PATCH 063/149] Added `Property` property Since this is now the `PropertyConfiguration` and not `PropertyAttributes`, it makes sense for the `PropertyInfo` to be included as part of that configuration. This also makes it easier to pass this data to e.g. other methods. --- Ignia.Topics/Mapping/PropertyConfiguration.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 2b9ebf94..94b65bfb 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -38,11 +38,6 @@ namespace Ignia.Topics.Mapping { /// public class PropertyConfiguration { - /*========================================================================================================================== - | PRIVATE VARIABLES - \-------------------------------------------------------------------------------------------------------------------------*/ - readonly PropertyInfo _property = null; - /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -56,7 +51,7 @@ public PropertyConfiguration(PropertyInfo property) { /*------------------------------------------------------------------------------------------------------------------------ | Set backing property \-----------------------------------------------------------------------------------------------------------------------*/ - _property = property; + Property = property; /*------------------------------------------------------------------------------------------------------------------------ | Set default values @@ -104,6 +99,14 @@ public PropertyConfiguration(PropertyInfo property) { } + /*========================================================================================================================== + | PROPERTY: PROPERTY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The that the current is associated with. + /// + public PropertyInfo Property { get; } + /*========================================================================================================================== | PROPERTY: ATTRIBUTE KEY \-------------------------------------------------------------------------------------------------------------------------*/ @@ -321,8 +324,8 @@ public bool SatisfiesAttributeFilters(Topic source) => /// /// The target DTO to validate the current property on. public void Validate(object target) { - foreach (ValidationAttribute validator in _property.GetCustomAttributes(typeof(ValidationAttribute))) { - validator.Validate(_property.GetValue(target), _property.Name); + foreach (ValidationAttribute validator in Property.GetCustomAttributes(typeof(ValidationAttribute))) { + validator.Validate(Property.GetValue(target), Property.Name); } } From dc2e88ee58434420fab2f6cfd623b869c6bf4b02 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 12:06:27 -0700 Subject: [PATCH 064/149] Established `SetScalarValue()` helper method This has two benefits. First, it breaks down the code into smaller chunks that are easier to read and evaluate. Second, it allows potential implementers to extend the functionality while being able to override just the specific method they're interested in. --- Ignia.Topics/Mapping/TopicMappingService.cs | 131 +++++++++++++------- 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 8ff82e6e..708a042b 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -9,6 +9,7 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; +using Ignia.Topics.Collections; using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; using Ignia.Topics.ViewModels; @@ -346,43 +347,21 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ var sourceType = topic.GetType(); var targetType = target.GetType(); - var attributes = new PropertyConfiguration(property); + var configuration = new PropertyConfiguration(property); var topicReferenceId = topic.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value \-----------------------------------------------------------------------------------------------------------------------*/ - if (attributes.DefaultValue != null) { - property.SetValue(target, attributes.DefaultValue); + if (configuration.DefaultValue != null) { + property.SetValue(target, configuration.DefaultValue); } /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ if (_typeCache.HasSettableProperty(targetType, property.Name)) { - - //Attempt to get value from topic.Get{Property}() - var attributeValue = _typeCache.GetMethodValue(topic, "Get" + property.Name)?.ToString(); - - //Otherwise, attempts to get value from topic.{Property} - if (String.IsNullOrEmpty(attributeValue)) { - attributeValue = _typeCache.GetPropertyValue(topic, property.Name)?.ToString(); - } - - //Otherwise, attempts to get value from topic.Attributes.GetValue({Property}) - if (String.IsNullOrEmpty(attributeValue)) { - attributeValue = topic.Attributes.GetValue( - attributes.AttributeKey, - attributes.DefaultValue?.ToString(), - attributes.InheritValue - ); - } - - //Sets the value, assuming it is defined - if (attributeValue != null) { - _typeCache.SetPropertyValue(target, property.Name, attributeValue); - } - + SetScalarValue(topic, target, configuration); } /*------------------------------------------------------------------------------------------------------------------------ @@ -402,41 +381,41 @@ Dictionary cache //Handle children if ( - (attributes.RelationshipKey.Equals("Children") || attributes.RelationshipType.Equals(RelationshipType.Children)) && - IsCurrentRelationship(RelationshipType.Children, attributes.RelationshipType, relationships, listSource) + (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && + IsCurrentRelationship(RelationshipType.Children, configuration.RelationshipType, relationships, listSource) ) { listSource = topic.Children.ToList(); } //Handle (outgoing) relationships - if (IsCurrentRelationship(RelationshipType.Relationship, attributes.RelationshipType, relationships, listSource)) { - if (topic.Relationships.Contains(attributes.RelationshipKey)) { - listSource = topic.Relationships.GetTopics(attributes.RelationshipKey); + if (IsCurrentRelationship(RelationshipType.Relationship, configuration.RelationshipType, relationships, listSource)) { + if (topic.Relationships.Contains(configuration.RelationshipKey)) { + listSource = topic.Relationships.GetTopics(configuration.RelationshipKey); } } //Handle nested topics, or children corresponding to the property name - if (IsCurrentRelationship(RelationshipType.NestedTopics, attributes.RelationshipType, relationships, listSource)) { - if (topic.Children.Contains(attributes.RelationshipKey)) { - listSource = topic.Children[attributes.RelationshipKey].Children.ToList(); + if (IsCurrentRelationship(RelationshipType.NestedTopics, configuration.RelationshipType, relationships, listSource)) { + if (topic.Children.Contains(configuration.RelationshipKey)) { + listSource = topic.Children[configuration.RelationshipKey].Children.ToList(); } } //Handle (incoming) relationships - if (IsCurrentRelationship(RelationshipType.IncomingRelationship, attributes.RelationshipType, relationships, listSource)) { - if (topic.IncomingRelationships.Contains(attributes.RelationshipKey)) { - listSource = topic.IncomingRelationships.GetTopics(attributes.RelationshipKey); + if (IsCurrentRelationship(RelationshipType.IncomingRelationship, configuration.RelationshipType, relationships, listSource)) { + if (topic.IncomingRelationships.Contains(configuration.RelationshipKey)) { + listSource = topic.IncomingRelationships.GetTopics(configuration.RelationshipKey); } } //Handle Metadata relationship - if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(attributes.MetadataKey)) { - listSource = _topicRepository.Load("Root:Configuration:Metadata:" + attributes.MetadataKey + ":LookupList")? + if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(configuration.MetadataKey)) { + listSource = _topicRepository.Load("Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList")? .Children.ToList(); } //Handle flattening of children - if (attributes.FlattenChildren) { + if (configuration.FlattenChildren) { var flattenedList = new List(); foreach (var childTopic in listSource) { AddChildren(childTopic, flattenedList); @@ -454,7 +433,7 @@ Dictionary cache //Validate and populate target collection if (listSource != null) { foreach (var childTopic in listSource) { - if (!attributes.SatisfiesAttributeFilters(childTopic)) { + if (!configuration.SatisfiesAttributeFilters(childTopic)) { continue; } if (childTopic.IsDisabled) { @@ -474,7 +453,7 @@ Dictionary cache } //Otherwise, assume the list type is a DTO else { - var childDto = Map(childTopic, attributes.CrawlRelationships, cache); + var childDto = Map(childTopic, configuration.CrawlRelationships, cache); //Ensure the mapped type derives from the list type if (listType.IsAssignableFrom(childDto.GetType())) { try { @@ -493,9 +472,9 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Property: Parent \-----------------------------------------------------------------------------------------------------------------------*/ - else if (attributes.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { + else if (configuration.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { if (topic.Parent != null) { - var parent = Map(topic.Parent, attributes.CrawlRelationships, cache); + var parent = Map(topic.Parent, configuration.CrawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(parent.GetType())) { property.SetValue(target, parent); } @@ -507,7 +486,7 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); - var viewModelReference = Map(topicReference, attributes.CrawlRelationships, cache); + var viewModelReference = Map(topicReference, configuration.CrawlRelationships, cache); if (property.PropertyType.IsAssignableFrom(viewModelReference.GetType())) { property.SetValue(target, viewModelReference); } @@ -516,7 +495,67 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Validate fields \-----------------------------------------------------------------------------------------------------------------------*/ - attributes.Validate(target); + configuration.Validate(target); + + } + + /*========================================================================================================================== + | PROTECTED: SET SCALAR VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Sets a scalar property on a target DTO. + /// + /// + /// Assuming the 's is of the type , , , or , the method will attempt to set the property on the based on, in order, the 's Get{Property}() method, {Property} + /// property, and, finally, its collection (using ). If the property is not of a settable type, or the source + /// value cannot be identified on the , then the property is not set. + /// + /// The source from which to pull the value. + /// The target DTO on which to set the property value. + /// The with details about the property's attributes. + /// + protected static void SetScalarValue(Topic topic, object target, PropertyConfiguration configuration) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Escape clause if preconditions are not met + \-----------------------------------------------------------------------------------------------------------------------*/ + if (!_typeCache.HasSettableProperty(target.GetType(), configuration.Property.Name)) { + return; + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Attempt to retrieve value from topic.Get{Property}() + \-----------------------------------------------------------------------------------------------------------------------*/ + var attributeValue = _typeCache.GetMethodValue(topic, "Get" + configuration.AttributeKey)?.ToString(); + + /*------------------------------------------------------------------------------------------------------------------------ + | Attempt to retrieve value from topic.{Property} + \-----------------------------------------------------------------------------------------------------------------------*/ + if (String.IsNullOrEmpty(attributeValue)) { + attributeValue = _typeCache.GetPropertyValue(topic, configuration.AttributeKey)?.ToString(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Otherwise, attempt to retrieve value from topic.Attributes.GetValue({Property}) + \-----------------------------------------------------------------------------------------------------------------------*/ + if (String.IsNullOrEmpty(attributeValue)) { + attributeValue = topic.Attributes.GetValue( + configuration.AttributeKey, + configuration.DefaultValue?.ToString(), + configuration.InheritValue + ); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Assuming a value was retrieved, set it + \-----------------------------------------------------------------------------------------------------------------------*/ + if (attributeValue != null) { + _typeCache.SetPropertyValue(target, configuration.Property.Name, attributeValue); + } } From 5c540f4d9ea49d9e1534ac9fae195f155d95b1a0 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 12:25:01 -0700 Subject: [PATCH 065/149] Established `SetCollectionValue()` helper method This has two benefits. First, it breaks down the code into smaller chunks that are easier to read and evaluate. Second, it allows potential implementers to extend the functionality while being able to override just the specific method they're interested in. --- Ignia.Topics/Mapping/TopicMappingService.cs | 259 ++++++++++++-------- 1 file changed, 160 insertions(+), 99 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 708a042b..dfd691a5 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -368,105 +368,7 @@ Dictionary cache | Property: Collections \-----------------------------------------------------------------------------------------------------------------------*/ else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { - - //Determine the type of item in the list - var listType = typeof(ITopicViewModel); - if (property.PropertyType.IsGenericType) { - //Uses last argument in case it's a KeyedCollection; in that case, we want the TItem type - listType = property.PropertyType.GetGenericArguments().Last(); - } - - //Get source for list - IList listSource = new Topic[] { }; - - //Handle children - if ( - (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && - IsCurrentRelationship(RelationshipType.Children, configuration.RelationshipType, relationships, listSource) - ) { - listSource = topic.Children.ToList(); - } - - //Handle (outgoing) relationships - if (IsCurrentRelationship(RelationshipType.Relationship, configuration.RelationshipType, relationships, listSource)) { - if (topic.Relationships.Contains(configuration.RelationshipKey)) { - listSource = topic.Relationships.GetTopics(configuration.RelationshipKey); - } - } - - //Handle nested topics, or children corresponding to the property name - if (IsCurrentRelationship(RelationshipType.NestedTopics, configuration.RelationshipType, relationships, listSource)) { - if (topic.Children.Contains(configuration.RelationshipKey)) { - listSource = topic.Children[configuration.RelationshipKey].Children.ToList(); - } - } - - //Handle (incoming) relationships - if (IsCurrentRelationship(RelationshipType.IncomingRelationship, configuration.RelationshipType, relationships, listSource)) { - if (topic.IncomingRelationships.Contains(configuration.RelationshipKey)) { - listSource = topic.IncomingRelationships.GetTopics(configuration.RelationshipKey); - } - } - - //Handle Metadata relationship - if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(configuration.MetadataKey)) { - listSource = _topicRepository.Load("Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList")? - .Children.ToList(); - } - - //Handle flattening of children - if (configuration.FlattenChildren) { - var flattenedList = new List(); - foreach (var childTopic in listSource) { - AddChildren(childTopic, flattenedList); - } - listSource = flattenedList; - } - - //Ensure list is created - var list = (IList)property.GetValue(target, null); - if (list == null) { - list = (IList)Activator.CreateInstance(property.PropertyType); - property.SetValue(target, list); - } - - //Validate and populate target collection - if (listSource != null) { - foreach (var childTopic in listSource) { - if (!configuration.SatisfiesAttributeFilters(childTopic)) { - continue; - } - if (childTopic.IsDisabled) { - continue; - } - //Handle scenario where the list type derives from Topic - if (typeof(Topic).IsAssignableFrom(listType)) { - //Ensure the list item derives from the list type (which may be more derived than Topic) - if (listType.IsAssignableFrom(childTopic.GetType())) { - try { - list.Add(childTopic); - } - catch (ArgumentException) { - //Ignore exceptions caused by duplicate keys - } - } - } - //Otherwise, assume the list type is a DTO - else { - var childDto = Map(childTopic, configuration.CrawlRelationships, cache); - //Ensure the mapped type derives from the list type - if (listType.IsAssignableFrom(childDto.GetType())) { - try { - list.Add(childDto); - } - catch (ArgumentException) { - //Ignore exceptions caused by duplicate keys - } - } - } - } - } - + SetCollectionValue(topic, target, relationships, cache, configuration); } /*------------------------------------------------------------------------------------------------------------------------ @@ -559,6 +461,165 @@ protected static void SetScalarValue(Topic topic, object target, PropertyConfigu } + + /*========================================================================================================================== + | PROTECTED: SET COLLECTION VALUE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a collection property, identifies a source collection, maps the values to DTOs, and attempts to add them to the + /// target collection. + /// + /// + /// Given a collection on a DTO, attempts to identify a source + /// collection on the . Collections can be mapped to , , or to a nested topic (which will be part of + /// ). By default, will attempt to map based on the + /// property name, though this behavior can be modified using the , based on annotations + /// on the DTO. + /// + /// The source from which to pull the value. + /// The target DTO on which to set the property value. + /// Determines what relationships the mapping should follow, if any. + /// A cache to keep track of already-mapped object instances. + /// + /// The with details about the property's attributes. + /// + protected void SetCollectionValue( + Topic topic, + object target, + Relationships relationships, + Dictionary cache, + PropertyConfiguration configuration + ) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Escape clause if preconditions are not met + \-----------------------------------------------------------------------------------------------------------------------*/ + if (!typeof(IList).IsAssignableFrom(configuration.Property.PropertyType)) { + return; + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Determine the type of item in the list + \-----------------------------------------------------------------------------------------------------------------------*/ + var listType = typeof(ITopicViewModel); + if (configuration.Property.PropertyType.IsGenericType) { + //Uses last argument in case it's a KeyedCollection; in that case, we want the TItem type + listType = configuration.Property.PropertyType.GetGenericArguments().Last(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Establish source collection to store topics to be mapped + \-----------------------------------------------------------------------------------------------------------------------*/ + IList listSource = new Topic[] { }; + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle children + \-----------------------------------------------------------------------------------------------------------------------*/ + if ( + (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && + IsCurrentRelationship(RelationshipType.Children, configuration.RelationshipType, relationships, listSource) + ) { + listSource = topic.Children.ToList(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle (outgoing) relationships + \-----------------------------------------------------------------------------------------------------------------------*/ + if (IsCurrentRelationship(RelationshipType.Relationship, configuration.RelationshipType, relationships, listSource)) { + if (topic.Relationships.Contains(configuration.RelationshipKey)) { + listSource = topic.Relationships.GetTopics(configuration.RelationshipKey); + } + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle nested topics, or children corresponding to the property name + \-----------------------------------------------------------------------------------------------------------------------*/ + if (IsCurrentRelationship(RelationshipType.NestedTopics, configuration.RelationshipType, relationships, listSource)) { + if (topic.Children.Contains(configuration.RelationshipKey)) { + listSource = topic.Children[configuration.RelationshipKey].Children.ToList(); + } + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle (incoming) relationships + \-----------------------------------------------------------------------------------------------------------------------*/ + if (IsCurrentRelationship(RelationshipType.IncomingRelationship, configuration.RelationshipType, relationships, listSource)) { + if (topic.IncomingRelationships.Contains(configuration.RelationshipKey)) { + listSource = topic.IncomingRelationships.GetTopics(configuration.RelationshipKey); + } + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle Metadata relationship + \-----------------------------------------------------------------------------------------------------------------------*/ + if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(configuration.MetadataKey)) { + listSource = _topicRepository.Load("Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList")? + .Children.ToList(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle flattening of children + \-----------------------------------------------------------------------------------------------------------------------*/ + if (configuration.FlattenChildren) { + var flattenedList = new List(); + foreach (var childTopic in listSource) { + AddChildren(childTopic, flattenedList); + } + listSource = flattenedList; + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Ensure target list is created + \-----------------------------------------------------------------------------------------------------------------------*/ + var list = (IList)configuration.Property.GetValue(target, null); + if (list == null) { + list = (IList)Activator.CreateInstance(configuration.Property.PropertyType); + configuration.Property.SetValue(target, list); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Validate that source collection was identified + \-----------------------------------------------------------------------------------------------------------------------*/ + if (listSource == null) { + return; + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Populate the target collection + \-----------------------------------------------------------------------------------------------------------------------*/ + foreach (var childTopic in listSource) { + + //Ensure the source topic matches any [FilterByAttribute()] settings + if (!configuration.SatisfiesAttributeFilters(childTopic)) { + continue; + } + + //Ensure the source topic isn't disabled; disabled topics should never be returned to the presentation layer + if (childTopic.IsDisabled) { + continue; + } + + //Map child topic to target DTO + var childDto = (object)childTopic; + if (!typeof(Topic).IsAssignableFrom(listType)) { + childDto = Map(childTopic, configuration.CrawlRelationships, cache); + } + + //Ensure the list item derives from the list type + if (listType.IsAssignableFrom(childDto.GetType())) { + try { + list.Add(childDto); + } + catch (ArgumentException) { + //Ignore exceptions caused by duplicate keys + } + } + + } + + } + /*========================================================================================================================== | PRIVATE: IS CURRENT RELATIONSHIP \-------------------------------------------------------------------------------------------------------------------------*/ From 39e4536cc4e7314479d5c42896f23ea2f8a8675b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 12:27:47 -0700 Subject: [PATCH 066/149] Established `SetTopicReference()` helper method This has two benefits. First, it breaks down the code into smaller chunks that are easier to read and evaluate. Second, it allows potential implementers to extend the functionality while being able to override just the specific method they're interested in. --- Ignia.Topics/Mapping/TopicMappingService.cs | 36 +++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index dfd691a5..f86168d7 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -375,12 +375,7 @@ Dictionary cache | Property: Parent \-----------------------------------------------------------------------------------------------------------------------*/ else if (configuration.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { - if (topic.Parent != null) { - var parent = Map(topic.Parent, configuration.CrawlRelationships, cache); - if (property.PropertyType.IsAssignableFrom(parent.GetType())) { - property.SetValue(target, parent); - } - } + SetTopicReference(topic.Parent, target, configuration, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -388,10 +383,7 @@ Dictionary cache \-----------------------------------------------------------------------------------------------------------------------*/ else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); - var viewModelReference = Map(topicReference, configuration.CrawlRelationships, cache); - if (property.PropertyType.IsAssignableFrom(viewModelReference.GetType())) { - property.SetValue(target, viewModelReference); - } + SetTopicReference(topicReference, target, configuration, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -620,6 +612,30 @@ PropertyConfiguration configuration } + /*========================================================================================================================== + | PROTECTED: SET TOPIC REFERENCE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a reference to an external topic, attempts to match it to a matching property. + /// + /// The source from which to pull the value. + /// The target DTO on which to set the property value. + /// A cache to keep track of already-mapped object instances. + /// + /// The with details about the property's attributes. + /// + protected void SetTopicReference( + Topic topic, + object target, + PropertyConfiguration configuration, + Dictionary cache + ) { + var topicDto = Map(topic, configuration.CrawlRelationships, cache); + if (topicDto != null && configuration.Property.PropertyType.IsAssignableFrom(topicDto.GetType())) { + configuration.Property.SetValue(target, topicDto); + } + } + /*========================================================================================================================== | PRIVATE: IS CURRENT RELATIONSHIP \-------------------------------------------------------------------------------------------------------------------------*/ From 8bb8f344e315bd572e2539d397d1bcd195c2e728 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 12:37:14 -0700 Subject: [PATCH 067/149] Swapped setting of `listType` and `list` The `list` should always be set. The `listType` is only needed if the `listSource` is populated. As such, moved that logic so it would only be assessed in that scenario. --- Ignia.Topics/Mapping/TopicMappingService.cs | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index f86168d7..8ed860ca 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -492,12 +492,12 @@ PropertyConfiguration configuration } /*------------------------------------------------------------------------------------------------------------------------ - | Determine the type of item in the list + | Ensure target list is created \-----------------------------------------------------------------------------------------------------------------------*/ - var listType = typeof(ITopicViewModel); - if (configuration.Property.PropertyType.IsGenericType) { - //Uses last argument in case it's a KeyedCollection; in that case, we want the TItem type - listType = configuration.Property.PropertyType.GetGenericArguments().Last(); + var list = (IList)configuration.Property.GetValue(target, null); + if (list == null) { + list = (IList)Activator.CreateInstance(configuration.Property.PropertyType); + configuration.Property.SetValue(target, list); } /*------------------------------------------------------------------------------------------------------------------------ @@ -562,19 +562,19 @@ PropertyConfiguration configuration } /*------------------------------------------------------------------------------------------------------------------------ - | Ensure target list is created + | Validate that source collection was identified \-----------------------------------------------------------------------------------------------------------------------*/ - var list = (IList)configuration.Property.GetValue(target, null); - if (list == null) { - list = (IList)Activator.CreateInstance(configuration.Property.PropertyType); - configuration.Property.SetValue(target, list); + if (listSource == null) { + return; } /*------------------------------------------------------------------------------------------------------------------------ - | Validate that source collection was identified + | Determine the type of item in the list \-----------------------------------------------------------------------------------------------------------------------*/ - if (listSource == null) { - return; + var listType = typeof(ITopicViewModel); + if (configuration.Property.PropertyType.IsGenericType) { + //Uses last argument in case it's a KeyedCollection; in that case, we want the TItem type + listType = configuration.Property.PropertyType.GetGenericArguments().Last(); } /*------------------------------------------------------------------------------------------------------------------------ From 22fe7dfdc1d9766b6d64881a7affc0dc16f967be Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 13:42:05 -0700 Subject: [PATCH 068/149] Further separated `SetCollectionValue()` into `GetSourceCollection()` and `PopulateTargetCollection()`. --- Ignia.Topics/Mapping/TopicMappingService.cs | 83 +++++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 8ed860ca..5d7761e3 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -453,7 +453,6 @@ protected static void SetScalarValue(Topic topic, object target, PropertyConfigu } - /*========================================================================================================================== | PROTECTED: SET COLLECTION VALUE \-------------------------------------------------------------------------------------------------------------------------*/ @@ -494,12 +493,53 @@ PropertyConfiguration configuration /*------------------------------------------------------------------------------------------------------------------------ | Ensure target list is created \-----------------------------------------------------------------------------------------------------------------------*/ - var list = (IList)configuration.Property.GetValue(target, null); - if (list == null) { - list = (IList)Activator.CreateInstance(configuration.Property.PropertyType); - configuration.Property.SetValue(target, list); + var targetList = (IList)configuration.Property.GetValue(target, null); + if (targetList == null) { + targetList = (IList)Activator.CreateInstance(configuration.Property.PropertyType); + configuration.Property.SetValue(target, targetList); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Establish source collection to store topics to be mapped + \-----------------------------------------------------------------------------------------------------------------------*/ + var sourceList = GetSourceCollection(topic, relationships, configuration); + + /*------------------------------------------------------------------------------------------------------------------------ + | Validate that source collection was identified + \-----------------------------------------------------------------------------------------------------------------------*/ + if (sourceList == null) { + return; } + /*------------------------------------------------------------------------------------------------------------------------ + | Map the topics from the source collection, and add them to the target collection + \-----------------------------------------------------------------------------------------------------------------------*/ + PopulateTargetCollection(configuration, cache, sourceList, targetList); + + + } + + /*========================================================================================================================== + | PROTECTED: GET SOURCE COLLECTION + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a source topic and a property configuration, attempts to identify a source collection that maps to the property. + /// + /// + /// Given a collection on a DTO, attempts to identify a source + /// collection on the . Collections can be mapped to , , or to a nested topic (which will be part of + /// ). By default, will attempt to map based on the + /// property name, though this behavior can be modified using the , based on annotations + /// on the target DTO. + /// + /// The source from which to pull the value. + /// Determines what relationships the mapping should follow, if any. + /// + /// The with details about the property's attributes. + /// + protected IList GetSourceCollection(Topic topic, Relationships relationships, PropertyConfiguration configuration) { + /*------------------------------------------------------------------------------------------------------------------------ | Establish source collection to store topics to be mapped \-----------------------------------------------------------------------------------------------------------------------*/ @@ -561,12 +601,28 @@ PropertyConfiguration configuration listSource = flattenedList; } - /*------------------------------------------------------------------------------------------------------------------------ - | Validate that source collection was identified - \-----------------------------------------------------------------------------------------------------------------------*/ - if (listSource == null) { - return; - } + return listSource; + + } + + /*========================================================================================================================== + | PROTECTED: POPULATE TARGET COLLECTION + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a source list, will populate a target list based on the configured behavior of the target property. + /// + /// + /// The with details about the property's attributes. + /// + /// A cache to keep track of already-mapped object instances. + /// The to pull the source objects from. + /// The target to add the mapped objects to. + protected void PopulateTargetCollection( + PropertyConfiguration configuration, + Dictionary cache, + IList sourceList, + IList targetList + ) { /*------------------------------------------------------------------------------------------------------------------------ | Determine the type of item in the list @@ -580,7 +636,7 @@ PropertyConfiguration configuration /*------------------------------------------------------------------------------------------------------------------------ | Populate the target collection \-----------------------------------------------------------------------------------------------------------------------*/ - foreach (var childTopic in listSource) { + foreach (var childTopic in sourceList) { //Ensure the source topic matches any [FilterByAttribute()] settings if (!configuration.SatisfiesAttributeFilters(childTopic)) { @@ -601,7 +657,7 @@ PropertyConfiguration configuration //Ensure the list item derives from the list type if (listType.IsAssignableFrom(childDto.GetType())) { try { - list.Add(childDto); + targetList.Add(childDto); } catch (ArgumentException) { //Ignore exceptions caused by duplicate keys @@ -609,7 +665,6 @@ PropertyConfiguration configuration } } - } /*========================================================================================================================== From f5b9bf1a4be266f3b84368432b7fe4b15c514097 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 13:50:05 -0700 Subject: [PATCH 069/149] Moved `IsCurrentRelationship()` to local function This is useful as it allows the `IsCurrentRelationship()` function to operate off locally defined variables and current state, instead of needing to relay those to the helper function. --- Ignia.Topics/Mapping/TopicMappingService.cs | 54 +++++++++------------ 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 5d7761e3..bb0b6ebd 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -550,7 +550,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh \-----------------------------------------------------------------------------------------------------------------------*/ if ( (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && - IsCurrentRelationship(RelationshipType.Children, configuration.RelationshipType, relationships, listSource) + IsCurrentRelationship(RelationshipType.Children) ) { listSource = topic.Children.ToList(); } @@ -558,7 +558,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh /*------------------------------------------------------------------------------------------------------------------------ | Handle (outgoing) relationships \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.Relationship, configuration.RelationshipType, relationships, listSource)) { + if (IsCurrentRelationship(RelationshipType.Relationship)) { if (topic.Relationships.Contains(configuration.RelationshipKey)) { listSource = topic.Relationships.GetTopics(configuration.RelationshipKey); } @@ -567,7 +567,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh /*------------------------------------------------------------------------------------------------------------------------ | Handle nested topics, or children corresponding to the property name \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.NestedTopics, configuration.RelationshipType, relationships, listSource)) { + if (IsCurrentRelationship(RelationshipType.NestedTopics)) { if (topic.Children.Contains(configuration.RelationshipKey)) { listSource = topic.Children[configuration.RelationshipKey].Children.ToList(); } @@ -576,7 +576,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh /*------------------------------------------------------------------------------------------------------------------------ | Handle (incoming) relationships \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.IncomingRelationship, configuration.RelationshipType, relationships, listSource)) { + if (IsCurrentRelationship(RelationshipType.IncomingRelationship)) { if (topic.IncomingRelationships.Contains(configuration.RelationshipKey)) { listSource = topic.IncomingRelationships.GetTopics(configuration.RelationshipKey); } @@ -590,6 +590,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh .Children.ToList(); } + /*------------------------------------------------------------------------------------------------------------------------ | Handle flattening of children \-----------------------------------------------------------------------------------------------------------------------*/ @@ -603,6 +604,22 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh return listSource; + /*------------------------------------------------------------------------------------------------------------------------ + | Provide local function for evaluating current relationship + \-----------------------------------------------------------------------------------------------------------------------*/ + bool IsCurrentRelationship(RelationshipType targetRelationshipType) => ( + listSource.Count == 0 && + ( + configuration.RelationshipType.Equals(RelationshipType.Any) || + configuration.RelationshipType.Equals(targetRelationshipType) + ) && + ( + RelationshipMap.Mappings[targetRelationshipType].Equals(Relationships.None) || + relationships.HasFlag(RelationshipMap.Mappings[targetRelationshipType]) + ) + ); + + } /*========================================================================================================================== @@ -692,34 +709,7 @@ Dictionary cache } /*========================================================================================================================== - | PRIVATE: IS CURRENT RELATIONSHIP - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Helper function evaluates whether a relationship is the active relationship for the current property. - /// - /// The currently being evaluated. - /// The for the current property. - /// The for the current property. - /// The existing assignment of instances for the relationship. - private static bool IsCurrentRelationship( - RelationshipType targetRelationshipType, - RelationshipType sourceRelationshipType, - Relationships sourceRelationships, - IList listSource - ) => ( - listSource.Count == 0 && - ( - sourceRelationshipType.Equals(RelationshipType.Any) || - sourceRelationshipType.Equals(targetRelationshipType) - ) && - ( - RelationshipMap.Mappings[targetRelationshipType].Equals(Relationships.None) || - sourceRelationships.HasFlag(RelationshipMap.Mappings[targetRelationshipType]) - ) - ); - - /*========================================================================================================================== - | PRIVATE: ADD CHILDREN + | PROTECTED: ADD CHILDREN \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Helper function recursively iterates through children and adds each to a collection. From 73569618f135655e139c26a91b3e046c305cf761 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 13:52:20 -0700 Subject: [PATCH 070/149] Made `PopulateChildTopics()` protected Renamed `AddChildren()` to `PopulateChildTopics()` and changed to `protected`. This will allow future developers to override this functionality, if needed, to customize the behavior of the class. --- Ignia.Topics/Mapping/TopicMappingService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index bb0b6ebd..65650f10 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -597,7 +597,7 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh if (configuration.FlattenChildren) { var flattenedList = new List(); foreach (var childTopic in listSource) { - AddChildren(childTopic, flattenedList); + PopulateChildTopics(childTopic, flattenedList); } listSource = flattenedList; } @@ -709,7 +709,7 @@ Dictionary cache } /*========================================================================================================================== - | PROTECTED: ADD CHILDREN + | PROTECTED: POPULATE CHILD TOPICS \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Helper function recursively iterates through children and adds each to a collection. @@ -717,12 +717,12 @@ Dictionary cache /// The entity pull the data from. /// The list of instances to add each child to. /// Optionally enable including nested topics in the list. - private IList AddChildren(Topic topic, IList topics, bool includeNestedTopics = false) { + protected IList PopulateChildTopics(Topic topic, IList topics, bool includeNestedTopics = false) { if (topic.IsDisabled) return topics; if (topic.ContentType.Equals("List") && !includeNestedTopics) return topics; topics.Add(topic); foreach (var child in topic.Children) { - AddChildren(child, topics); + PopulateChildTopics(child, topics); } return topics; } From 60a1cbb9d53f095763f64edaff5c3955e82b1b92 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 13:57:14 -0700 Subject: [PATCH 071/149] Converted `SetProperty()` to `protected` If the child methods need to be set to `protected` to allow extensibility, then so should `SetProperty()` itself. --- Ignia.Topics/Mapping/TopicMappingService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 65650f10..d4cf1347 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -323,7 +323,7 @@ private object Map(Topic topic, object target, Relationships relationships, Dict } /*========================================================================================================================== - | PRIVATE: SET PROPERTY + | PROTECTED: SET PROPERTY \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Helper function that evaluates each property on the target object and attempts to retrieve a value from the source @@ -334,7 +334,7 @@ private object Map(Topic topic, object target, Relationships relationships, Dict /// Determines what relationships the mapping should follow, if any. /// Information related to the current property. /// A cache to keep track of already-mapped object instances. - private void SetProperty( + protected void SetProperty( Topic topic, object target, Relationships relationships, From a5a968d98864b78c886c043dd7041607ad6d358f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 23:32:24 -0700 Subject: [PATCH 072/149] Removed extraneous line breaks --- Ignia.Topics/Mapping/TopicMappingService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index d4cf1347..2a79ea04 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -516,7 +516,6 @@ PropertyConfiguration configuration \-----------------------------------------------------------------------------------------------------------------------*/ PopulateTargetCollection(configuration, cache, sourceList, targetList); - } /*========================================================================================================================== @@ -590,7 +589,6 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh .Children.ToList(); } - /*------------------------------------------------------------------------------------------------------------------------ | Handle flattening of children \-----------------------------------------------------------------------------------------------------------------------*/ @@ -619,7 +617,6 @@ bool IsCurrentRelationship(RelationshipType targetRelationshipType) => ( ) ); - } /*========================================================================================================================== From fdcac62077e37135febf21956a9331807160028c Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 23:35:04 -0700 Subject: [PATCH 073/149] Removed legacy `` --- Ignia.Topics/Mapping/TopicMappingService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 2a79ea04..0d332c75 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -525,8 +525,8 @@ PropertyConfiguration configuration /// Given a source topic and a property configuration, attempts to identify a source collection that maps to the property. /// /// - /// Given a collection on a DTO, attempts to identify a source - /// collection on the . Collections can be mapped to , on a target DTO, attempts to identify a source collection on the + /// . Collections can be mapped to , , or to a nested topic (which will be part of /// ). By default, will attempt to map based on the /// property name, though this behavior can be modified using the , based on annotations From 0e96ce3c1acba091622af43a9790df101c470bce Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 23:41:04 -0700 Subject: [PATCH 074/149] Renamed `topic` parameter to `source` This makes it's clearer when reading the code that the `topic` is actually the `source`. (Obviously, that's the point of the service, but naming the parameter as such, and contrasted to `target`, makes the code much more intuitive.) --- Ignia.Topics/Mapping/TopicMappingService.cs | 74 ++++++++++----------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 0d332c75..9ee0df1b 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -329,13 +329,13 @@ private object Map(Topic topic, object target, Relationships relationships, Dict /// Helper function that evaluates each property on the target object and attempts to retrieve a value from the source /// based on predetermined conventions. /// - /// The entity to derive the data from. + /// The entity to derive the data from. /// The target object to map the data to. /// Determines what relationships the mapping should follow, if any. /// Information related to the current property. /// A cache to keep track of already-mapped object instances. protected void SetProperty( - Topic topic, + Topic source, object target, Relationships relationships, PropertyInfo property, @@ -345,10 +345,10 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Establish per-property variables \-----------------------------------------------------------------------------------------------------------------------*/ - var sourceType = topic.GetType(); + var sourceType = source.GetType(); var targetType = target.GetType(); var configuration = new PropertyConfiguration(property); - var topicReferenceId = topic.Attributes.GetInteger(property.Name + "Id", 0); + var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value @@ -361,21 +361,21 @@ Dictionary cache | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ if (_typeCache.HasSettableProperty(targetType, property.Name)) { - SetScalarValue(topic, target, configuration); + SetScalarValue(source, target, configuration); } /*------------------------------------------------------------------------------------------------------------------------ | Property: Collections \-----------------------------------------------------------------------------------------------------------------------*/ else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { - SetCollectionValue(topic, target, relationships, cache, configuration); + SetCollectionValue(source, target, relationships, cache, configuration); } /*------------------------------------------------------------------------------------------------------------------------ | Property: Parent \-----------------------------------------------------------------------------------------------------------------------*/ else if (configuration.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { - SetTopicReference(topic.Parent, target, configuration, cache); + SetTopicReference(source.Parent, target, configuration, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -403,16 +403,16 @@ Dictionary cache /// Assuming the 's is of the type , , , or , the method will attempt to set the property on the based on, in order, the 's Get{Property}() method, {Property} + /// name="target"/> based on, in order, the 's Get{Property}() method, {Property} /// property, and, finally, its collection (using ). If the property is not of a settable type, or the source - /// value cannot be identified on the , then the property is not set. + /// value cannot be identified on the , then the property is not set. /// - /// The source from which to pull the value. + /// The source from which to pull the value. /// The target DTO on which to set the property value. /// The with details about the property's attributes. /// - protected static void SetScalarValue(Topic topic, object target, PropertyConfiguration configuration) { + protected static void SetScalarValue(Topic source, object target, PropertyConfiguration configuration) { /*------------------------------------------------------------------------------------------------------------------------ | Escape clause if preconditions are not met @@ -424,20 +424,20 @@ protected static void SetScalarValue(Topic topic, object target, PropertyConfigu /*------------------------------------------------------------------------------------------------------------------------ | Attempt to retrieve value from topic.Get{Property}() \-----------------------------------------------------------------------------------------------------------------------*/ - var attributeValue = _typeCache.GetMethodValue(topic, "Get" + configuration.AttributeKey)?.ToString(); + var attributeValue = _typeCache.GetMethodValue(source, "Get" + configuration.AttributeKey)?.ToString(); /*------------------------------------------------------------------------------------------------------------------------ | Attempt to retrieve value from topic.{Property} \-----------------------------------------------------------------------------------------------------------------------*/ if (String.IsNullOrEmpty(attributeValue)) { - attributeValue = _typeCache.GetPropertyValue(topic, configuration.AttributeKey)?.ToString(); + attributeValue = _typeCache.GetPropertyValue(source, configuration.AttributeKey)?.ToString(); } /*------------------------------------------------------------------------------------------------------------------------ | Otherwise, attempt to retrieve value from topic.Attributes.GetValue({Property}) \-----------------------------------------------------------------------------------------------------------------------*/ if (String.IsNullOrEmpty(attributeValue)) { - attributeValue = topic.Attributes.GetValue( + attributeValue = source.Attributes.GetValue( configuration.AttributeKey, configuration.DefaultValue?.ToString(), configuration.InheritValue @@ -462,13 +462,13 @@ protected static void SetScalarValue(Topic topic, object target, PropertyConfigu /// /// /// Given a collection on a DTO, attempts to identify a source - /// collection on the . Collections can be mapped to , . Collections can be mapped to , , or to a nested topic (which will be part of /// ). By default, will attempt to map based on the /// property name, though this behavior can be modified using the , based on annotations /// on the DTO. /// - /// The source from which to pull the value. + /// The source from which to pull the value. /// The target DTO on which to set the property value. /// Determines what relationships the mapping should follow, if any. /// A cache to keep track of already-mapped object instances. @@ -476,7 +476,7 @@ protected static void SetScalarValue(Topic topic, object target, PropertyConfigu /// The with details about the property's attributes. /// protected void SetCollectionValue( - Topic topic, + Topic source, object target, Relationships relationships, Dictionary cache, @@ -502,7 +502,7 @@ PropertyConfiguration configuration /*------------------------------------------------------------------------------------------------------------------------ | Establish source collection to store topics to be mapped \-----------------------------------------------------------------------------------------------------------------------*/ - var sourceList = GetSourceCollection(topic, relationships, configuration); + var sourceList = GetSourceCollection(source, relationships, configuration); /*------------------------------------------------------------------------------------------------------------------------ | Validate that source collection was identified @@ -526,18 +526,18 @@ PropertyConfiguration configuration /// /// /// Given a collection on a target DTO, attempts to identify a source collection on the - /// . Collections can be mapped to , . Collections can be mapped to , , or to a nested topic (which will be part of /// ). By default, will attempt to map based on the /// property name, though this behavior can be modified using the , based on annotations /// on the target DTO. /// - /// The source from which to pull the value. + /// The source from which to pull the value. /// Determines what relationships the mapping should follow, if any. /// /// The with details about the property's attributes. /// - protected IList GetSourceCollection(Topic topic, Relationships relationships, PropertyConfiguration configuration) { + protected IList GetSourceCollection(Topic source, Relationships relationships, PropertyConfiguration configuration) { /*------------------------------------------------------------------------------------------------------------------------ | Establish source collection to store topics to be mapped @@ -551,15 +551,15 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && IsCurrentRelationship(RelationshipType.Children) ) { - listSource = topic.Children.ToList(); + listSource = source.Children.ToList(); } /*------------------------------------------------------------------------------------------------------------------------ | Handle (outgoing) relationships \-----------------------------------------------------------------------------------------------------------------------*/ if (IsCurrentRelationship(RelationshipType.Relationship)) { - if (topic.Relationships.Contains(configuration.RelationshipKey)) { - listSource = topic.Relationships.GetTopics(configuration.RelationshipKey); + if (source.Relationships.Contains(configuration.RelationshipKey)) { + listSource = source.Relationships.GetTopics(configuration.RelationshipKey); } } @@ -567,8 +567,8 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh | Handle nested topics, or children corresponding to the property name \-----------------------------------------------------------------------------------------------------------------------*/ if (IsCurrentRelationship(RelationshipType.NestedTopics)) { - if (topic.Children.Contains(configuration.RelationshipKey)) { - listSource = topic.Children[configuration.RelationshipKey].Children.ToList(); + if (source.Children.Contains(configuration.RelationshipKey)) { + listSource = source.Children[configuration.RelationshipKey].Children.ToList(); } } @@ -576,8 +576,8 @@ protected IList GetSourceCollection(Topic topic, Relationships relationsh | Handle (incoming) relationships \-----------------------------------------------------------------------------------------------------------------------*/ if (IsCurrentRelationship(RelationshipType.IncomingRelationship)) { - if (topic.IncomingRelationships.Contains(configuration.RelationshipKey)) { - listSource = topic.IncomingRelationships.GetTopics(configuration.RelationshipKey); + if (source.IncomingRelationships.Contains(configuration.RelationshipKey)) { + listSource = source.IncomingRelationships.GetTopics(configuration.RelationshipKey); } } @@ -687,19 +687,19 @@ IList targetList /// /// Given a reference to an external topic, attempts to match it to a matching property. /// - /// The source from which to pull the value. + /// The source from which to pull the value. /// The target DTO on which to set the property value. /// A cache to keep track of already-mapped object instances. /// /// The with details about the property's attributes. /// protected void SetTopicReference( - Topic topic, + Topic source, object target, PropertyConfiguration configuration, Dictionary cache ) { - var topicDto = Map(topic, configuration.CrawlRelationships, cache); + var topicDto = Map(source, configuration.CrawlRelationships, cache); if (topicDto != null && configuration.Property.PropertyType.IsAssignableFrom(topicDto.GetType())) { configuration.Property.SetValue(target, topicDto); } @@ -711,14 +711,14 @@ Dictionary cache /// /// Helper function recursively iterates through children and adds each to a collection. /// - /// The entity pull the data from. + /// The entity pull the data from. /// The list of instances to add each child to. /// Optionally enable including nested topics in the list. - protected IList PopulateChildTopics(Topic topic, IList topics, bool includeNestedTopics = false) { - if (topic.IsDisabled) return topics; - if (topic.ContentType.Equals("List") && !includeNestedTopics) return topics; - topics.Add(topic); - foreach (var child in topic.Children) { + protected IList PopulateChildTopics(Topic source, IList topics, bool includeNestedTopics = false) { + if (source.IsDisabled) return topics; + if (source.ContentType.Equals("List") && !includeNestedTopics) return topics; + topics.Add(source); + foreach (var child in source.Children) { PopulateChildTopics(child, topics); } return topics; From ed80d27de70625cd235c0bffc32223b5ca247367 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 23:46:06 -0700 Subject: [PATCH 075/149] Rearranged parameter order The ordering is (slightly) less consistent with the `Map()` overloads, but is syntactically more intuitive for each of the individual helper functions. Focused on putting source and target objects first, where appropriate, and always putting `cache` last. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 36 +++++++++---------- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 14822cdc..c75337cb 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 0b04877d..8be06123 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 316698f5..2f46f44c 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 8c47dcc3..a4be3947 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 46d176f0..34ca6128 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 11b66b27..da8d0069 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 9ee0df1b..ad2381de 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -368,7 +368,7 @@ Dictionary cache | Property: Collections \-----------------------------------------------------------------------------------------------------------------------*/ else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { - SetCollectionValue(source, target, relationships, cache, configuration); + SetCollectionValue(source, target, relationships, configuration, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -471,16 +471,16 @@ protected static void SetScalarValue(Topic source, object target, PropertyConfig /// The source from which to pull the value. /// The target DTO on which to set the property value. /// Determines what relationships the mapping should follow, if any. - /// A cache to keep track of already-mapped object instances. /// /// The with details about the property's attributes. /// + /// A cache to keep track of already-mapped object instances. protected void SetCollectionValue( Topic source, object target, Relationships relationships, - Dictionary cache, - PropertyConfiguration configuration + PropertyConfiguration configuration, + Dictionary cache ) { /*------------------------------------------------------------------------------------------------------------------------ @@ -514,7 +514,7 @@ PropertyConfiguration configuration /*------------------------------------------------------------------------------------------------------------------------ | Map the topics from the source collection, and add them to the target collection \-----------------------------------------------------------------------------------------------------------------------*/ - PopulateTargetCollection(configuration, cache, sourceList, targetList); + PopulateTargetCollection(sourceList, targetList, configuration, cache); } @@ -625,17 +625,17 @@ bool IsCurrentRelationship(RelationshipType targetRelationshipType) => ( /// /// Given a source list, will populate a target list based on the configured behavior of the target property. /// + /// The to pull the source objects from. + /// The target to add the mapped objects to. /// /// The with details about the property's attributes. /// /// A cache to keep track of already-mapped object instances. - /// The to pull the source objects from. - /// The target to add the mapped objects to. protected void PopulateTargetCollection( - PropertyConfiguration configuration, - Dictionary cache, IList sourceList, - IList targetList + IList targetList, + PropertyConfiguration configuration, + Dictionary cache ) { /*------------------------------------------------------------------------------------------------------------------------ @@ -689,10 +689,10 @@ IList targetList /// /// The source from which to pull the value. /// The target DTO on which to set the property value. - /// A cache to keep track of already-mapped object instances. /// /// The with details about the property's attributes. /// + /// A cache to keep track of already-mapped object instances. protected void SetTopicReference( Topic source, object target, @@ -712,16 +712,16 @@ Dictionary cache /// Helper function recursively iterates through children and adds each to a collection. /// /// The entity pull the data from. - /// The list of instances to add each child to. + /// The list of instances to add each child to. /// Optionally enable including nested topics in the list. - protected IList PopulateChildTopics(Topic source, IList topics, bool includeNestedTopics = false) { - if (source.IsDisabled) return topics; - if (source.ContentType.Equals("List") && !includeNestedTopics) return topics; - topics.Add(source); + protected IList PopulateChildTopics(Topic source, IList targetList, bool includeNestedTopics = false) { + if (source.IsDisabled) return targetList; + if (source.ContentType.Equals("List") && !includeNestedTopics) return targetList; + targetList.Add(source); foreach (var child in source.Children) { - PopulateChildTopics(child, topics); + PopulateChildTopics(child, targetList); } - return topics; + return targetList; } } //Class diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 4f5d967e..3239f230 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1740.0")] +[assembly: AssemblyFileVersion("3.5.1741.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 6f10323cb91f01a8acaa0d533d34df6eb67ba1e5 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sat, 5 May 2018 23:52:20 -0700 Subject: [PATCH 076/149] Added better explanation for empty `catch` statement Empty `catch` statements are generally frowned upon. In this case, there isn't a much better option, while keeping the format highly-reusable. That's fine, but it needs a good explanation so future developers understand the thinking. --- Ignia.Topics/Mapping/TopicMappingService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index ad2381de..14b3dcaa 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -674,7 +674,8 @@ Dictionary cache targetList.Add(childDto); } catch (ArgumentException) { - //Ignore exceptions caused by duplicate keys + //Ignore exceptions caused by duplicate keys, in case the IList represents a keyed collection + //We would defensively check for this, except IList doesn't provide a suitable method to do so } } From b38ded9eb356e8a8e8b10a949c3c501fdd9c489d Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 00:01:59 -0700 Subject: [PATCH 077/149] Removed unnecessary `sourcetype` and `targetType` variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `targetType` is used, but only once—can be defined inline easy enough. `sourceType` is no longer used, and is defined instead within helper methods as appropriate. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 4 +--- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index c75337cb..db31a2b2 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 8be06123..1d2f63cc 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 2f46f44c..6e15bdd1 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index a4be3947..ea8842f6 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 34ca6128..4bc69089 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index da8d0069..dbe2a0dc 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 14b3dcaa..36794678 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -345,8 +345,6 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Establish per-property variables \-----------------------------------------------------------------------------------------------------------------------*/ - var sourceType = source.GetType(); - var targetType = target.GetType(); var configuration = new PropertyConfiguration(property); var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); @@ -360,7 +358,7 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Property: Scalar Value \-----------------------------------------------------------------------------------------------------------------------*/ - if (_typeCache.HasSettableProperty(targetType, property.Name)) { + if (_typeCache.HasSettableProperty(target.GetType(), property.Name)) { SetScalarValue(source, target, configuration); } diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 3239f230..aec403f5 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1742.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From ce53eac25591378663bb086bf6180a0f638bf5e1 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 11:40:09 -0700 Subject: [PATCH 078/149] Introduced generic `FindAll()` method with delegate `FindAllByAttribute()` is handy, and satisfies many conditions, but it should be abstracted to support _any_ condition. The new `FindAll()` does just that. --- .../CachedTopicRepository.cs | 1 + .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Querying/Topic.cs | 74 ++++++++++++------- 7 files changed, 55 insertions(+), 30 deletions(-) diff --git a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs index dd987518..d4e02b17 100644 --- a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs +++ b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Contracts; using Ignia.Topics.Collections; using Ignia.Topics.Repositories; +using Ignia.Topics.Querying; namespace Ignia.Topics.Data.Caching { diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index db31a2b2..15a83bd2 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 6e15bdd1..f2b58e59 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1744.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index ea8842f6..4c2af6b4 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 4bc69089..31a4a2c4 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index aec403f5..f27354d3 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Querying/Topic.cs b/Ignia.Topics/Querying/Topic.cs index 78eb6fdb..87c4d951 100644 --- a/Ignia.Topics/Querying/Topic.cs +++ b/Ignia.Topics/Querying/Topic.cs @@ -18,54 +18,36 @@ namespace Ignia.Topics.Querying { public static class Topic { /*========================================================================================================================== - | METHOD: FIND ALL BY ATTRIBUTE - >=========================================================================================================================== - | ###TODO JJC080313: Consider adding an overload of the out-of-the-box FindAll() method that supports recursion, thus - | allowing a search by any criteria - including attributes. + | METHOD: FIND ALL \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Retrieves a collection of topics based on an attribute name and value. + /// Retrieves a collection of topics based on a supplied function. /// /// The instance of the to operate against; populated automatically by .NET. - /// The string identifier for the against which to be searched. - /// The text value for the against which to be searched. + /// The function to validate whether a should be included in the output. /// A collection of topics matching the input parameters. - /// - /// !String.IsNullOrWhiteSpace(name) - /// - /// - /// !name.Contains(" ") - /// - public static ReadOnlyTopicCollection FindAllByAttribute(this Target.Topic topic, string name, string value) { + public static ReadOnlyTopicCollection FindAll(this Target.Topic topic, Func predicate) { /*---------------------------------------------------------------------------------------------------------------------- | Validate contracts \---------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(topic != null, "The topic parameter must be specified."); - Contract.Requires(!String.IsNullOrWhiteSpace(name), "The attribute name must be specified."); - Contract.Requires(!String.IsNullOrWhiteSpace(value), "The attribute value must be specified."); Contract.Ensures(Contract.Result>() != null); - TopicFactory.ValidateKey(name); /*---------------------------------------------------------------------------------------------------------------------- | Search attributes \---------------------------------------------------------------------------------------------------------------------*/ var results = new TopicCollection(); - if ( - !String.IsNullOrEmpty(topic.Attributes.GetValue(name)) && - topic.Attributes.GetValue(name).IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0 - ) { + if (predicate(topic)) { results.Add(topic); } /*---------------------------------------------------------------------------------------------------------------------- - | Search children, if recursive + | Recurse over children \---------------------------------------------------------------------------------------------------------------------*/ foreach (var child in topic.Children) { - var nestedResults = child.FindAllByAttribute(name, value); + var nestedResults = child.FindAll(predicate); foreach (var matchedTopic in nestedResults) { if (!results.Contains(matchedTopic.Key)) { results.Add(matchedTopic); @@ -80,5 +62,47 @@ public static class Topic { } + /*========================================================================================================================== + | METHOD: FIND ALL BY ATTRIBUTE + >=========================================================================================================================== + | ###TODO JJC080313: Consider adding an overload of the out-of-the-box FindAll() method that supports recursion, thus + | allowing a search by any criteria - including attributes. + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Retrieves a collection of topics based on an attribute name and value. + /// + /// The instance of the to operate against; populated automatically by .NET. + /// The string identifier for the against which to be searched. + /// The text value for the against which to be searched. + /// A collection of topics matching the input parameters. + /// + /// !String.IsNullOrWhiteSpace(name) + /// + /// + /// !name.Contains(" ") + /// + public static ReadOnlyTopicCollection FindAllByAttribute(this Target.Topic topic, string name, string value) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Validate contracts + \---------------------------------------------------------------------------------------------------------------------*/ + Contract.Requires(topic != null, "The topic parameter must be specified."); + Contract.Requires(!String.IsNullOrWhiteSpace(name), "The attribute name must be specified."); + Contract.Requires(!String.IsNullOrWhiteSpace(value), "The attribute value must be specified."); + Contract.Ensures(Contract.Result>() != null); + TopicFactory.ValidateKey(name); + + /*---------------------------------------------------------------------------------------------------------------------- + | Return results + \---------------------------------------------------------------------------------------------------------------------*/ + return topic.FindAll(t => + !String.IsNullOrEmpty(t.Attributes.GetValue(name)) && + t.Attributes.GetValue(name).IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0 + ); + + } + } } From 35b83d187bb2abba4cb0e9312da4e03da671cef4 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 12:08:37 -0700 Subject: [PATCH 079/149] Introduced `FindFirst()` extension method --- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Querying/Topic.cs | 40 +++++++++++++++++++ 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 15a83bd2..3fae4d35 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1747.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index f2b58e59..2880ccef 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1744.0")] +[assembly: AssemblyFileVersion("3.5.1751.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 4c2af6b4..aaf5d5ea 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1745.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 31a4a2c4..ba61b224 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1745.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f27354d3..4c5d155c 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1746.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Querying/Topic.cs b/Ignia.Topics/Querying/Topic.cs index 87c4d951..976fe840 100644 --- a/Ignia.Topics/Querying/Topic.cs +++ b/Ignia.Topics/Querying/Topic.cs @@ -17,6 +17,46 @@ namespace Ignia.Topics.Querying { /// public static class Topic { + /*========================================================================================================================== + | METHOD: FIND FIRST + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Finds the first instance of a in the topic tree that satisfies the delegate. + /// + /// The instance of the to operate against; populated automatically by .NET. + /// The function to validate whether a should be included in the output. + /// The first instance of the topic to be satisfied. + public static Target.Topic FindFirst(this Target.Topic topic, Func predicate) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Validate contracts + \---------------------------------------------------------------------------------------------------------------------*/ + Contract.Requires(topic != null, "The topic parameter must be specified."); + + /*---------------------------------------------------------------------------------------------------------------------- + | Search attributes + \---------------------------------------------------------------------------------------------------------------------*/ + if (predicate(topic)) { + return topic; + } + + /*---------------------------------------------------------------------------------------------------------------------- + | Recurse over children + \---------------------------------------------------------------------------------------------------------------------*/ + foreach (var child in topic.Children) { + var nestedResult = child.FindFirst(predicate); + if (nestedResult != null) { + return nestedResult; + } + } + + /*---------------------------------------------------------------------------------------------------------------------- + | Indicate no results found + \---------------------------------------------------------------------------------------------------------------------*/ + return null; + + } + /*========================================================================================================================== | METHOD: FIND ALL \-------------------------------------------------------------------------------------------------------------------------*/ From 746141f6663951a5ded18066b2c3b470c6938f7b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 12:10:16 -0700 Subject: [PATCH 080/149] =?UTF-8?q?Replaced=20`GetTopic(topicId)`=20with?= =?UTF-8?q?=20`Topic.FindFirst(=E2=80=A6)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new `FindFirst()` extension method effectively handles the use case of traversing the entire topic graph and returning the first topic that has the specified `topicId`. Also wrote a unit test for this scenario to ensure it was appropriately covered (unclear why this scenario wasn't previously addressed). --- .../CachedTopicRepository.cs | 54 ++----------------- Ignia.Topics.Tests/ITopicRepositoryTest.cs | 16 ++++++ 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs index d4e02b17..696fef0d 100644 --- a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs +++ b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs @@ -72,11 +72,6 @@ public CachedTopicRepository(ITopicRepository dataProvider) : base() { /// A topic object. public override Topic Load(int topicId, bool isRecursive = true) { - /*------------------------------------------------------------------------------------------------------------------------ - | Validate contracts - \-----------------------------------------------------------------------------------------------------------------------*/ - Contract.Ensures(Contract.Result() != null); - /*------------------------------------------------------------------------------------------------------------------------ | Ensure topics are loaded \-----------------------------------------------------------------------------------------------------------------------*/ @@ -85,16 +80,16 @@ public override Topic Load(int topicId, bool isRecursive = true) { } /*------------------------------------------------------------------------------------------------------------------------ - | Lookup by TopicId + | Handle request for entire tree \-----------------------------------------------------------------------------------------------------------------------*/ - if (topicId >= 0) { - return GetTopic(_cache, topicId); + if (topicId < 0) { + return _cache; } /*------------------------------------------------------------------------------------------------------------------------ - | Return entire cache + | Recursive search \-----------------------------------------------------------------------------------------------------------------------*/ - return _cache; + return _cache.FindFirst(t => t.Id.Equals(topicId)); } @@ -143,45 +138,6 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { /*========================================================================================================================== | METHOD: GET TOPIC \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Retrieves a topic object based on the current topic scope and the specified integer identifier. - /// - /// - /// If the specified ID does not match the identifier for the current topic, its children will be searched. - /// - /// The root topic to search from. - /// The integer identifier for the topic. - /// The topic or null, if the topic is not found. - /// - /// topicId <= 0 - /// - private Topic GetTopic(Topic sourceTopic, int topicId) { - - /*------------------------------------------------------------------------------------------------------------------------ - | Validate input - \-----------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(topicId >= 0, "The topicId is expected to be a non-negative integer."); - - /*------------------------------------------------------------------------------------------------------------------------ - | Return if current - \-----------------------------------------------------------------------------------------------------------------------*/ - if (sourceTopic.Id == topicId) return sourceTopic; - - /*------------------------------------------------------------------------------------------------------------------------ - | Iterate through children - \-----------------------------------------------------------------------------------------------------------------------*/ - foreach (var childTopic in sourceTopic.Children) { - var foundTopic = GetTopic(childTopic, topicId); - if (foundTopic != null) return foundTopic; - } - - /*------------------------------------------------------------------------------------------------------------------------ - | Return null if not found - \-----------------------------------------------------------------------------------------------------------------------*/ - return null; - - } - /// /// Retrieves a topic object based on the specified partial or full (prefixed) topic key. /// diff --git a/Ignia.Topics.Tests/ITopicRepositoryTest.cs b/Ignia.Topics.Tests/ITopicRepositoryTest.cs index 85be5757..0903daed 100644 --- a/Ignia.Topics.Tests/ITopicRepositoryTest.cs +++ b/Ignia.Topics.Tests/ITopicRepositoryTest.cs @@ -66,6 +66,22 @@ public void ITopicRepository_LoadTest() { } + /*========================================================================================================================== + | TEST: LOAD BY ID + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Loads topic by ID and ensures it is found. + /// + [TestMethod] + public void ITopicRepository_LoadByIdTest() { + + var topic = _topicRepository.Load(11111); + + Assert.IsNotNull(topic); + Assert.AreEqual("Web_1_1_1_1", topic.Key); + + } + /*========================================================================================================================== | TEST: SAVE \-------------------------------------------------------------------------------------------------------------------------*/ From d6f60850647c5de4f7204db8e256b306c94b363f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 12:54:16 -0700 Subject: [PATCH 081/149] Established reusable `TypeIndex` type The `TypeIndex` centralizes the logic of looking up classes based on some criteria (specified via a delegate in the constructor). This is intended as an internal helper class. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Reflection/TypeIndex.cs | 119 ++++++++++++++++++ 9 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 Ignia.Topics/Reflection/TypeIndex.cs diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 3fae4d35..b87ae315 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1747.0")] +[assembly: AssemblyFileVersion("3.5.1748.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 1d2f63cc..79bb1945 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 2880ccef..4f490630 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1752.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index aaf5d5ea..4bc14fd3 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1746.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index ba61b224..ddac8705 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1746.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index dbe2a0dc..83261e82 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1743.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 374d37b0..b18ded8a 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -160,6 +160,7 @@ + diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 4c5d155c..a86acb5a 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1746.0")] +[assembly: AssemblyFileVersion("3.5.1747.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Reflection/TypeIndex.cs b/Ignia.Topics/Reflection/TypeIndex.cs new file mode 100644 index 00000000..5e5a9a55 --- /dev/null +++ b/Ignia.Topics/Reflection/TypeIndex.cs @@ -0,0 +1,119 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Ignia.Topics.Reflection { + + /*============================================================================================================================ + | CLASS: TYPE INDEX + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The will search all assemblies for instances that match a predicate. + /// + internal class TypeIndex: KeyedCollection { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a based on a and, optionally, a + /// default object to return if none is specified. + /// + /// The search condition to use to identify target classes. + /// The default type to return if no match can be found. Defaults to object. + internal TypeIndex(Func predicate, Type defaultType = null) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Set default type + \---------------------------------------------------------------------------------------------------------------------*/ + DefaultType = defaultType; + + /*---------------------------------------------------------------------------------------------------------------------- + | Find target classes + \---------------------------------------------------------------------------------------------------------------------*/ + var matchedTypes = AppDomain + .CurrentDomain + .GetAssemblies() + .SelectMany(t => t.GetTypes()) + .Where(t => t.IsClass && predicate(t)) + .OrderBy(t => t.Namespace.StartsWith("Ignia.Topics")) + .ToList(); + + /*---------------------------------------------------------------------------------------------------------------------- + | Populate collection + \---------------------------------------------------------------------------------------------------------------------*/ + foreach (var type in matchedTypes) { + if (!Contains(type)) { + Add(type); + } + } + + } + + /*========================================================================================================================== + | PROPERTY: DEFAULT TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The default type to return in case cannot find a match. + /// + internal Type DefaultType { get; } + + /*========================================================================================================================== + | METHOD: GET TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Retrieves a from the class based on its string representation. + /// + /// A string representing the type. + /// A class type corresponding to the specified string. + /// + /// !String.IsNullOrWhiteSpace(contentType) + /// + /// + /// !contentType.Contains(" ") + /// + internal Type GetType(string typeName) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Validate contracts + \---------------------------------------------------------------------------------------------------------------------*/ + Contract.Requires(!String.IsNullOrWhiteSpace(typeName)); + Contract.Ensures(Contract.Result() != null); + + /*---------------------------------------------------------------------------------------------------------------------- + | Return cached entry + \---------------------------------------------------------------------------------------------------------------------*/ + if (Contains(typeName)) { + return this[typeName]; + } + + /*---------------------------------------------------------------------------------------------------------------------- + | Return default + \---------------------------------------------------------------------------------------------------------------------*/ + return DefaultType; + + } + + /*========================================================================================================================== + | OVERRIDE: GET KEY FOR ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Method must be overridden for the EntityCollection to extract the keys from the items. + /// + /// The object from which to extract the key. + /// The key for the specified collection item. + protected override string GetKeyForItem(Type item) { + Contract.Assume(item != null, "Assumes the item is available when deriving its key."); + return item.Name; + } + + } //Class +} //Namespace \ No newline at end of file From 9173ba99de1930bb2bc0f4e934962b8facbd8458 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 12:55:41 -0700 Subject: [PATCH 082/149] Implemented `TypeIndex` Replaced classes that were using reflection to lookup types with the `TypeIndex`. This reduces the code in those classes, and keeps them focused on their primary responsibility. --- Ignia.Topics/Mapping/TopicMappingService.cs | 85 ++------------------- Ignia.Topics/TopicFactory.cs | 76 ++++-------------- 2 files changed, 20 insertions(+), 141 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 36794678..7fc58e49 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -28,7 +28,7 @@ public class TopicMappingService : ITopicMappingService { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly Dictionary _typeLookup = null; + static readonly TypeIndex _typeLookup = null; static readonly TypeCollection _typeCache = new TypeCollection(); /*========================================================================================================================== @@ -48,21 +48,10 @@ static TopicMappingService() { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - var typeLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); - var matchedTypes = AppDomain - .CurrentDomain - .GetAssemblies() - .SelectMany(t => t.GetTypes()) - .Where(t => t.IsClass && t.Name.EndsWith("TopicViewModel", StringComparison.InvariantCultureIgnoreCase)) - .OrderBy(t => t.Namespace.Equals("Ignia.Topics.ViewModels")) - .ToList(); - foreach (var type in matchedTypes) { - var associatedContentType = type.Name.Replace("TopicViewModel", ""); - if (!typeLookup.ContainsKey(associatedContentType)) { - typeLookup.Add(associatedContentType, type); - } - } - _typeLookup = typeLookup; + _typeLookup = new TypeIndex( + t => t.Name.EndsWith("TopicViewModel", StringComparison.InvariantCultureIgnoreCase), + typeof(object) + ); } #pragma warning restore CA1810 // Initialize reference type static fields inline @@ -78,68 +67,6 @@ public TopicMappingService(ITopicRepository topicRepository) { _topicRepository = topicRepository; } - /*========================================================================================================================== - | METHOD: GET VIEW MODEL TYPE - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Static helper method for looking up a class type based on a string name. - /// - /// - /// - /// According to the default mapping rules, the following will each be mapped: - /// - /// Scalar values of types , , or - /// Properties named Parent (will reference ) - /// Collections named Children (will reference ) - /// - /// Collections starting with Related (will reference corresponding ) - /// - /// - /// Collections corresponding to any of the same name, and the type ListItem, - /// representing a nested topic list - /// - /// - /// - /// - /// Currently, this method uses reflection to lookup all types ending with TopicViewModel across all assemblies - /// and namespaces. This is incredibly non-performant, and can take over a second to execute. As such, this data is - /// cached for the duration of the application (it is not expected that new classes will be generated during the scope - /// of the application). - /// - /// - /// A string representing the key of the target content type. - /// A class type corresponding to the specified string, and ending with "TopicViewModel". - /// - /// !String.IsNullOrWhiteSpace(contentType) - /// - /// - /// !contentType.Contains(" ") - /// - private static Type GetViewModelType(string contentType) { - - /*---------------------------------------------------------------------------------------------------------------------- - | Validate contracts - \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(!String.IsNullOrWhiteSpace(contentType)); - Contract.Ensures(Contract.Result() != null); - TopicFactory.ValidateKey(contentType); - - /*---------------------------------------------------------------------------------------------------------------------- - | Return cached entry - \---------------------------------------------------------------------------------------------------------------------*/ - if (_typeLookup.Keys.Contains(contentType)) { - return _typeLookup[contentType]; - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Return default - \---------------------------------------------------------------------------------------------------------------------*/ - return typeof(object); - - } - /*========================================================================================================================== | METHOD: MAP \-------------------------------------------------------------------------------------------------------------------------*/ @@ -206,7 +133,7 @@ private object Map(Topic topic, Relationships relationships, Dictionary _typeLookup = new Dictionary(); + static readonly TypeIndex _typeLookup = null; /*========================================================================================================================== - | METHOD: GET TOPIC TYPE + | CONSTRUCTOR (STATIC) \-------------------------------------------------------------------------------------------------------------------------*/ +#pragma warning disable CA1810 // Initialize reference type static fields inline /// - /// Static helper method for looking up a class type based on a string name. + /// Establishes static variables for the . /// - /// - /// Currently, this method uses , which can be non-performant. As such, this helper method - /// caches its results in a static lookup table keyed by the string value. - /// - /// A string representing the key of the target content type. - /// A class type corresponding to a derived class of . - /// - /// !String.IsNullOrWhiteSpace(contentType) - /// - /// - /// !contentType.Contains(" ") - /// - private static Type GetTopicType(string contentType) { + static TopicFactory() { /*---------------------------------------------------------------------------------------------------------------------- - | Validate contracts + | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(!String.IsNullOrWhiteSpace(contentType)); - Contract.Ensures(Contract.Result() != null); - TopicFactory.ValidateKey(contentType); - - /*---------------------------------------------------------------------------------------------------------------------- - | Return cached entry - \---------------------------------------------------------------------------------------------------------------------*/ - if (_typeLookup.Keys.Contains(contentType)) { - return _typeLookup[contentType]; - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Determine if there is a matched type - \---------------------------------------------------------------------------------------------------------------------*/ - var baseType = typeof(Topic); - var targetType = Type.GetType("Ignia.Topics." + contentType); - - /*---------------------------------------------------------------------------------------------------------------------- - | Validate type - \---------------------------------------------------------------------------------------------------------------------*/ - if (targetType == null) { - targetType = baseType; - } - else if (!targetType.IsSubclassOf(baseType)) { - targetType = baseType; - throw new ArgumentException("The topic \"Ignia.Topics." + contentType + "\" does not derive from \"Ignia.Topics.Topic\"."); - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Cache findings - \---------------------------------------------------------------------------------------------------------------------*/ - lock (_typeLookup) { - if (_typeLookup.Keys.Contains(contentType)) { - _typeLookup.Add(contentType, targetType); - } - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Return result - \---------------------------------------------------------------------------------------------------------------------*/ - return targetType; + _typeLookup = new TypeIndex( + t => typeof(Topic).IsAssignableFrom(t), + typeof(Topic) + ); } + #pragma warning restore CA1810 // Initialize reference type static fields inline /*========================================================================================================================== | METHOD: CREATE @@ -143,7 +95,7 @@ public static Topic Create(string key, string contentType, Topic parent = null) /*---------------------------------------------------------------------------------------------------------------------- | Determine target type \---------------------------------------------------------------------------------------------------------------------*/ - var targetType = TopicFactory.GetTopicType(contentType); + var targetType = _typeLookup.GetType(contentType); /*---------------------------------------------------------------------------------------------------------------------- | Identify the appropriate topic @@ -190,7 +142,7 @@ public static Topic Create(string key, string contentType, int id, Topic parent /*---------------------------------------------------------------------------------------------------------------------- | Determine target type \---------------------------------------------------------------------------------------------------------------------*/ - var targetType = TopicFactory.GetTopicType(contentType); + var targetType = _typeLookup.GetType(contentType); /*---------------------------------------------------------------------------------------------------------------------- | Identify the appropriate topic From 20916c0159056e1bd00ec8f1e729a4c1330ae060 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 14:47:50 -0700 Subject: [PATCH 083/149] Established new `ITypeLookupService` This will allow what's currently called the `TypeIndex` to be abstracted and, optionally, replaced. --- Ignia.Topics/ITypeLookupService.cs | 36 ++++++++++++++++++++++++++++++ Ignia.Topics/Ignia.Topics.csproj | 1 + 2 files changed, 37 insertions(+) create mode 100644 Ignia.Topics/ITypeLookupService.cs diff --git a/Ignia.Topics/ITypeLookupService.cs b/Ignia.Topics/ITypeLookupService.cs new file mode 100644 index 00000000..beb11036 --- /dev/null +++ b/Ignia.Topics/ITypeLookupService.cs @@ -0,0 +1,36 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using Ignia.Topics.Mapping; + +namespace Ignia.Topics { + + /*============================================================================================================================ + | INTERFACE: TYPE LOOKUP SERVICE + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides an interface for looking up s by string. + /// + /// + /// Various areas of the OnTopic library require looking up s dynamically. For instance, the and the both determine whether or not there is a corresponding to the ContentType (though the former is looking for a , and the + /// latter is looking for a data transfer object). The provides a standard interface for + /// libraries capable of providing this functionality, allowing them to be injected into other services. + /// + public interface ITypeLookupService { + + /*========================================================================================================================== + | METHOD: GET TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Gets the requested . + /// + /// The name of the to retrieve. + Type GetType(string typeName); + + } +} diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index b18ded8a..381f0ac9 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -142,6 +142,7 @@ + From 45e58a4b94ea1e572592784d04c01e74b2163117 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 14:49:28 -0700 Subject: [PATCH 084/149] Established `StaticTypeLookupService` The `StaticTypeLookupService` is simply a `KeyedCollection` of `Type` objects, which can be manually configured with a predetermined list of types. --- Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/StaticTypeLookupService.cs | 116 ++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Ignia.Topics/StaticTypeLookupService.cs diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 381f0ac9..8b37c719 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -162,6 +162,7 @@ + diff --git a/Ignia.Topics/StaticTypeLookupService.cs b/Ignia.Topics/StaticTypeLookupService.cs new file mode 100644 index 00000000..08dd96f0 --- /dev/null +++ b/Ignia.Topics/StaticTypeLookupService.cs @@ -0,0 +1,116 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; + +namespace Ignia.Topics { + + /*============================================================================================================================ + | CLASS: TYPE INDEX + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The can be configured to provide a lookup of . + /// + public class StaticTypeLookupService: KeyedCollection, ITypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a . Optionally accepts a list of + /// instances and a default value. + /// + /// + /// Any instances submitted via should be unique by ; if they are not, they will be removed. + /// + /// The list of instances to expose as part of this service. + /// The default type to return if no match can be found. Defaults to object. + public StaticTypeLookupService(IEnumerable types = null, Type defaultType = null) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Set default type + \---------------------------------------------------------------------------------------------------------------------*/ + DefaultType = defaultType; + + /*---------------------------------------------------------------------------------------------------------------------- + | Populate collection + \---------------------------------------------------------------------------------------------------------------------*/ + if (types != null) { + foreach (var type in types) { + if (!Contains(type)) { + Add(type); + } + } + } + + } + + /*========================================================================================================================== + | PROPERTY: DEFAULT TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The default type to return in case cannot find a match. + /// + public Type DefaultType { get; } + + /*========================================================================================================================== + | METHOD: GET TYPE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Retrieves a from the class based on its string representation. + /// + /// A string representing the type. + /// A class type corresponding to the specified string. + /// + /// !String.IsNullOrWhiteSpace(contentType) + /// + /// + /// !contentType.Contains(" ") + /// + public Type GetType(string typeName) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Validate contracts + \---------------------------------------------------------------------------------------------------------------------*/ + Contract.Requires(!String.IsNullOrWhiteSpace(typeName)); + Contract.Ensures(Contract.Result() != null); + + /*---------------------------------------------------------------------------------------------------------------------- + | Return cached entry + \---------------------------------------------------------------------------------------------------------------------*/ + if (Contains(typeName)) { + return this[typeName]; + } + + /*---------------------------------------------------------------------------------------------------------------------- + | Return default + \---------------------------------------------------------------------------------------------------------------------*/ + return DefaultType; + + } + + /*========================================================================================================================== + | OVERRIDE: GET KEY FOR ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Method must be overridden for the EntityCollection to extract the keys from the items. + /// + /// The object from which to extract the key. + /// The key for the specified collection item. + protected override string GetKeyForItem(Type item) { + Contract.Assume(item != null, "Assumes the item is available when deriving its key."); + return item.Name; + } + + } //Class +} //Namespace \ No newline at end of file From 0b7da33ce15ffda93f1970236d6abe690753416a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 15:49:21 -0700 Subject: [PATCH 085/149] Established `DynamicTypeLookupService` Renamed `TypeIndex` to `DynamicTypeLookupService`, based on the `StaticTypeLookupService`, thus supporting the new `ITypeLookupService` abstraction. Further, introduced `TopicLookupService` (for looking up derivatives of `Topic`) and `TopicViewModelLookupService` (for looking up classes ending with `TopicViewModel`) and implemented them, respectively, in `TopicFactory` (using property injection) and `TopicMappingService` (using constructor injection). --- Ignia.Topics/Ignia.Topics.csproj | 4 +- Ignia.Topics/Mapping/TopicMappingService.cs | 7 +- .../Reflection/DynamicTypeLookupService.cs | 54 ++++++++ Ignia.Topics/Reflection/TopicLookupService.cs | 31 +++++ .../Reflection/TopicViewModelLookupService.cs | 31 +++++ Ignia.Topics/Reflection/TypeIndex.cs | 119 ------------------ Ignia.Topics/TopicFactory.cs | 7 +- 7 files changed, 123 insertions(+), 130 deletions(-) create mode 100644 Ignia.Topics/Reflection/DynamicTypeLookupService.cs create mode 100644 Ignia.Topics/Reflection/TopicLookupService.cs create mode 100644 Ignia.Topics/Reflection/TopicViewModelLookupService.cs delete mode 100644 Ignia.Topics/Reflection/TypeIndex.cs diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 8b37c719..63edf81d 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -158,10 +158,12 @@ + + + - diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 7fc58e49..9260e609 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -28,7 +28,7 @@ public class TopicMappingService : ITopicMappingService { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly TypeIndex _typeLookup = null; + static readonly ITypeLookupService _typeLookup = null; static readonly TypeCollection _typeCache = new TypeCollection(); /*========================================================================================================================== @@ -48,10 +48,7 @@ static TopicMappingService() { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - _typeLookup = new TypeIndex( - t => t.Name.EndsWith("TopicViewModel", StringComparison.InvariantCultureIgnoreCase), - typeof(object) - ); + _typeLookup = new TopicViewModelLookupService(); } #pragma warning restore CA1810 // Initialize reference type static fields inline diff --git a/Ignia.Topics/Reflection/DynamicTypeLookupService.cs b/Ignia.Topics/Reflection/DynamicTypeLookupService.cs new file mode 100644 index 00000000..24888cf5 --- /dev/null +++ b/Ignia.Topics/Reflection/DynamicTypeLookupService.cs @@ -0,0 +1,54 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Linq; + +namespace Ignia.Topics.Reflection { + + /*============================================================================================================================ + | CLASS: TYPE INDEX + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The will search all assemblies for instances that match a + /// predicate. + /// + public class DynamicTypeLookupService : StaticTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a based on a and, + /// optionally, a default object to return if none is specified. + /// + /// The search condition to use to identify target classes. + /// The default type to return if no match can be found. Defaults to object. + internal DynamicTypeLookupService(Func predicate, Type defaultType = null) : base(null, defaultType) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Find target classes + \---------------------------------------------------------------------------------------------------------------------*/ + var matchedTypes = AppDomain + .CurrentDomain + .GetAssemblies() + .SelectMany(t => t.GetTypes()) + .Where(t => t.IsClass && predicate(t)) + .OrderBy(t => t.Namespace.StartsWith("Ignia.Topics")) + .ToList(); + + /*---------------------------------------------------------------------------------------------------------------------- + | Populate collection + \---------------------------------------------------------------------------------------------------------------------*/ + foreach (var type in matchedTypes) { + if (!Contains(type)) { + Add(type); + } + } + + } + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Reflection/TopicLookupService.cs b/Ignia.Topics/Reflection/TopicLookupService.cs new file mode 100644 index 00000000..065efda1 --- /dev/null +++ b/Ignia.Topics/Reflection/TopicLookupService.cs @@ -0,0 +1,31 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; + +namespace Ignia.Topics.Reflection { + + /*============================================================================================================================ + | CLASS: TOPIC LOOKUP SERVICE + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The will search all assemblies for s that derive from . + /// + public class TopicLookupService : DynamicTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a . + /// + internal TopicLookupService() : base( + t => typeof(Topic).IsAssignableFrom(t), + typeof(Topic) + ) {} + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Reflection/TopicViewModelLookupService.cs b/Ignia.Topics/Reflection/TopicViewModelLookupService.cs new file mode 100644 index 00000000..84e44860 --- /dev/null +++ b/Ignia.Topics/Reflection/TopicViewModelLookupService.cs @@ -0,0 +1,31 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; + +namespace Ignia.Topics.Reflection { + + /*============================================================================================================================ + | CLASS: TOPIC VIEW MODEL LOOKUP SERVICE + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The will search all assemblies for s that end with + /// "TopicViewModel" + /// + public class TopicViewModelLookupService : DynamicTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a . + /// + internal TopicViewModelLookupService() : base( + t => t.Name.EndsWith("TopicViewModel"), + typeof(object) + ) {} + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Reflection/TypeIndex.cs b/Ignia.Topics/Reflection/TypeIndex.cs deleted file mode 100644 index 5e5a9a55..00000000 --- a/Ignia.Topics/Reflection/TypeIndex.cs +++ /dev/null @@ -1,119 +0,0 @@ -/*============================================================================================================================== -| Author Ignia, LLC -| Client Ignia, LLC -| Project Topics Library -\=============================================================================================================================*/ -using System; -using System.Collections.ObjectModel; -using System.Diagnostics.Contracts; -using System.Linq; - -namespace Ignia.Topics.Reflection { - - /*============================================================================================================================ - | CLASS: TYPE INDEX - \---------------------------------------------------------------------------------------------------------------------------*/ - /// - /// The will search all assemblies for instances that match a predicate. - /// - internal class TypeIndex: KeyedCollection { - - /*========================================================================================================================== - | CONSTRUCTOR - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Establishes a new instance of a based on a and, optionally, a - /// default object to return if none is specified. - /// - /// The search condition to use to identify target classes. - /// The default type to return if no match can be found. Defaults to object. - internal TypeIndex(Func predicate, Type defaultType = null) { - - /*---------------------------------------------------------------------------------------------------------------------- - | Set default type - \---------------------------------------------------------------------------------------------------------------------*/ - DefaultType = defaultType; - - /*---------------------------------------------------------------------------------------------------------------------- - | Find target classes - \---------------------------------------------------------------------------------------------------------------------*/ - var matchedTypes = AppDomain - .CurrentDomain - .GetAssemblies() - .SelectMany(t => t.GetTypes()) - .Where(t => t.IsClass && predicate(t)) - .OrderBy(t => t.Namespace.StartsWith("Ignia.Topics")) - .ToList(); - - /*---------------------------------------------------------------------------------------------------------------------- - | Populate collection - \---------------------------------------------------------------------------------------------------------------------*/ - foreach (var type in matchedTypes) { - if (!Contains(type)) { - Add(type); - } - } - - } - - /*========================================================================================================================== - | PROPERTY: DEFAULT TYPE - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// The default type to return in case cannot find a match. - /// - internal Type DefaultType { get; } - - /*========================================================================================================================== - | METHOD: GET TYPE - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Retrieves a from the class based on its string representation. - /// - /// A string representing the type. - /// A class type corresponding to the specified string. - /// - /// !String.IsNullOrWhiteSpace(contentType) - /// - /// - /// !contentType.Contains(" ") - /// - internal Type GetType(string typeName) { - - /*---------------------------------------------------------------------------------------------------------------------- - | Validate contracts - \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(!String.IsNullOrWhiteSpace(typeName)); - Contract.Ensures(Contract.Result() != null); - - /*---------------------------------------------------------------------------------------------------------------------- - | Return cached entry - \---------------------------------------------------------------------------------------------------------------------*/ - if (Contains(typeName)) { - return this[typeName]; - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Return default - \---------------------------------------------------------------------------------------------------------------------*/ - return DefaultType; - - } - - /*========================================================================================================================== - | OVERRIDE: GET KEY FOR ITEM - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Method must be overridden for the EntityCollection to extract the keys from the items. - /// - /// The object from which to extract the key. - /// The key for the specified collection item. - protected override string GetKeyForItem(Type item) { - Contract.Assume(item != null, "Assumes the item is available when deriving its key."); - return item.Name; - } - - } //Class -} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index 64f798ac..cb643272 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -24,7 +24,7 @@ public static class TopicFactory { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly TypeIndex _typeLookup = null; + static readonly ITypeLookupService _typeLookup = null; /*========================================================================================================================== | CONSTRUCTOR (STATIC) @@ -38,10 +38,7 @@ static TopicFactory() { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - _typeLookup = new TypeIndex( - t => typeof(Topic).IsAssignableFrom(t), - typeof(Topic) - ); + _typeLookup = new TopicLookupService(); } #pragma warning restore CA1810 // Initialize reference type static fields inline From 94c42181195c7235c095e810a3b508f570297fb2 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 15:54:32 -0700 Subject: [PATCH 086/149] Exposed `TypeLookupService` to `TopicFactory` By default, `TopicFactory` will use the local default of `TopicLookupService` for identifying strongly-typed `Topic` classes. Optionally, this can be overwritten, thus allowing an alternate source of `Topic` classes. As part of this, also introduced the new `FakeTopicLookupService` in the unit tests, and configured the `AssemblyInit()` to initialize it when the test assembly loads. This prevents the unit tests from needing to use any reflection, or rely on dynamic lookup of the classes, while simultaneously reducing the number of (production) classes involved in each test. --- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + .../TestDoubles/FakeTopicLookupService.cs | 39 +++++++++++++++++++ Ignia.Topics.Tests/TopicTest.cs | 6 +++ Ignia.Topics/TopicFactory.cs | 28 +++++-------- 4 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index ced22ca4..b4c6da87 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -97,6 +97,7 @@ + diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs new file mode 100644 index 00000000..b9260ab9 --- /dev/null +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs @@ -0,0 +1,39 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ignia.Topics.Tests.TestDoubles { + + /*============================================================================================================================ + | CLASS: FAKE TOPIC LOOKUP SERVICE + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides access to derived types of classes. + /// + /// + /// Allows testing of services that depend on without using expensive reflection. + /// + internal class FakeTopicLookupService: StaticTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Instantiates a new instance of the . + /// + /// A new instance of the . + internal FakeTopicLookupService(): base(null, typeof(Topic)) { + Add(typeof(Topic)); + Add(typeof(ContentTypeDescriptor)); + Add(typeof(AttributeDescriptor)); + } + + } +} diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index bb3fafeb..1d0d62e6 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using Ignia.Topics.Querying; +using Ignia.Topics.Tests.TestDoubles; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Ignia.Topics.Tests { @@ -19,6 +20,11 @@ namespace Ignia.Topics.Tests { [TestClass] public class TopicTest { + [AssemblyInitialize] + public static void AssemblyInit(TestContext context) { + TopicFactory.TypeLookupService = new FakeTopicLookupService(); + } + /*========================================================================================================================== | TEST: CREATE \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index cb643272..5a2c4002 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -22,26 +22,16 @@ namespace Ignia.Topics { public static class TopicFactory { /*========================================================================================================================== - | STATIC VARIABLES + | PROPERTY: TYPE LOOKUP SERVICE \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly ITypeLookupService _typeLookup = null; - - /*========================================================================================================================== - | CONSTRUCTOR (STATIC) - \-------------------------------------------------------------------------------------------------------------------------*/ -#pragma warning disable CA1810 // Initialize reference type static fields inline /// /// Establishes static variables for the . /// - static TopicFactory() { - - /*---------------------------------------------------------------------------------------------------------------------- - | Ensure cache is populated - \---------------------------------------------------------------------------------------------------------------------*/ - _typeLookup = new TopicLookupService(); - - } - #pragma warning restore CA1810 // Initialize reference type static fields inline + public static ITypeLookupService TypeLookupService { get; set; } = + new DynamicTypeLookupService( + t => typeof(Topic).IsAssignableFrom(t), + typeof(Topic) + ); /*========================================================================================================================== | METHOD: CREATE @@ -92,7 +82,7 @@ public static Topic Create(string key, string contentType, Topic parent = null) /*---------------------------------------------------------------------------------------------------------------------- | Determine target type \---------------------------------------------------------------------------------------------------------------------*/ - var targetType = _typeLookup.GetType(contentType); + var targetType = TypeLookupService.GetType(contentType); /*---------------------------------------------------------------------------------------------------------------------- | Identify the appropriate topic @@ -139,7 +129,7 @@ public static Topic Create(string key, string contentType, int id, Topic parent /*---------------------------------------------------------------------------------------------------------------------- | Determine target type \---------------------------------------------------------------------------------------------------------------------*/ - var targetType = _typeLookup.GetType(contentType); + var targetType = TypeLookupService.GetType(contentType); /*---------------------------------------------------------------------------------------------------------------------- | Identify the appropriate topic @@ -170,7 +160,7 @@ public static Topic Create(string key, string contentType, int id, Topic parent public static void ValidateKey(string topicKey, bool isOptional = false) { Contract.Requires(isOptional || !String.IsNullOrEmpty(topicKey)); Contract.Requires( - String.IsNullOrEmpty(topicKey) || Regex.IsMatch(topicKey?? "", @"^[a-zA-Z0-9\.\-_]+$"), + String.IsNullOrEmpty(topicKey) || Regex.IsMatch(topicKey ?? "", @"^[a-zA-Z0-9\.\-_]+$"), "Key names should only contain letters, numbers, hyphens, and/or underscores." ); } From 2d33371faf9bd32e6a6f31005e88b60177f035f7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 15:58:13 -0700 Subject: [PATCH 087/149] [Breaking] Introduced `ITypeLookupService` dependency on `TopicMappingService` This allows the `ITypeLookupService` to be defined in the Composition Root, thus enabling it to be cached using the Singleton lifestyle. As part of this, also introduced the `FakeViewModelLookupService` for unit tests, which allows the unit tests to rely on a faster, lighter implementation which isn't dependent on reflection. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 1 + Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../TestDoubles/FakeViewModelLookupService.cs | 53 +++++++++++++++++++ Ignia.Topics.Tests/TopicControllerTest.cs | 2 +- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 50 ++++++++--------- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 33 ++++-------- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 12 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index b87ae315..04bbb99d 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1748.0")] +[assembly: AssemblyFileVersion("3.5.1751.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 79bb1945..7ecfdc7a 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1745.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index b4c6da87..91799496 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -97,6 +97,7 @@ + diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 4f490630..36f1b26f 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1752.0")] +[assembly: AssemblyFileVersion("3.5.1760.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs new file mode 100644 index 00000000..28c066cc --- /dev/null +++ b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs @@ -0,0 +1,53 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ignia.Topics.Tests.ViewModels; +using Ignia.Topics.ViewModels; + +namespace Ignia.Topics.Tests.TestDoubles { + + /*============================================================================================================================ + | CLASS: FAKE TOPIC LOOKUP SERVICE + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides access to derived types of classes. + /// + /// + /// Allows testing of services that depend on without using expensive reflection. + /// + internal class FakeViewModelLookupService: StaticTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Instantiates a new instance of the . + /// + /// A new instance of the . + internal FakeViewModelLookupService(): base(null, typeof(object)) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Add out-of-the-box view models + \-----------------------------------------------------------------------------------------------------------------------*/ + Add(typeof(ContentItemTopicViewModel)); + Add(typeof(ContentListTopicViewModel)); + Add(typeof(IndexTopicViewModel)); Add(typeof(ItemTopicViewModel)); Add(typeof(LookupListItemTopicViewModel)); Add(typeof(NavigationTopicViewModel)); + Add(typeof(PageGroupTopicViewModel)); Add(typeof(PageTopicViewModel)); + Add(typeof(SectionTopicViewModel)); Add(typeof(SlideshowTopicViewModel)); Add(typeof(SlideTopicViewModel)); Add(typeof(TopicViewModel)); Add(typeof(VideoTopicViewModel)); + + /*------------------------------------------------------------------------------------------------------------------------ + | Add test specific view models + \-----------------------------------------------------------------------------------------------------------------------*/ + Add(typeof(CircularTopicViewModel)); Add(typeof(DefaultValueTopicViewModel)); Add(typeof(FilteredTopicViewModel)); Add(typeof(FlattenChildrenTopicViewModel)); Add(typeof(MetadataLookupTopicViewModel)); Add(typeof(MethodBasedViewModel)); Add(typeof(MinimumLengthPropertyTopicViewModel)); Add(typeof(RequiredObjectTopicViewModel)); Add(typeof(RequiredTopicViewModel)); Add(typeof(SampleTopicViewModel)); + + } + + } +} diff --git a/Ignia.Topics.Tests/TopicControllerTest.cs b/Ignia.Topics.Tests/TopicControllerTest.cs index 5715e9e7..7ac925e4 100644 --- a/Ignia.Topics.Tests/TopicControllerTest.cs +++ b/Ignia.Topics.Tests/TopicControllerTest.cs @@ -180,7 +180,7 @@ public void LayoutController_MenuTest() { var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var controller = new LayoutController(_topicRepository, topicRoutingService, mappingService); var result = controller.Menu() as PartialViewResult; diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index 27962cb8..bf66f74f 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -55,7 +55,7 @@ public TopicMappingServiceTest() { [TestMethod] public void TopicMappingService_MapGeneric() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); topic.Attributes.SetValue("MetaTitle", "ValueA"); @@ -80,7 +80,7 @@ public void TopicMappingService_MapGeneric() { [TestMethod] public void TopicMappingService_MapDynamic() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); topic.Attributes.SetValue("MetaTitle", "ValueA"); @@ -104,7 +104,7 @@ public void TopicMappingService_MapDynamic() { [TestMethod] public void TopicMappingService_MapParents() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Sample"); var parent = TopicFactory.Create("Parent", "Page", grandParent); var topic = TopicFactory.Create("Test", "Page", parent); @@ -146,7 +146,7 @@ public void TopicMappingService_MapParents() { [TestMethod] public void TopicMappingService_InheritValues() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Page"); var parent = TopicFactory.Create("Parent", "Page", grandParent); var topic = TopicFactory.Create("Test", "Sample", parent); @@ -171,7 +171,7 @@ public void TopicMappingService_InheritValues() { [TestMethod] public void TopicMappingService_AlternateAttributeKey() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); topic.Attributes.SetValue("Property", "ValueA"); @@ -192,7 +192,7 @@ public void TopicMappingService_AlternateAttributeKey() { [TestMethod] public void TopicMappingService_MapRelationships() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); var relatedTopic2 = TopicFactory.Create("RelatedTopic2", "Index"); var relatedTopic3 = TopicFactory.Create("RelatedTopic3", "Page"); @@ -228,7 +228,7 @@ public void TopicMappingService_MapRelationships() { [TestMethod] public void TopicMappingService_AlternateRelationship() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); var relatedTopic2 = TopicFactory.Create("RelatedTopic2", "Index"); var relatedTopic3 = TopicFactory.Create("RelatedTopic3", "Page"); @@ -264,7 +264,7 @@ public void TopicMappingService_AlternateRelationship() { [TestMethod] public void TopicMappingService_MapNestedTopics() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); var childTopic = TopicFactory.Create("ChildTopic", "Page", topic); var topicList = TopicFactory.Create("Categories", "List", topic); @@ -290,7 +290,7 @@ public void TopicMappingService_MapNestedTopics() { [TestMethod] public void TopicMappingService_MapChildren() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); var childTopic2 = TopicFactory.Create("ChildTopic2", "Page", topic); @@ -317,7 +317,7 @@ public void TopicMappingService_MapChildren() { [TestMethod] public void TopicMappingService_MapTopicReferences() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -340,7 +340,7 @@ public void TopicMappingService_MapTopicReferences() { [TestMethod] public void TopicMappingService_RecursiveRelationships() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); //Self var topic = TopicFactory.Create("Test", "Sample"); @@ -393,7 +393,7 @@ public void TopicMappingService_RecursiveRelationships() { [TestMethod] public void TopicMappingService_MapSlideshow() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Slideshow"); var slides = TopicFactory.Create("ContentItems", "List", topic); var childTopic1 = TopicFactory.Create("ChildTopic1", "Slide", slides); @@ -421,7 +421,7 @@ public void TopicMappingService_MapSlideshow() { [TestMethod] public void TopicMappingService_MapTopics() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); var relatedTopic2 = TopicFactory.Create("RelatedTopic2", "Index"); var relatedTopic3 = TopicFactory.Create("RelatedTopic3", "Page"); @@ -452,7 +452,7 @@ public void TopicMappingService_MapTopics() { [TestMethod] public void TopicMappingService_MapMetadata() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "MetadataLookup"); var target = (MetadataLookupTopicViewModel)mappingService.Map(topic); @@ -471,7 +471,7 @@ public void TopicMappingService_MapMetadata() { [TestMethod] public void TopicMappingService_MapCircularReference() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Circular", 1); var childTopic = TopicFactory.Create("ChildTopic", "Circular", 2, topic); @@ -492,7 +492,7 @@ public void TopicMappingService_MapCircularReference() { [TestMethod] public void TopicMappingService_FilterByContentType() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); @@ -520,7 +520,7 @@ public void TopicMappingService_FilterByContentType() { [TestMethod] public void TopicMappingService_MapGetterMethods() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Sample"); var childTopic = TopicFactory.Create("Child", "Page", topic); var grandChildTopic = TopicFactory.Create("GrandChild", "Index", childTopic); @@ -540,7 +540,7 @@ public void TopicMappingService_MapGetterMethods() { [TestMethod] public void TopicMappingService_MapRequiredProperty() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); topic.Attributes.SetValue("RequiredAttribute", "Required"); @@ -561,7 +561,7 @@ public void TopicMappingService_MapRequiredProperty() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapRequiredPropertyException() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); var target = (RequiredTopicViewModel)mappingService.Map(topic); @@ -578,7 +578,7 @@ public void TopicMappingService_MapRequiredPropertyException() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapRequiredObjectPropertyException() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "RequiredObject"); var target = (RequiredTopicViewModel)mappingService.Map(topic); @@ -594,7 +594,7 @@ public void TopicMappingService_MapRequiredObjectPropertyException() { [TestMethod] public void TopicMappingService_MapDefaultValueProperties() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "DefaultValue"); var target = (DefaultValueTopicViewModel)mappingService.Map(topic); @@ -615,7 +615,7 @@ public void TopicMappingService_MapDefaultValueProperties() { [ExpectedException(typeof(ValidationException))] public void TopicMappingService_MapMinimumValueProperties() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "MinimumLengthProperty"); topic.Attributes.SetValue("MinimumLength", "Hello World"); @@ -635,7 +635,7 @@ public void TopicMappingService_MapMinimumValueProperties() { [TestMethod] public void TopicMappingService_FilterByAttribute() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Filtered"); var childTopic1 = TopicFactory.Create("ChildTopic1", "Page", topic); var childTopic2 = TopicFactory.Create("ChildTopic2", "Index", topic); @@ -663,7 +663,7 @@ public void TopicMappingService_FilterByAttribute() { [TestMethod] public void TopicMappingService_Flatten() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "FlattenChildren"); @@ -690,7 +690,7 @@ public void TopicMappingService_Flatten() { [TestMethod] public void TopicMappingService_Caching() { - var mappingService = new TopicMappingService(_topicRepository); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var cachedMappingService = new CachedTopicMappingService(mappingService); var topic = TopicFactory.Create("Test", "Filtered", 5); diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 4bc14fd3..0e02200a 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1746.0")] +[assembly: AssemblyFileVersion("3.5.1749.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index ddac8705..e2287ee2 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1746.0")] +[assembly: AssemblyFileVersion("3.5.1749.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 83261e82..3640fa43 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1745.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 9260e609..adc0943d 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -28,30 +28,13 @@ public class TopicMappingService : ITopicMappingService { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly ITypeLookupService _typeLookup = null; - static readonly TypeCollection _typeCache = new TypeCollection(); + static readonly TypeCollection _typeCache = new TypeCollection(); /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - readonly ITopicRepository _topicRepository = null; - - /*========================================================================================================================== - | CONSTRUCTOR (STATIC) - \-------------------------------------------------------------------------------------------------------------------------*/ - #pragma warning disable CA1810 // Initialize reference type static fields inline - /// - /// Establishes static variables for the . - /// - static TopicMappingService() { - - /*---------------------------------------------------------------------------------------------------------------------- - | Ensure cache is populated - \---------------------------------------------------------------------------------------------------------------------*/ - _typeLookup = new TopicViewModelLookupService(); - - } - #pragma warning restore CA1810 // Initialize reference type static fields inline + readonly ITopicRepository _topicRepository = null; + readonly ITypeLookupService _typeLookupService = null; /*========================================================================================================================== | CONSTRUCTOR @@ -59,9 +42,11 @@ static TopicMappingService() { /// /// Establishes a new instance of a with required dependencies. /// - public TopicMappingService(ITopicRepository topicRepository) { + public TopicMappingService(ITopicRepository topicRepository, ITypeLookupService typeLookupService) { Contract.Requires(topicRepository != null, "An instance of an ITopicRepository is required."); + Contract.Requires(typeLookupService != null, "An instance of an ITypeLookupService is required."); _topicRepository = topicRepository; + _typeLookupService = typeLookupService; } /*========================================================================================================================== @@ -130,7 +115,7 @@ private object Map(Topic topic, Relationships relationships, Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Establish per-property variables \-----------------------------------------------------------------------------------------------------------------------*/ - var configuration = new PropertyConfiguration(property); - var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); + var configuration = new PropertyConfiguration(property); + var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index a86acb5a..a4a76c62 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1747.0")] +[assembly: AssemblyFileVersion("3.5.1751.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 61fabc372b661aa39ee6b5295aaebdcb5b6d7eda Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 15:58:22 -0700 Subject: [PATCH 088/149] Removed errant whitespace --- Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index 02a09df0..55c65840 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -20,7 +20,6 @@ namespace Ignia.Topics.Tests.TestDoubles { /// Allows testing of services that depend on without maintaining a dependency on a live /// database, or working against actual data. This is faster and safer for test methods. /// - public class FakeTopicRepository : TopicRepositoryBase, ITopicRepository { /*========================================================================================================================== From ce7d2eeb9729f94e3d2c4c2b35a704890d13b394 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:09:22 -0700 Subject: [PATCH 089/149] Marked constructor as `public` Since it is exposed as part of an abstraction `DynamicTypeLookupService` must be marked as `public`. --- Ignia.Topics/Reflection/DynamicTypeLookupService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics/Reflection/DynamicTypeLookupService.cs b/Ignia.Topics/Reflection/DynamicTypeLookupService.cs index 24888cf5..388df5d2 100644 --- a/Ignia.Topics/Reflection/DynamicTypeLookupService.cs +++ b/Ignia.Topics/Reflection/DynamicTypeLookupService.cs @@ -26,7 +26,7 @@ public class DynamicTypeLookupService : StaticTypeLookupService { /// /// The search condition to use to identify target classes. /// The default type to return if no match can be found. Defaults to object. - internal DynamicTypeLookupService(Func predicate, Type defaultType = null) : base(null, defaultType) { + public DynamicTypeLookupService(Func predicate, Type defaultType = null) : base(null, defaultType) { /*---------------------------------------------------------------------------------------------------------------------- | Find target classes From 914237717fb9374fd6701ec30d7453da6e93b02a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:14:05 -0700 Subject: [PATCH 090/149] Added `ITypeLookupService` to documentation --- Ignia.Topics/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/README.md b/Ignia.Topics/README.md index 23d17440..d52b81bf 100644 --- a/Ignia.Topics/README.md +++ b/Ignia.Topics/README.md @@ -15,12 +15,17 @@ Out of the box, the OnTopic library contains two specially derived topics for su - **[`ITopicRoutingService`](ITopicRoutingService.cs)**: Given contextual information, such as a URL and routing information, will identify the current `Topic` instance. What contextual information is required is environment-specific; for instance, the `MvcTopicRoutingService` requires an `ITopicRepository`, `Uri`, and `RouteData` collection. - **[`ITopicRepository`](Repositories/ITopicRepository.cs)**: Defines the data access layer interface, with `Load()`, `Save()`, `Delete()`, and `Move()` methods. - **[`ITopicMappingService`](Mapping)**: Defines the interface for a service that can convert a `Topic` class into any arbitrary data transfer object based on predetermined conventions. +- **[`ITypeLookupService`](ITypeLookupService.cs)**: Defines the interface that can identify `Type` objects based on a `GetType(typeName)` query. Used by e.g. `ITopicMappingService` to find corresponding `TopicViewModel` classes to map to. ## Implementations - **[`TopicMappingService`](Mapping)**: A default implementation of the `ITopicMappingService`, with built-in conventions that should address that majority of mapping requirements. This also includes a number of attributes for annotating view models with hints that the `TopicMappingService` can use in populating target objects. +- **[`StaticTypeLookupService`](StaticTypeLookupService.cs)**: A basic implementation of the `ITypeLookupService` interface that allows types to be explicitly registered; useful when a small number of types are expected. + - **[`DynamicTypeLookupService`](Reflection\DynamicTypeLookupService.cs)**: A reflection-based implementation of the `ITypeLookupService` interface that looks up types from all loaded assemblies based on a `Func` delegate. + - **[`TopicLookupService`](Reflection\TopicLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that derive from `Topic`; this is the default implementation for `TopicFactory`. + - **[`TopicViewModeLookupService`](Reflection\TopicViewModeLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that end with `TopicViewModel`; this is useful for the `TopicMappingService`. ## Extension Methods -- **[`Querying`](Querying/Topic.cs)**: The `Topic` class exposes optional extension methods for querying a topic (and its descendants) based on attribute values. +- **[`Querying`](Querying/Topic.cs)**: The `Topic` class exposes optional extension methods for querying a topic (and its descendants) based on attribute values. This includes the useful `Topic.FindAll(Func)` method for querying an entire topic graph and returning topics validated by a predicate. ## Collections In addition to the above key classes, the `Ignia.Topics` assembly contains a number of specialized collections. These include: From 65d9e0e6bbe2972da8cdacb3ead208bbd39e1a86 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:14:18 -0700 Subject: [PATCH 091/149] Added XmlDoc for initializer --- Ignia.Topics.Tests/TopicTest.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index 1d0d62e6..891a0df7 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -20,10 +20,15 @@ namespace Ignia.Topics.Tests { [TestClass] public class TopicTest { + /*========================================================================================================================== + | TEST: INITIALIZE ASSEMBLY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Runs before any tests to provide basic initialization. Used to ensure uses the + /// for performance reasons. + /// [AssemblyInitialize] - public static void AssemblyInit(TestContext context) { - TopicFactory.TypeLookupService = new FakeTopicLookupService(); - } + public static void AssemblyInit(TestContext context) => TopicFactory.TypeLookupService = new FakeTopicLookupService(); /*========================================================================================================================== | TEST: CREATE From 872177fa92e69239ac40b648f07c66597b98ff3c Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:14:31 -0700 Subject: [PATCH 092/149] Removed unnecessary code contract --- Ignia.Topics/StaticTypeLookupService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Ignia.Topics/StaticTypeLookupService.cs b/Ignia.Topics/StaticTypeLookupService.cs index 08dd96f0..cbe9d954 100644 --- a/Ignia.Topics/StaticTypeLookupService.cs +++ b/Ignia.Topics/StaticTypeLookupService.cs @@ -82,7 +82,6 @@ public Type GetType(string typeName) { /*---------------------------------------------------------------------------------------------------------------------- | Validate contracts \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Requires(!String.IsNullOrWhiteSpace(typeName)); Contract.Ensures(Contract.Result() != null); /*---------------------------------------------------------------------------------------------------------------------- From c074767e797b4faeadd40c963b1d87b9c4d27841 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:29:29 -0700 Subject: [PATCH 093/149] Updated to include `ITypeLookupService` --- Ignia.Topics.Web.Mvc/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/README.md b/Ignia.Topics.Web.Mvc/README.md index ee3c7ab1..50c3003c 100644 --- a/Ignia.Topics.Web.Mvc/README.md +++ b/Ignia.Topics.Web.Mvc/README.md @@ -89,6 +89,7 @@ As OnTopic relies on constructor injection, the application must be configured i var connectionString = ConfigurationManager.ConnectionStrings["OnTopic"].ConnectionString; var sqlTopicRepository = new SqlTopicRepository(connectionString); var cachedTopicRepository = new CachedTopicRepository(sqlTopicRepository); +var topicViewModelLookupService = new TopicViewModelLookupService(); var mvcTopicRoutingService = new MvcTopicRoutingService( cachedTopicRepository, @@ -96,12 +97,12 @@ var mvcTopicRoutingService = new MvcTopicRoutingService( requestContext.RouteData ); -var topicMappingService = new TopicMappingService(cachedTopicRepository); +var topicMappingService = new TopicMappingService(cachedTopicRepository, topicViewModelLookupService); switch (controllerType.Name) { case nameof(TopicController): - return new TopicController(_topicRepository, mvcTopicRoutingService, _topicMappingService); + return new TopicController(_topicRepository, mvcTopicRoutingService, topicMappingService); case default: return base.GetControllerInstance(requestContext, controllerType); From 2587c859374912d952fd6d1796cb3afd328725e4 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:33:03 -0700 Subject: [PATCH 094/149] Fixed alignment Had copied and pasted from a backup, accidentally removing alignment of variables. Fixed. --- Ignia.Topics/Mapping/TopicMappingService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index adc0943d..67f59eee 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -28,13 +28,13 @@ public class TopicMappingService : ITopicMappingService { /*========================================================================================================================== | STATIC VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - static readonly TypeCollection _typeCache = new TypeCollection(); + static readonly TypeCollection _typeCache = new TypeCollection(); /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - readonly ITopicRepository _topicRepository = null; - readonly ITypeLookupService _typeLookupService = null; + readonly ITopicRepository _topicRepository = null; + readonly ITypeLookupService _typeLookupService = null; /*========================================================================================================================== | CONSTRUCTOR @@ -254,8 +254,8 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Establish per-property variables \-----------------------------------------------------------------------------------------------------------------------*/ - var configuration = new PropertyConfiguration(property); - var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); + var configuration = new PropertyConfiguration(property); + var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Assign default value From 8f7b1d9fc3e527eb1ea0d1722a6edc375c5559a7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:34:07 -0700 Subject: [PATCH 095/149] Removed unnecessary contract Since this is an interface method, Code Contracts doesn't recognize it. --- Ignia.Topics/StaticTypeLookupService.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Ignia.Topics/StaticTypeLookupService.cs b/Ignia.Topics/StaticTypeLookupService.cs index cbe9d954..d1fb3eb3 100644 --- a/Ignia.Topics/StaticTypeLookupService.cs +++ b/Ignia.Topics/StaticTypeLookupService.cs @@ -79,11 +79,6 @@ public StaticTypeLookupService(IEnumerable types = null, Type defaultType /// public Type GetType(string typeName) { - /*---------------------------------------------------------------------------------------------------------------------- - | Validate contracts - \---------------------------------------------------------------------------------------------------------------------*/ - Contract.Ensures(Contract.Result() != null); - /*---------------------------------------------------------------------------------------------------------------------- | Return cached entry \---------------------------------------------------------------------------------------------------------------------*/ From fcbd2e8af7b01af49f05e8c370cec0b51a5b73ea Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:35:42 -0700 Subject: [PATCH 096/149] Removed unused `using` statements --- Ignia.Topics/StaticTypeLookupService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Ignia.Topics/StaticTypeLookupService.cs b/Ignia.Topics/StaticTypeLookupService.cs index d1fb3eb3..cd04c576 100644 --- a/Ignia.Topics/StaticTypeLookupService.cs +++ b/Ignia.Topics/StaticTypeLookupService.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; -using System.Linq; using System.Reflection; namespace Ignia.Topics { From bdbd4e3a119c09c173fbc1a71c1b48ed8e2e3528 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 16:43:53 -0700 Subject: [PATCH 097/149] Fixed `topicRepository` reference Code still referenced `_topicRepository`, which is typically correct in a real implementation, but not in this simplified example. Also moved `topicMappingService` to be next to the other definitions. --- Ignia.Topics.Web.Mvc/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/README.md b/Ignia.Topics.Web.Mvc/README.md index 50c3003c..b1982524 100644 --- a/Ignia.Topics.Web.Mvc/README.md +++ b/Ignia.Topics.Web.Mvc/README.md @@ -90,6 +90,7 @@ var connectionString = ConfigurationManager.ConnectionStrings["OnTopi var sqlTopicRepository = new SqlTopicRepository(connectionString); var cachedTopicRepository = new CachedTopicRepository(sqlTopicRepository); var topicViewModelLookupService = new TopicViewModelLookupService(); +var topicMappingService = new TopicMappingService(cachedTopicRepository, topicViewModelLookupService); var mvcTopicRoutingService = new MvcTopicRoutingService( cachedTopicRepository, @@ -97,12 +98,10 @@ var mvcTopicRoutingService = new MvcTopicRoutingService( requestContext.RouteData ); -var topicMappingService = new TopicMappingService(cachedTopicRepository, topicViewModelLookupService); - switch (controllerType.Name) { case nameof(TopicController): - return new TopicController(_topicRepository, mvcTopicRoutingService, topicMappingService); + return new TopicController(sqlTopicRepository, mvcTopicRoutingService, topicMappingService); case default: return base.GetControllerInstance(requestContext, controllerType); From 8bc1f201e5fb3ecc520cd9b338e2693ae2a8a289 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Sun, 6 May 2018 20:37:52 -0700 Subject: [PATCH 098/149] Changed `IsCurrentRelationship()` to `GetRelationship()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn't actually reduce the lines of code, but it does make the lines of code more readable by putting more of the logic in the local function. The only reason it doesn't reduce the lines of code is because we're placing each function call on multiple lines for readability. This does help reduce the amount of complex `(… && …) || (… && …)` style expressions. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 63 +++++++++---------- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 38 insertions(+), 39 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 04bbb99d..30be4790 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 7ecfdc7a..09a50525 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1750.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 36f1b26f..99799e23 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1760.0")] +[assembly: AssemblyFileVersion("3.5.1765.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 0e02200a..ce61b871 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1749.0")] +[assembly: AssemblyFileVersion("3.5.1754.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index e2287ee2..732c8335 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1749.0")] +[assembly: AssemblyFileVersion("3.5.1754.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 3640fa43..0a0bf3c4 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1750.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 67f59eee..8879dff0 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -455,45 +455,45 @@ protected IList GetSourceCollection(Topic source, Relationships relations | Handle children \-----------------------------------------------------------------------------------------------------------------------*/ if ( - (configuration.RelationshipKey.Equals("Children") || configuration.RelationshipType.Equals(RelationshipType.Children)) && - IsCurrentRelationship(RelationshipType.Children) + configuration.RelationshipKey.Equals("Children") || + configuration.RelationshipType.Equals(RelationshipType.Children) ) { - listSource = source.Children.ToList(); + listSource = GetRelationship(RelationshipType.Children, s => true, () => source.Children.ToList()); } /*------------------------------------------------------------------------------------------------------------------------ | Handle (outgoing) relationships \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.Relationship)) { - if (source.Relationships.Contains(configuration.RelationshipKey)) { - listSource = source.Relationships.GetTopics(configuration.RelationshipKey); - } - } + listSource = GetRelationship( + RelationshipType.Relationship, + source.Relationships.Contains, + () => source.Relationships.GetTopics(configuration.RelationshipKey) + ); /*------------------------------------------------------------------------------------------------------------------------ | Handle nested topics, or children corresponding to the property name \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.NestedTopics)) { - if (source.Children.Contains(configuration.RelationshipKey)) { - listSource = source.Children[configuration.RelationshipKey].Children.ToList(); - } - } + listSource = GetRelationship( + RelationshipType.NestedTopics, + source.Children.Contains, + () => source.Children[configuration.RelationshipKey].Children.ToList() + ); /*------------------------------------------------------------------------------------------------------------------------ | Handle (incoming) relationships \-----------------------------------------------------------------------------------------------------------------------*/ - if (IsCurrentRelationship(RelationshipType.IncomingRelationship)) { - if (source.IncomingRelationships.Contains(configuration.RelationshipKey)) { - listSource = source.IncomingRelationships.GetTopics(configuration.RelationshipKey); - } - } + listSource = GetRelationship( + RelationshipType.IncomingRelationship, + source.IncomingRelationships.Contains, + () => source.IncomingRelationships.GetTopics(configuration.RelationshipKey) + ); /*------------------------------------------------------------------------------------------------------------------------ | Handle Metadata relationship \-----------------------------------------------------------------------------------------------------------------------*/ if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(configuration.MetadataKey)) { - listSource = _topicRepository.Load("Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList")? - .Children.ToList(); + var metadataKey = "Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList"; + listSource = _topicRepository.Load(metadataKey)?.Children.ToList(); } /*------------------------------------------------------------------------------------------------------------------------ @@ -512,17 +512,16 @@ protected IList GetSourceCollection(Topic source, Relationships relations /*------------------------------------------------------------------------------------------------------------------------ | Provide local function for evaluating current relationship \-----------------------------------------------------------------------------------------------------------------------*/ - bool IsCurrentRelationship(RelationshipType targetRelationshipType) => ( - listSource.Count == 0 && - ( - configuration.RelationshipType.Equals(RelationshipType.Any) || - configuration.RelationshipType.Equals(targetRelationshipType) - ) && - ( - RelationshipMap.Mappings[targetRelationshipType].Equals(Relationships.None) || - relationships.HasFlag(RelationshipMap.Mappings[targetRelationshipType]) - ) - ); + IList GetRelationship(RelationshipType relationship, Func contains, Func> getTopics) { + var targetRelationships = RelationshipMap.Mappings[relationship]; + var relationshipType = configuration.RelationshipType; + var preconditionsMet = + listSource.Count == 0 && + (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(relationship)) && + (targetRelationships.Equals(Relationships.None) || relationships.HasFlag(targetRelationships)) && + contains(configuration.RelationshipKey); + return preconditionsMet? getTopics() : listSource; + } } @@ -633,4 +632,4 @@ protected IList PopulateChildTopics(Topic source, IList targetList } } //Class -} //Namespace +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index a4a76c62..f87fbded 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 1bdea849a22409a757e053bbd72b2b3d7062217b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 10:54:24 -0700 Subject: [PATCH 099/149] Streamlined syntax for brevity Implemented a number of syntactical shortcuts (such as `.ForEach()` instead of `foreach()`) in order to reduce length of code and improve readability. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 71 +++++-------------- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 25 insertions(+), 60 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 30be4790..ab6c4f1b 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1757.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 09a50525..187b945f 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1750.0")] +[assembly: AssemblyFileVersion("3.5.1751.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 99799e23..46fd64be 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1766.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index ce61b871..c550339b 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1754.0")] +[assembly: AssemblyFileVersion("3.5.1755.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 732c8335..def24c1f 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1754.0")] +[assembly: AssemblyFileVersion("3.5.1755.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 0a0bf3c4..59134f0f 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1750.0")] +[assembly: AssemblyFileVersion("3.5.1751.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 8879dff0..2b86ca5f 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -114,19 +114,13 @@ private object Map(Topic topic, Relationships relationships, Dictionary with properties appropriately mapped. /// public T Map(Topic topic, Relationships relationships = Relationships.All) where T : class, new() { - if (typeof(Topic).IsAssignableFrom(typeof(T))) { return topic as T; } - var target = new T(); - return (T)Map(topic, target, relationships); - + return (T)Map(topic, new T(), relationships); } /*========================================================================================================================== @@ -209,11 +200,7 @@ private object Map(Topic topic, object target, Relationships relationships, Dict if (cache.ContainsKey(topic.Id)) { return cache[topic.Id]; } - - /*------------------------------------------------------------------------------------------------------------------------ - | Cache results - \-----------------------------------------------------------------------------------------------------------------------*/ - if (topic.Id > 0 && !cache.ContainsKey(topic.Id)) { + else if (topic.Id > 0) { cache.Add(topic.Id, target); } @@ -258,36 +245,24 @@ Dictionary cache var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); /*------------------------------------------------------------------------------------------------------------------------ - | Attributes: Assign default value + | Assign default value \-----------------------------------------------------------------------------------------------------------------------*/ if (configuration.DefaultValue != null) { property.SetValue(target, configuration.DefaultValue); } /*------------------------------------------------------------------------------------------------------------------------ - | Property: Scalar Value + | Handle by type, attribute \-----------------------------------------------------------------------------------------------------------------------*/ if (_typeCache.HasSettableProperty(target.GetType(), property.Name)) { SetScalarValue(source, target, configuration); } - - /*------------------------------------------------------------------------------------------------------------------------ - | Property: Collections - \-----------------------------------------------------------------------------------------------------------------------*/ else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { SetCollectionValue(source, target, relationships, configuration, cache); } - - /*------------------------------------------------------------------------------------------------------------------------ - | Property: Parent - \-----------------------------------------------------------------------------------------------------------------------*/ else if (configuration.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { SetTopicReference(source.Parent, target, configuration, cache); } - - /*------------------------------------------------------------------------------------------------------------------------ - | Property: Topic Reference - \-----------------------------------------------------------------------------------------------------------------------*/ else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); SetTopicReference(topicReference, target, configuration, cache); @@ -393,9 +368,7 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Escape clause if preconditions are not met \-----------------------------------------------------------------------------------------------------------------------*/ - if (!typeof(IList).IsAssignableFrom(configuration.Property.PropertyType)) { - return; - } + if (!typeof(IList).IsAssignableFrom(configuration.Property.PropertyType)) return; /*------------------------------------------------------------------------------------------------------------------------ | Ensure target list is created @@ -414,9 +387,7 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Validate that source collection was identified \-----------------------------------------------------------------------------------------------------------------------*/ - if (sourceList == null) { - return; - } + if (sourceList == null) return; /*------------------------------------------------------------------------------------------------------------------------ | Map the topics from the source collection, and add them to the target collection @@ -449,15 +420,14 @@ protected IList GetSourceCollection(Topic source, Relationships relations /*------------------------------------------------------------------------------------------------------------------------ | Establish source collection to store topics to be mapped \-----------------------------------------------------------------------------------------------------------------------*/ - IList listSource = new Topic[] { }; + var listSource = (IList)new Topic[] { }; + var relationshipKey = configuration.RelationshipKey; + var relationshipType = configuration.RelationshipType; /*------------------------------------------------------------------------------------------------------------------------ | Handle children \-----------------------------------------------------------------------------------------------------------------------*/ - if ( - configuration.RelationshipKey.Equals("Children") || - configuration.RelationshipType.Equals(RelationshipType.Children) - ) { + if (relationshipKey.Equals("Children") || relationshipType.Equals(RelationshipType.Children)) { listSource = GetRelationship(RelationshipType.Children, s => true, () => source.Children.ToList()); } @@ -467,7 +437,7 @@ protected IList GetSourceCollection(Topic source, Relationships relations listSource = GetRelationship( RelationshipType.Relationship, source.Relationships.Contains, - () => source.Relationships.GetTopics(configuration.RelationshipKey) + () => source.Relationships.GetTopics(relationshipKey) ); /*------------------------------------------------------------------------------------------------------------------------ @@ -476,7 +446,7 @@ protected IList GetSourceCollection(Topic source, Relationships relations listSource = GetRelationship( RelationshipType.NestedTopics, source.Children.Contains, - () => source.Children[configuration.RelationshipKey].Children.ToList() + () => source.Children[relationshipKey].Children ); /*------------------------------------------------------------------------------------------------------------------------ @@ -485,7 +455,7 @@ protected IList GetSourceCollection(Topic source, Relationships relations listSource = GetRelationship( RelationshipType.IncomingRelationship, source.IncomingRelationships.Contains, - () => source.IncomingRelationships.GetTopics(configuration.RelationshipKey) + () => source.IncomingRelationships.GetTopics(relationshipKey) ); /*------------------------------------------------------------------------------------------------------------------------ @@ -496,14 +466,12 @@ protected IList GetSourceCollection(Topic source, Relationships relations listSource = _topicRepository.Load(metadataKey)?.Children.ToList(); } - /*------------------------------------------------------------------------------------------------------------------------ + /*------------------------------------------------------------------------------------------------------------------------ | Handle flattening of children \-----------------------------------------------------------------------------------------------------------------------*/ if (configuration.FlattenChildren) { var flattenedList = new List(); - foreach (var childTopic in listSource) { - PopulateChildTopics(childTopic, flattenedList); - } + listSource.ToList().ForEach(t => PopulateChildTopics(t, flattenedList)); listSource = flattenedList; } @@ -514,7 +482,6 @@ protected IList GetSourceCollection(Topic source, Relationships relations \-----------------------------------------------------------------------------------------------------------------------*/ IList GetRelationship(RelationshipType relationship, Func contains, Func> getTopics) { var targetRelationships = RelationshipMap.Mappings[relationship]; - var relationshipType = configuration.RelationshipType; var preconditionsMet = listSource.Count == 0 && (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(relationship)) && @@ -625,9 +592,7 @@ protected IList PopulateChildTopics(Topic source, IList targetList if (source.IsDisabled) return targetList; if (source.ContentType.Equals("List") && !includeNestedTopics) return targetList; targetList.Add(source); - foreach (var child in source.Children) { - PopulateChildTopics(child, targetList); - } + source.Children.ToList().ForEach(t => PopulateChildTopics(t, targetList)); return targetList; } diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f87fbded..f14953d8 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1757.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From e59dd1bd28e107b167e1275f97b02d973e0472a3 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 10:59:37 -0700 Subject: [PATCH 100/149] Updated libraries to .NET Framework 4.7 This will provide support for latest features. This will mandate updating projects to also use .NET Framework 4.7, though this ought not be a problem for our clients and web projects. --- Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj | 3 ++- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- .../Ignia.Topics.Data.Sql.Database.sqlproj | 3 ++- Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 3 ++- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Ignia.Topics.Web.csproj | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Ignia.Topics.csproj | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 15 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj index aae37bc2..6629e0dd 100644 --- a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj +++ b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj @@ -10,7 +10,7 @@ Properties Ignia.Topics.Data.Caching Ignia.Topics.Data.Caching - v4.5 + v4.7 512 @@ -45,6 +45,7 @@ True Full 0 + true diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index ab6c4f1b..2594905c 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1758.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql.Database/Ignia.Topics.Data.Sql.Database.sqlproj b/Ignia.Topics.Data.Sql.Database/Ignia.Topics.Data.Sql.Database.sqlproj index 6430b703..bafd59c9 100644 --- a/Ignia.Topics.Data.Sql.Database/Ignia.Topics.Data.Sql.Database.sqlproj +++ b/Ignia.Topics.Data.Sql.Database/Ignia.Topics.Data.Sql.Database.sqlproj @@ -17,7 +17,7 @@ 1033,CI BySchemaAndSchemaType True - v4.5 + v4.7 CS Properties False @@ -27,6 +27,7 @@ PRIMARY False OnTopic + bin\Release\ diff --git a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj index bad0812d..a1f5f6df 100644 --- a/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj +++ b/Ignia.Topics.Data.Sql/Ignia.Topics.Data.Sql.csproj @@ -10,7 +10,7 @@ Properties Ignia.Topics.Data.Sql Ignia.Topics.Data.Sql - v4.5 + v4.7 512 1 diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 187b945f..8047f953 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1752.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index 91799496..41e8babe 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -9,7 +9,7 @@ Properties Ignia.Topics.Tests Ignia.Topics.Tests - v4.5 + v4.7 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 @@ -19,6 +19,7 @@ UnitTest + true diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 46fd64be..c03b8721 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1766.0")] +[assembly: AssemblyFileVersion("3.5.1767.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index 3a8b2bfa..2b6946a0 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -9,7 +9,7 @@ Properties Ignia.Topics.Models Ignia.Topics.ViewModels - v4.5 + v4.7 512 True diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index c550339b..2e8bd24c 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1755.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index e826f4dd..ec2f7bdd 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -10,7 +10,7 @@ Properties Ignia.Topics.Web.Mvc Ignia.Topics.Web.Mvc - v4.5 + v4.7 512 diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index def24c1f..07323d98 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1755.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Ignia.Topics.Web.csproj b/Ignia.Topics.Web/Ignia.Topics.Web.csproj index 07cbe6ab..ce9a9af4 100644 --- a/Ignia.Topics.Web/Ignia.Topics.Web.csproj +++ b/Ignia.Topics.Web/Ignia.Topics.Web.csproj @@ -10,7 +10,7 @@ Properties Ignia.Topics.Web Ignia.Topics.Web - v4.5 + v4.7 512 1 diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 59134f0f..b0ec5975 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1752.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 63edf81d..b4322565 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -10,7 +10,7 @@ Properties Ignia.Topics Ignia.Topics - v4.5 + v4.7 512 diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f14953d8..45b3d722 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1758.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From c5300018ede300c09424724b7bc71d505b622536 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:28:33 -0700 Subject: [PATCH 101/149] Baked `Children` logic into `PropertyConfiguration` If a collection is named `Children` then it should be assumed to be of type `RelationshipType.Children` unless explicitly set otherwise via a `[Relationship()]` attribute. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/PropertyConfiguration.cs | 4 ++++ Ignia.Topics/Mapping/TopicMappingService.cs | 9 ++++++--- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 9 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 2594905c..b29393d9 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 8047f953..4eb5cf7e 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1752.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index c03b8721..020e2cc9 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1767.0")] +[assembly: AssemblyFileVersion("3.5.1772.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 2e8bd24c..6f440993 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1761.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 07323d98..74b1afe2 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1761.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index b0ec5975..f8975dfb 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1752.0")] +[assembly: AssemblyFileVersion("3.5.1756.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 94b65bfb..2ab29505 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -87,6 +87,10 @@ public PropertyConfiguration(PropertyInfo property) { } ); + if (RelationshipType.Equals(RelationshipType.Any) && RelationshipKey.Equals("Children")) { + RelationshipType = RelationshipType.Children; + } + /*------------------------------------------------------------------------------------------------------------------------ | Attributes: Set attribute filters \-----------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 2b86ca5f..8f58218f 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -427,9 +427,11 @@ protected IList GetSourceCollection(Topic source, Relationships relations /*------------------------------------------------------------------------------------------------------------------------ | Handle children \-----------------------------------------------------------------------------------------------------------------------*/ - if (relationshipKey.Equals("Children") || relationshipType.Equals(RelationshipType.Children)) { - listSource = GetRelationship(RelationshipType.Children, s => true, () => source.Children.ToList()); - } + listSource = GetRelationship( + RelationshipType.Children, + s => true, + () => source.Children.ToList() + ); /*------------------------------------------------------------------------------------------------------------------------ | Handle (outgoing) relationships @@ -485,6 +487,7 @@ IList GetRelationship(RelationshipType relationship, Func c var preconditionsMet = listSource.Count == 0 && (relationshipType.Equals(RelationshipType.Any) || relationshipType.Equals(relationship)) && + (relationshipType.Equals(RelationshipType.Children) || !relationship.Equals(RelationshipType.Children)) && (targetRelationships.Equals(Relationships.None) || relationships.HasFlag(targetRelationships)) && contains(configuration.RelationshipKey); return preconditionsMet? getTopics() : listSource; diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 45b3d722..1d51e56a 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 2bb146ea56d11c23586f4632acaa14d946d25fa9 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:43:11 -0700 Subject: [PATCH 102/149] Prefixed dynamic lookup services with `Dynamic` Using reflection adds some overhead and comes with some risks (e.g., of conflicts between classes). Given that, implementors should be explicitly aware that they are utilizing a dynamic class. These are in the `Reflection` namespace for this reason, but adding `Dynamic` makes this even clearer. --- Ignia.Topics/README.md | 4 ++-- ...TopicLookupService.cs => DynamicTopicLookupService.cs} | 8 ++++---- ...upService.cs => DynamicTopicViewModelLookupService.cs} | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) rename Ignia.Topics/Reflection/{TopicLookupService.cs => DynamicTopicLookupService.cs} (78%) rename Ignia.Topics/Reflection/{TopicViewModelLookupService.cs => DynamicTopicViewModelLookupService.cs} (77%) diff --git a/Ignia.Topics/README.md b/Ignia.Topics/README.md index d52b81bf..b0b9227c 100644 --- a/Ignia.Topics/README.md +++ b/Ignia.Topics/README.md @@ -21,8 +21,8 @@ Out of the box, the OnTopic library contains two specially derived topics for su - **[`TopicMappingService`](Mapping)**: A default implementation of the `ITopicMappingService`, with built-in conventions that should address that majority of mapping requirements. This also includes a number of attributes for annotating view models with hints that the `TopicMappingService` can use in populating target objects. - **[`StaticTypeLookupService`](StaticTypeLookupService.cs)**: A basic implementation of the `ITypeLookupService` interface that allows types to be explicitly registered; useful when a small number of types are expected. - **[`DynamicTypeLookupService`](Reflection\DynamicTypeLookupService.cs)**: A reflection-based implementation of the `ITypeLookupService` interface that looks up types from all loaded assemblies based on a `Func` delegate. - - **[`TopicLookupService`](Reflection\TopicLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that derive from `Topic`; this is the default implementation for `TopicFactory`. - - **[`TopicViewModeLookupService`](Reflection\TopicViewModeLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that end with `TopicViewModel`; this is useful for the `TopicMappingService`. + - **[`DynamicTopicLookupService`](Reflection\DynamicTopicLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that derive from `Topic`; this is the default implementation for `TopicFactory`. + - **[`DynamicTopicViewModeLookupService`](Reflection\DynamicTopicViewModeLookupService.cs)**: A version of `DynamicTypeLookupService` that returns all classes that end with `TopicViewModel`; this is useful for the `TopicMappingService`. ## Extension Methods - **[`Querying`](Querying/Topic.cs)**: The `Topic` class exposes optional extension methods for querying a topic (and its descendants) based on attribute values. This includes the useful `Topic.FindAll(Func)` method for querying an entire topic graph and returning topics validated by a predicate. diff --git a/Ignia.Topics/Reflection/TopicLookupService.cs b/Ignia.Topics/Reflection/DynamicTopicLookupService.cs similarity index 78% rename from Ignia.Topics/Reflection/TopicLookupService.cs rename to Ignia.Topics/Reflection/DynamicTopicLookupService.cs index 065efda1..e56e0727 100644 --- a/Ignia.Topics/Reflection/TopicLookupService.cs +++ b/Ignia.Topics/Reflection/DynamicTopicLookupService.cs @@ -11,18 +11,18 @@ namespace Ignia.Topics.Reflection { | CLASS: TOPIC LOOKUP SERVICE \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// The will search all assemblies for s that derive from will search all assemblies for s that derive from . /// - public class TopicLookupService : DynamicTypeLookupService { + public class DynamicTopicLookupService : DynamicTypeLookupService { /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a new instance of a . + /// Establishes a new instance of a . /// - internal TopicLookupService() : base( + internal DynamicTopicLookupService() : base( t => typeof(Topic).IsAssignableFrom(t), typeof(Topic) ) {} diff --git a/Ignia.Topics/Reflection/TopicViewModelLookupService.cs b/Ignia.Topics/Reflection/DynamicTopicViewModelLookupService.cs similarity index 77% rename from Ignia.Topics/Reflection/TopicViewModelLookupService.cs rename to Ignia.Topics/Reflection/DynamicTopicViewModelLookupService.cs index 84e44860..64d9c390 100644 --- a/Ignia.Topics/Reflection/TopicViewModelLookupService.cs +++ b/Ignia.Topics/Reflection/DynamicTopicViewModelLookupService.cs @@ -11,18 +11,18 @@ namespace Ignia.Topics.Reflection { | CLASS: TOPIC VIEW MODEL LOOKUP SERVICE \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// The will search all assemblies for s that end with + /// The will search all assemblies for s that end with /// "TopicViewModel" /// - public class TopicViewModelLookupService : DynamicTypeLookupService { + public class DynamicTopicViewModelLookupService : DynamicTypeLookupService { /*========================================================================================================================== | CONSTRUCTOR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a new instance of a . + /// Establishes a new instance of a . /// - internal TopicViewModelLookupService() : base( + internal DynamicTopicViewModelLookupService() : base( t => t.Name.EndsWith("TopicViewModel"), typeof(object) ) {} From 9875416d0a2b50e36472ae850688ad2bb356c42d Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:43:27 -0700 Subject: [PATCH 103/149] Fixed flowerbox width --- Ignia.Topics/TopicFactory.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index 5a2c4002..b7400924 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -70,28 +70,28 @@ public static class TopicFactory { /// public static Topic Create(string key, string contentType, Topic parent = null) { - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Validate contracts - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(!String.IsNullOrWhiteSpace(key)); Contract.Requires(!String.IsNullOrWhiteSpace(contentType)); Contract.Ensures(Contract.Result() != null); TopicFactory.ValidateKey(key); TopicFactory.ValidateKey(contentType); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Determine target type - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ var targetType = TypeLookupService.GetType(contentType); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Identify the appropriate topic - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ var topic = (Topic)Activator.CreateInstance(targetType, key, contentType, parent, -1); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Return the topic - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ return topic; } @@ -116,9 +116,9 @@ public static Topic Create(string key, string contentType, Topic parent = null) /// A strongly-typed instance of the class based on the target content type. public static Topic Create(string key, string contentType, int id, Topic parent = null) { - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Validate input - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ Contract.Requires(!String.IsNullOrWhiteSpace(key)); Contract.Requires(!String.IsNullOrWhiteSpace(contentType)); Contract.Requires(id > 0); @@ -126,19 +126,19 @@ public static Topic Create(string key, string contentType, int id, Topic parent TopicFactory.ValidateKey(key); TopicFactory.ValidateKey(contentType); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Determine target type - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ var targetType = TypeLookupService.GetType(contentType); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Identify the appropriate topic \---------------------------------------------------------------------------------------------------------------------*/ var topic = (Topic)Activator.CreateInstance(targetType, key, contentType, parent, id); - /*---------------------------------------------------------------------------------------------------------------------- + /*------------------------------------------------------------------------------------------------------------------------ | Return object - \---------------------------------------------------------------------------------------------------------------------*/ + \-----------------------------------------------------------------------------------------------------------------------*/ return topic; } From e94a1e161c4acbd65621e5aa9fc934164bbb59c7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:50:27 -0700 Subject: [PATCH 104/149] Introduced `DefaultTopicLookupService` This is a custom configuration of the `StaticTypeLookupService` that ensures that the basic classes (such as `ContentTypeDescriptor`) are accounted for. These are necessary for backend support and may not be obvious if an unfamiliar developer attempts to establish a new `StaticTypeLookupService` from scratch. As such, this provides a less error-prone default implementation. --- Ignia.Topics/DefaultTopicLookupService.cs | 47 +++++++++++++++++++++++ Ignia.Topics/Ignia.Topics.csproj | 5 ++- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Ignia.Topics/DefaultTopicLookupService.cs diff --git a/Ignia.Topics/DefaultTopicLookupService.cs b/Ignia.Topics/DefaultTopicLookupService.cs new file mode 100644 index 00000000..183363ee --- /dev/null +++ b/Ignia.Topics/DefaultTopicLookupService.cs @@ -0,0 +1,47 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Reflection; + +namespace Ignia.Topics { + + /*============================================================================================================================ + | CLASS: TYPE INDEX + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The can be configured to provide a lookup of . + /// + public class DefaultTopicLookupService: StaticTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a . Optionally accepts a list of + /// instances and a default value. + /// + /// + /// Any instances submitted via should be unique by ; if they are not, they will be removed. + /// + /// The list of instances to expose as part of this service. + /// The default type to return if no match can be found. Defaults to object. + public DefaultTopicLookupService(IEnumerable types = null, Type defaultType = null) : + base(types, defaultType?? typeof(Topic)) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Ensure editor types are accounted for + \-----------------------------------------------------------------------------------------------------------------------*/ + if (!Contains("ContentTypeDescriptor")) Add(typeof(ContentTypeDescriptor)); + if (!Contains("AttributeDescriptor")) Add(typeof(AttributeDescriptor)); + + } + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index b4322565..433fd815 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -158,12 +158,13 @@ - - + + + From a7ead4d1b13b5fd0b863e7685dbf4c2b3ada4191 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:54:41 -0700 Subject: [PATCH 105/149] Configured `TopicFactory` to use `DefaultTopicLookupService` Previously, the `TopicFactory` defaulted to using the `DynamicTypeLookupService`. This would have been valuable when derived Topics were the primary point of extensibility within OnTopic. With the `ITopicMappingService`, however, there is rarely a need to extend `Topic` outside of the core library (which is accounted for by `DefaultTopicLookupService`). The `DynamicTopicLookupService` is still available for applications that require it, but it is no longer the default implementation. Customers with one or two custom types are advised to simply extend `DefaultTopicLookupService`. Customers with extensive use of derived types should configure `TopicFactory` to use `DynamicTopicLookupService`. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/TopicTest.cs | 10 ---------- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/TopicFactory.cs | 6 +----- 9 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index b29393d9..1534a871 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1764.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 4eb5cf7e..d9b9b431 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1757.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 020e2cc9..b652f65f 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1772.0")] +[assembly: AssemblyFileVersion("3.5.1773.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index 891a0df7..9d9cb4ca 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -20,16 +20,6 @@ namespace Ignia.Topics.Tests { [TestClass] public class TopicTest { - /*========================================================================================================================== - | TEST: INITIALIZE ASSEMBLY - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Runs before any tests to provide basic initialization. Used to ensure uses the - /// for performance reasons. - /// - [AssemblyInitialize] - public static void AssemblyInit(TestContext context) => TopicFactory.TypeLookupService = new FakeTopicLookupService(); - /*========================================================================================================================== | TEST: CREATE \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 6f440993..7ea92ca5 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1761.0")] +[assembly: AssemblyFileVersion("3.5.1762.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 74b1afe2..31dc9722 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1761.0")] +[assembly: AssemblyFileVersion("3.5.1762.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index f8975dfb..2e101c23 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1757.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 1d51e56a..ea37042c 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1764.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index b7400924..91016a18 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -27,11 +27,7 @@ public static class TopicFactory { /// /// Establishes static variables for the . /// - public static ITypeLookupService TypeLookupService { get; set; } = - new DynamicTypeLookupService( - t => typeof(Topic).IsAssignableFrom(t), - typeof(Topic) - ); + public static ITypeLookupService TypeLookupService { get; set; } = new DefaultTopicLookupService(); /*========================================================================================================================== | METHOD: CREATE From d5b45eb58e37e5e80543a8549485248b421b11be Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 11:58:10 -0700 Subject: [PATCH 106/149] Removed zero-length arrays Replaced with `Array.Empty()`, which Code Analysis suggests to be faster. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/TopicViewResult.cs | 12 ++++++------ Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Reflection/TypeCollection.cs | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 1534a871..c20f23d3 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1764.0")] +[assembly: AssemblyFileVersion("3.5.1765.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index d9b9b431..1ae3b66c 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1758.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 7ea92ca5..2e1fe036 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 31dc9722..1def6416 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web.Mvc/TopicViewResult.cs b/Ignia.Topics.Web.Mvc/TopicViewResult.cs index c75f28cc..245a61fc 100644 --- a/Ignia.Topics.Web.Mvc/TopicViewResult.cs +++ b/Ignia.Topics.Web.Mvc/TopicViewResult.cs @@ -77,7 +77,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { var contentType = _contentType; var viewEngine = ViewEngines.Engines; var requestContext = context.HttpContext.Request; - var view = new ViewEngineResult(new string[] { }); + var view = new ViewEngineResult(Array.Empty()); var searchedPaths = new List(); /*------------------------------------------------------------------------------------------------------------------------ @@ -89,7 +89,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { var queryStringValue = requestContext.QueryString["View"]; if (queryStringValue != null) { view = viewEngine.FindView(context, queryStringValue, MasterName); - searchedPaths = searchedPaths.Union(view.SearchedLocations?? new string[] { }).ToList(); + searchedPaths = searchedPaths.Union(view.SearchedLocations?? Array.Empty()).ToList(); } } @@ -108,7 +108,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { // Validate against available views; if content-type represents a valid view, stop validation if (acceptHeader != null) { view = viewEngine.FindView(context, acceptHeader, MasterName); - searchedPaths = searchedPaths.Union(view.SearchedLocations ?? new string[] { }).ToList(); + searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty()).ToList(); } if (view != null) { break; @@ -125,7 +125,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { \-----------------------------------------------------------------------------------------------------------------------*/ if (view.View == null && !String.IsNullOrEmpty(_topicView)) { view = viewEngine.FindView(context, _topicView, MasterName); - searchedPaths = searchedPaths.Union(view.SearchedLocations ?? new string[] { }).ToList(); + searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty()).ToList(); } /*------------------------------------------------------------------------------------------------------------------------ @@ -133,7 +133,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { \-----------------------------------------------------------------------------------------------------------------------*/ if (view.View == null) { view = viewEngine.FindView(context, contentType, MasterName); - searchedPaths = searchedPaths.Union(view.SearchedLocations ?? new string[] { }).ToList(); + searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty()).ToList(); } /*------------------------------------------------------------------------------------------------------------------------ @@ -141,7 +141,7 @@ protected override ViewEngineResult FindView(ControllerContext context) { \-----------------------------------------------------------------------------------------------------------------------*/ if (view.View == null) { view = base.FindView(context); - searchedPaths = searchedPaths.Union(view.SearchedLocations ?? new string[] { }).ToList(); + searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty()).ToList(); } /*------------------------------------------------------------------------------------------------------------------------ diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 2e101c23..7822e091 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1758.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 8f58218f..a6a1876c 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -420,7 +420,7 @@ protected IList GetSourceCollection(Topic source, Relationships relations /*------------------------------------------------------------------------------------------------------------------------ | Establish source collection to store topics to be mapped \-----------------------------------------------------------------------------------------------------------------------*/ - var listSource = (IList)new Topic[] { }; + var listSource = (IList)Array.Empty(); var relationshipKey = configuration.RelationshipKey; var relationshipType = configuration.RelationshipType; diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index ea37042c..dff47a20 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1764.0")] +[assembly: AssemblyFileVersion("3.5.1765.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 0de9d4ad..8093de73 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -322,7 +322,7 @@ internal object GetMethodValue(object target, string name, Type targetType = nul var method = GetMember(target.GetType(), name); - return method.Invoke(target, new object[] { }); + return method.Invoke(target, Array.Empty()); } From 2c21506663e9d6d449f009fc56ed9503254dd8b5 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 12:21:19 -0700 Subject: [PATCH 107/149] Established `TopicViewModelLookupService` If an application is simply using the OOTB view models, then the `TopicViewModelLookupService` provides a convenient wrapper for `StaticTypeLookupService` which automatically adds the out-of-the-box view models. In addition, it can be derived by applications to add any application-specific view models. If an application adds a view model of the same name as an out-of-the-box version, that view model will be used instead of the factory default. Updated the `FakeViewModelLookupService` as an example. Updated documentation. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../TestDoubles/FakeViewModelLookupService.cs | 11 +--- .../Ignia.Topics.ViewModels.csproj | 1 + .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/README.md | 8 ++- .../TopicViewModelLookupService.cs | 64 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 11 files changed, 78 insertions(+), 20 deletions(-) create mode 100644 Ignia.Topics.ViewModels/TopicViewModelLookupService.cs diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index c20f23d3..25f0750a 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1766.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 1ae3b66c..96efa9bd 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1759.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index b652f65f..7412651b 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1773.0")] +[assembly: AssemblyFileVersion("3.5.1777.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs index 28c066cc..be60d25f 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs @@ -22,7 +22,7 @@ namespace Ignia.Topics.Tests.TestDoubles { /// /// Allows testing of services that depend on without using expensive reflection. /// - internal class FakeViewModelLookupService: StaticTypeLookupService { + internal class FakeViewModelLookupService: TopicViewModelLookupService { /*========================================================================================================================== | CONSTRUCTOR @@ -33,15 +33,6 @@ internal class FakeViewModelLookupService: StaticTypeLookupService { /// A new instance of the . internal FakeViewModelLookupService(): base(null, typeof(object)) { - /*------------------------------------------------------------------------------------------------------------------------ - | Add out-of-the-box view models - \-----------------------------------------------------------------------------------------------------------------------*/ - Add(typeof(ContentItemTopicViewModel)); - Add(typeof(ContentListTopicViewModel)); - Add(typeof(IndexTopicViewModel)); Add(typeof(ItemTopicViewModel)); Add(typeof(LookupListItemTopicViewModel)); Add(typeof(NavigationTopicViewModel)); - Add(typeof(PageGroupTopicViewModel)); Add(typeof(PageTopicViewModel)); - Add(typeof(SectionTopicViewModel)); Add(typeof(SlideshowTopicViewModel)); Add(typeof(SlideTopicViewModel)); Add(typeof(TopicViewModel)); Add(typeof(VideoTopicViewModel)); - /*------------------------------------------------------------------------------------------------------------------------ | Add test specific view models \-----------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index 2b6946a0..a20067a7 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -56,6 +56,7 @@ + diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 2e1fe036..3f3adeb9 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1765.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.ViewModels/README.md b/Ignia.Topics.ViewModels/README.md index 0ab94047..46498518 100644 --- a/Ignia.Topics.ViewModels/README.md +++ b/Ignia.Topics.ViewModels/README.md @@ -16,12 +16,14 @@ The `Ignia.Topics.ViewModels` assembly includes default implementations of basic - [`ItemTopicViewModel`](ItemTopicViewModel.cs) - [`ContentItemTopicViewModel`](ContentItemTopicViewModel.cs) - [`LookupListItemTopicViewModel`](LookupListItemTopicViewModel.cs) +- [`TopicViewModelLookupService`](TopicViewModelLookupService.cs) - [`TopicViewModelCollection<>`](TopicViewModelCollection.cs) ## Usage -By default, the [`Ignia.Topics.Web.Mvc`](../Ignia.Topics.Web.Mvc)'s [`TopicController`](../Ignia.Topics.Web.Mvc/TopicController.cs) uses the out-of-the-box [`TopicMappingService`](../Ignia.Topics/Mapping), which will attempt to map topics to view models based on the naming convention `{ContentType}TopicViewModel`, from any assembly or namespace. If the `Ignia.Topics.ViewModels.dll` is in an application's `/bin` directory then these view models will be available to the mapping service. +By default, the [`Ignia.Topics.Web.Mvc`](../Ignia.Topics.Web.Mvc)'s [`TopicController`](../Ignia.Topics.Web.Mvc/Controllers/TopicController.cs) uses the out-of-the-box [`TopicMappingService`](../Ignia.Topics/Mapping) to map topics to view models. For applications primarily relying on the out-of-the-box view models, it is recommended that the [`TopicViewModelLookupService`](TopicViewModelLookupService.cs) be used; this includes all of the out-of-the-box view models, and can be derived to add application-specific view models. -If any classes with the same name are available in _any other assembly or namespace_ then they will override the `ViewModels` from this assembly. That allows these classes to be treated as default fallbacks. +### `DynamicTopicViewModelLookupService` +For applications with a large number of view models, it may be preferable to use the `DynamicTopicViewModelLookupService`, which will attempt to map topics to view models based on the naming convention `{ContentType}TopicViewModel`, from any assembly or namespace. If the `Ignia.Topics.ViewModels.dll` is in an application's `/bin` directory then these view models will be available to the lookup service and, thus, the mapping service. If any classes with the same name are available in _any other assembly or namespace_ then they will override the `ViewModels` from this assembly. That allows these classes to be treated as default fallbacks. > *Note:* If a base class is overwritten then topics that derive from the original version will continue to do so unless they are _also_ overwritten. For example, if a `Theme` property is added to a customer-specific `PageTopicViewModel`, the `Theme` property won't be available on e.g. `SlideShowTopicViewModel` unless it is _also_ overwritten by the customer to inherit from their `PageTopicViewModel`. @@ -33,7 +35,7 @@ As view models, not all attributes and relationships are exposed by the view mod All of the view models assume a default constructor (e.g., `new TopicViewModel()`). This is necessary to provide compatibility with the `TopicMappingService` which will attempt to create new instances of view models based on the default constructor. ### Inheritance -The view models map to the hierarchy of the content types in OnTopic, with each view model only including properties that are _specific_ to that content type. So, for example, [`PageTopicContentType`](PageTopicContentType.cs) includes a `Body` property, which is introduced by the `Page` content type, but doesn't include e.g. `Key`, `ContentType`, or `Title`; these are all inherited from the base [`TopicViewModel`](TopicViewModel.cs). +The view models map to the hierarchy of the content types in OnTopic, with each view model only including properties that are _specific_ to that content type. So, for example, [`PageTopicViewModel`](PageTopicViewModel.cs) includes a `Body` property, which is introduced by the `Page` content type, but doesn't include e.g. `Key`, `ContentType`, or `Title`; these are all inherited from the base [`TopicViewModel`](TopicViewModel.cs). This is advantageous not only because it effectively models the familiar content type hierarchy, but also because it allows for polymorphism in the mapping library. So, for example, if a property accepts a `List` then this can contain any view models that implement or derive from `PageTopicViewModel` (e.g., `SlideshowTopicViewModel`, `VideoTopicViewModel`, &c.). diff --git a/Ignia.Topics.ViewModels/TopicViewModelLookupService.cs b/Ignia.Topics.ViewModels/TopicViewModelLookupService.cs new file mode 100644 index 00000000..54107c65 --- /dev/null +++ b/Ignia.Topics.ViewModels/TopicViewModelLookupService.cs @@ -0,0 +1,64 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Collections.Generic; + +namespace Ignia.Topics.ViewModels { + + /*============================================================================================================================ + | CLASS: TYPE INDEX + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The can be configured to provide a lookup of . + /// + public class TopicViewModelLookupService : StaticTypeLookupService { + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Establishes a new instance of a . Optionally accepts a list of instances and a default value. + /// + /// + /// Any instances submitted via should be unique by ; if they are not, they will be removed. + /// + /// The list of instances to expose as part of this service. + /// The default type to return if no match can be found. Defaults to object. + public TopicViewModelLookupService(IEnumerable types = null, Type defaultType = null) : + base(types, defaultType?? typeof(TopicViewModel)) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Ensure local view models are accounted for + \-----------------------------------------------------------------------------------------------------------------------*/ + AddIfMissing(typeof(ContentItemTopicViewModel)); + AddIfMissing(typeof(ContentListTopicViewModel)); + AddIfMissing(typeof(IndexTopicViewModel)); + AddIfMissing(typeof(ItemTopicViewModel)); + AddIfMissing(typeof(LookupListItemTopicViewModel)); + AddIfMissing(typeof(NavigationTopicViewModel)); + AddIfMissing(typeof(PageGroupTopicViewModel)); + AddIfMissing(typeof(PageTopicViewModel)); + AddIfMissing(typeof(SectionTopicViewModel)); + AddIfMissing(typeof(SlideTopicViewModel)); + AddIfMissing(typeof(SlideshowTopicViewModel)); + AddIfMissing(typeof(TopicViewModel)); + AddIfMissing(typeof(VideoTopicViewModel)); + + /*------------------------------------------------------------------------------------------------------------------------ + | Function: Add If Missing + \-----------------------------------------------------------------------------------------------------------------------*/ + void AddIfMissing(Type type) { + if (!Contains(type.Name)) { + Add(type); + } + } + + } + + } //Class +} //Namespace \ No newline at end of file diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 1def6416..257eeb10 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1764.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 7822e091..ae7321e9 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1759.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index dff47a20..33cccf65 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1766.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From ffdeef307ea3d9896346997c074b6fb39fc5e880 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 14:10:26 -0700 Subject: [PATCH 108/149] [Breaking] Converted `ITopicMappingService` to `async` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By converting `ITopicMappingService` to be `async`, we allow nested calls to `MapAsync()` to be done in parallel. This is especially useful when mapping related topics from collections; being able to do those in parallel should be considerably faster. This mandates that all calling items—such as the unit tests and MVC controllers—also be made async. Updated accordingly. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/TopicControllerTest.cs | 30 ++++- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 103 +++++++++--------- .../Properties/AssemblyInfo.cs | 2 +- .../Controllers/LayoutControllerBase{T}.cs | 13 ++- .../Controllers/TopicController.cs | 7 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- .../Mapping/CachedTopicMappingService.cs | 15 +-- Ignia.Topics/Mapping/ITopicMappingService.cs | 9 +- Ignia.Topics/Mapping/TopicMappingService.cs | 77 ++++++++----- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 14 files changed, 160 insertions(+), 108 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 25f0750a..35048380 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1766.0")] +[assembly: AssemblyFileVersion("3.5.1770.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 96efa9bd..bd0d59e8 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1762.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 7412651b..dedb883a 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1777.0")] +[assembly: AssemblyFileVersion("3.5.1786.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TopicControllerTest.cs b/Ignia.Topics.Tests/TopicControllerTest.cs index 7ac925e4..e37232e3 100644 --- a/Ignia.Topics.Tests/TopicControllerTest.cs +++ b/Ignia.Topics.Tests/TopicControllerTest.cs @@ -5,6 +5,7 @@ \=============================================================================================================================*/ using System; using System.Linq; +using System.Threading.Tasks; using System.Web.Mvc; using System.Web.Routing; using Ignia.Topics.Data.Caching; @@ -50,6 +51,31 @@ public TopicControllerTest() { _topicRepository = new CachedTopicRepository(new FakeTopicRepository()); } + /*========================================================================================================================== + | TEST: TOPIC + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Triggers the action. + /// + [TestMethod] + public async Task TopicController_IndexTestAsync() { + + var routes = new RouteData(); + var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); + var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); + + var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); + var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); + + var controller = new TopicController(_topicRepository, topicRoutingService, mappingService); + var result = await controller.IndexAsync(topic.GetWebPath()) as TopicViewResult; + var model = result.Model as PageTopicViewModel; + + Assert.IsNotNull(model); + Assert.AreEqual("Web_0_1_1", model.Title); + + } + /*========================================================================================================================== | TEST: ERROR \-------------------------------------------------------------------------------------------------------------------------*/ @@ -173,7 +199,7 @@ public void SitemapController_IndexTest() { /// Triggers the action. /// [TestMethod] - public void LayoutController_MenuTest() { + public async Task LayoutController_MenuTest() { var routes = new RouteData(); var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); @@ -183,7 +209,7 @@ public void LayoutController_MenuTest() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var controller = new LayoutController(_topicRepository, topicRoutingService, mappingService); - var result = controller.Menu() as PartialViewResult; + var result = await controller.Menu() as PartialViewResult; var model = result.Model as NavigationViewModel; Assert.IsNotNull(model); diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index bf66f74f..3ba392b4 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -5,6 +5,7 @@ \=============================================================================================================================*/ using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Threading.Tasks; using Ignia.Topics.Data.Caching; using Ignia.Topics.Mapping; using Ignia.Topics.Repositories; @@ -53,7 +54,7 @@ public TopicMappingServiceTest() { /// constructed instance of a DTO. /// [TestMethod] - public void TopicMappingService_MapGeneric() { + public async Task TopicMappingService_MapGeneric() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); @@ -62,7 +63,7 @@ public void TopicMappingService_MapGeneric() { topic.Attributes.SetValue("Title", "Value1"); topic.Attributes.SetValue("IsHidden", "1"); - var target = (PageTopicViewModel)mappingService.Map(topic, new PageTopicViewModel()); + var target = (PageTopicViewModel)await mappingService.MapAsync(topic, new PageTopicViewModel()); Assert.AreEqual("ValueA", target.MetaTitle); Assert.AreEqual("Value1", target.Title); @@ -78,7 +79,7 @@ public void TopicMappingService_MapGeneric() { /// determine the instance type. /// [TestMethod] - public void TopicMappingService_MapDynamic() { + public async Task TopicMappingService_MapDynamic() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); @@ -87,7 +88,7 @@ public void TopicMappingService_MapDynamic() { topic.Attributes.SetValue("Title", "Value1"); topic.Attributes.SetValue("IsHidden", "1"); - var target = (PageTopicViewModel)mappingService.Map(topic); + var target = (PageTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual("ValueA", target.MetaTitle); Assert.AreEqual("Value1", target.Title); @@ -102,7 +103,7 @@ public void TopicMappingService_MapDynamic() { /// Establishes a and tests whether it successfully crawls the parent tree. /// [TestMethod] - public void TopicMappingService_MapParents() { + public async Task TopicMappingService_MapParents() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Sample"); @@ -120,7 +121,7 @@ public void TopicMappingService_MapParents() { grandParent.Attributes.SetValue("IsHidden", "1"); grandParent.Attributes.SetValue("Property", "ValueB"); - var viewModel = (PageTopicViewModel)mappingService.Map(topic); + var viewModel = (PageTopicViewModel) await mappingService.MapAsync(topic); var parentViewModel = viewModel?.Parent; var grandParentViewModel = parentViewModel?.Parent as SampleTopicViewModel; @@ -144,7 +145,7 @@ public void TopicMappingService_MapParents() { /// . /// [TestMethod] - public void TopicMappingService_InheritValues() { + public async Task TopicMappingService_InheritValues() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Page"); @@ -154,7 +155,7 @@ public void TopicMappingService_InheritValues() { grandParent.Attributes.SetValue("Property", "ValueA"); grandParent.Attributes.SetValue("InheritedProperty", "ValueB"); - var viewModel = (SampleTopicViewModel)mappingService.Map(topic); + var viewModel = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual(null, viewModel.Property); Assert.AreEqual("ValueB", viewModel.InheritedProperty); @@ -169,7 +170,7 @@ public void TopicMappingService_InheritValues() { /// specified by . /// [TestMethod] - public void TopicMappingService_AlternateAttributeKey() { + public async Task TopicMappingService_AlternateAttributeKey() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -177,7 +178,7 @@ public void TopicMappingService_AlternateAttributeKey() { topic.Attributes.SetValue("Property", "ValueA"); topic.Attributes.SetValue("PropertyAlias", "ValueB"); - var viewModel = (SampleTopicViewModel)mappingService.Map(topic); + var viewModel = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual("ValueA", viewModel.PropertyAlias); @@ -190,7 +191,7 @@ public void TopicMappingService_AlternateAttributeKey() { /// Establishes a and tests whether it successfully crawls the relationships. /// [TestMethod] - public void TopicMappingService_MapRelationships() { + public async Task TopicMappingService_MapRelationships() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -202,7 +203,7 @@ public void TopicMappingService_MapRelationships() { topic.Relationships.SetTopic("Cousins", relatedTopic2); topic.Relationships.SetTopic("Siblings", relatedTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual(2, target.Cousins.Count); Assert.IsNotNull(target.Cousins.FirstOrDefault((t) => t.Key.StartsWith("RelatedTopic1"))); @@ -226,7 +227,7 @@ public void TopicMappingService_MapRelationships() { /// RelationshipAlias, and b) source from the collection. /// [TestMethod] - public void TopicMappingService_AlternateRelationship() { + public async Task TopicMappingService_AlternateRelationship() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -247,7 +248,7 @@ public void TopicMappingService_AlternateRelationship() { relatedTopic5.Relationships.SetTopic("AmbiguousRelationship", topic); relatedTopic6.Relationships.SetTopic("AmbiguousRelationship", topic); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual(2, target.RelationshipAlias.Count); Assert.IsNotNull(target.RelationshipAlias.FirstOrDefault((t) => t.Key.StartsWith("RelatedTopic5"))); @@ -262,7 +263,7 @@ public void TopicMappingService_AlternateRelationship() { /// Establishes a and tests whether it successfully crawls the nested topics. /// [TestMethod] - public void TopicMappingService_MapNestedTopics() { + public async Task TopicMappingService_MapNestedTopics() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -271,7 +272,7 @@ public void TopicMappingService_MapNestedTopics() { var nestedTopic1 = TopicFactory.Create("NestedTopic1", "Page", topicList); var nestedTopic2 = TopicFactory.Create("NestedTopic2", "Index", topicList); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual(2, target.Categories.Count); Assert.IsNotNull(target.Categories.FirstOrDefault((t) => t.Key.StartsWith("NestedTopic1"))); @@ -288,7 +289,7 @@ public void TopicMappingService_MapNestedTopics() { /// Establishes a and tests whether it successfully crawls the nested topics. /// [TestMethod] - public void TopicMappingService_MapChildren() { + public async Task TopicMappingService_MapChildren() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -297,7 +298,7 @@ public void TopicMappingService_MapChildren() { var childTopic3 = TopicFactory.Create("ChildTopic3", "Sample", topic); var childTopic4 = TopicFactory.Create("ChildTopic4", "Index", childTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.AreEqual(3, target.Children.Count); Assert.IsNotNull(target.Children.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic1"))); @@ -315,7 +316,7 @@ public void TopicMappingService_MapChildren() { /// Establishes a and tests whether it successfully maps referenced topics. /// [TestMethod] - public void TopicMappingService_MapTopicReferences() { + public async Task TopicMappingService_MapTopicReferences() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -323,7 +324,7 @@ public void TopicMappingService_MapTopicReferences() { topic.Attributes.SetInteger("TopicReferenceId", 11111); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel)await mappingService.MapAsync(topic); Assert.IsNotNull(target.TopicReference); Assert.AreEqual(11111, target.TopicReference.Id); @@ -338,7 +339,7 @@ public void TopicMappingService_MapTopicReferences() { /// instructions of each model class. /// [TestMethod] - public void TopicMappingService_RecursiveRelationships() { + public async Task TopicMappingService_RecursiveRelationships() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -367,7 +368,7 @@ public void TopicMappingService_RecursiveRelationships() { //Set ancillary relationships cousinTopic3.Relationships.SetTopic("Cousins", secondCousin); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel) await mappingService.MapAsync(topic); var cousinTarget = (SampleTopicViewModel)target.Cousins.FirstOrDefault((t) => t.Key.StartsWith("CousinTopic3")); var distantCousinTarget = (SampleTopicViewModel)cousinTarget.Children.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic3")); @@ -391,7 +392,7 @@ public void TopicMappingService_RecursiveRelationships() { /// collection of objects (from which . /// [TestMethod] - public void TopicMappingService_MapSlideshow() { + public async Task TopicMappingService_MapSlideshow() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Slideshow"); @@ -401,7 +402,7 @@ public void TopicMappingService_MapSlideshow() { var childTopic3 = TopicFactory.Create("ChildTopic3", "Slide", slides); var childTopic4 = TopicFactory.Create("ChildTopic4", "ContentItem", slides); - var target = (SlideshowTopicViewModel)mappingService.Map(topic); + var target = (SlideshowTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual(4, target.ContentItems.Count); Assert.IsNotNull(target.ContentItems.FirstOrDefault((t) => t.Key.StartsWith("ChildTopic1"))); @@ -419,7 +420,7 @@ public void TopicMappingService_MapSlideshow() { /// instances if called for by the model. This isn't a best practice, but is maintained for edge cases. /// [TestMethod] - public void TopicMappingService_MapTopics() { + public async Task TopicMappingService_MapTopics() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -431,7 +432,7 @@ public void TopicMappingService_MapTopics() { topic.Relationships.SetTopic("Related", relatedTopic2); topic.Relationships.SetTopic("Related", relatedTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel) await mappingService.MapAsync(topic); var relatedTopic3copy = ((Topic)target.Related.FirstOrDefault((t) => t.Key.StartsWith("RelatedTopic3"))); Assert.AreEqual(3, target.Related.Count); @@ -450,12 +451,12 @@ public void TopicMappingService_MapTopics() { /// . /// [TestMethod] - public void TopicMappingService_MapMetadata() { + public async Task TopicMappingService_MapMetadata() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "MetadataLookup"); - var target = (MetadataLookupTopicViewModel)mappingService.Map(topic); + var target = (MetadataLookupTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual(5, target.Categories.Count); @@ -469,14 +470,14 @@ public void TopicMappingService_MapMetadata() { /// taking advantage of its internal caching mechanism. /// [TestMethod] - public void TopicMappingService_MapCircularReference() { + public async Task TopicMappingService_MapCircularReference() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Circular", 1); var childTopic = TopicFactory.Create("ChildTopic", "Circular", 2, topic); - var mappedTopic = (CircularTopicViewModel)mappingService.Map(topic); + var mappedTopic = (CircularTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual(mappedTopic, mappedTopic.Children.First().Parent); @@ -490,7 +491,7 @@ public void TopicMappingService_MapCircularReference() { /// cref="SampleTopicViewModel.Children"/> property can be filtered by . /// [TestMethod] - public void TopicMappingService_FilterByContentType() { + public async Task TopicMappingService_FilterByContentType() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -499,7 +500,7 @@ public void TopicMappingService_FilterByContentType() { var childTopic3 = TopicFactory.Create("ChildTopic3", "Index", topic); var childTopic4 = TopicFactory.Create("ChildTopic4", "Index", childTopic3); - var target = (SampleTopicViewModel)mappingService.Map(topic); + var target = (SampleTopicViewModel) await mappingService.MapAsync(topic); var indexes = target.Children.GetByContentType("Index"); @@ -518,14 +519,14 @@ public void TopicMappingService_FilterByContentType() { /// correctly populated. /// [TestMethod] - public void TopicMappingService_MapGetterMethods() { + public async Task TopicMappingService_MapGetterMethods() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Sample"); var childTopic = TopicFactory.Create("Child", "Page", topic); var grandChildTopic = TopicFactory.Create("GrandChild", "Index", childTopic); - var target = (IndexTopicViewModel)mappingService.Map(grandChildTopic); + var target = (IndexTopicViewModel) await mappingService.MapAsync(grandChildTopic); Assert.AreEqual("Topic:Child:GrandChild", target.UniqueKey); @@ -538,14 +539,14 @@ public void TopicMappingService_MapGetterMethods() { /// Maps a content type that has a required property. Ensures that an error is not thrown if it is set. /// [TestMethod] - public void TopicMappingService_MapRequiredProperty() { + public async Task TopicMappingService_MapRequiredProperty() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); topic.Attributes.SetValue("RequiredAttribute", "Required"); - var target = (RequiredTopicViewModel)mappingService.Map(topic); + var target = (RequiredTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual("Required", target.RequiredAttribute); @@ -559,12 +560,12 @@ public void TopicMappingService_MapRequiredProperty() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public void TopicMappingService_MapRequiredPropertyException() { + public async Task TopicMappingService_MapRequiredPropertyException() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); - var target = (RequiredTopicViewModel)mappingService.Map(topic); + var target = (RequiredTopicViewModel) await mappingService.MapAsync(topic); } @@ -576,12 +577,12 @@ public void TopicMappingService_MapRequiredPropertyException() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public void TopicMappingService_MapRequiredObjectPropertyException() { + public async Task TopicMappingService_MapRequiredObjectPropertyException() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "RequiredObject"); - var target = (RequiredTopicViewModel)mappingService.Map(topic); + var target = (RequiredTopicViewModel) await mappingService.MapAsync(topic); } @@ -592,12 +593,12 @@ public void TopicMappingService_MapRequiredObjectPropertyException() { /// Maps a content type that has default properties. Ensures that each is set appropriately. /// [TestMethod] - public void TopicMappingService_MapDefaultValueProperties() { + public async Task TopicMappingService_MapDefaultValueProperties() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "DefaultValue"); - var target = (DefaultValueTopicViewModel)mappingService.Map(topic); + var target = (DefaultValueTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual("Default", target.DefaultString); Assert.AreEqual(10, target.DefaultInt); @@ -613,14 +614,14 @@ public void TopicMappingService_MapDefaultValueProperties() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public void TopicMappingService_MapMinimumValueProperties() { + public async Task TopicMappingService_MapMinimumValueProperties() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "MinimumLengthProperty"); topic.Attributes.SetValue("MinimumLength", "Hello World"); - var target = (MinimumLengthPropertyTopicViewModel)mappingService.Map(topic); + var target = (MinimumLengthPropertyTopicViewModel) await mappingService.MapAsync(topic); } @@ -633,7 +634,7 @@ public void TopicMappingService_MapMinimumValueProperties() { /// instances. /// [TestMethod] - public void TopicMappingService_FilterByAttribute() { + public async Task TopicMappingService_FilterByAttribute() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Filtered"); @@ -647,7 +648,7 @@ public void TopicMappingService_FilterByAttribute() { childTopic3.Attributes.SetValue("SomeAttribute", "ValueA"); childTopic4.Attributes.SetValue("SomeAttribute", "ValueB"); - var target = (FilteredTopicViewModel)mappingService.Map(topic); + var target = (FilteredTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual(2, target.Children.Count); @@ -661,7 +662,7 @@ public void TopicMappingService_FilterByAttribute() { /// cref="FlattenChildrenTopicViewModel.Children"/> property is properly flattened. /// [TestMethod] - public void TopicMappingService_Flatten() { + public async Task TopicMappingService_Flatten() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -674,7 +675,7 @@ public void TopicMappingService_Flatten() { } } - var target = (FlattenChildrenTopicViewModel)mappingService.Map(topic); + var target = (FlattenChildrenTopicViewModel) await mappingService.MapAsync(topic); Assert.AreEqual(25, target.Children.Count); @@ -688,15 +689,15 @@ public void TopicMappingService_Flatten() { /// the same instance of a mapped object is turned after two calls. /// [TestMethod] - public void TopicMappingService_Caching() { + public async Task TopicMappingService_Caching() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var cachedMappingService = new CachedTopicMappingService(mappingService); var topic = TopicFactory.Create("Test", "Filtered", 5); - var target1 = (FilteredTopicViewModel)cachedMappingService.Map(topic); - var target2 = (FilteredTopicViewModel)cachedMappingService.Map(topic); + var target1 = (FilteredTopicViewModel)await cachedMappingService.MapAsync(topic); + var target2 = (FilteredTopicViewModel)await cachedMappingService.MapAsync(topic); Assert.AreEqual(target1, target2); diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 3f3adeb9..696577f2 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1769.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs index 8d8dd892..67b0502a 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs @@ -5,6 +5,7 @@ \=============================================================================================================================*/ using System; using System.Linq; +using System.Threading.Tasks; using System.Web.Mvc; using Ignia.Topics.Mapping; using Ignia.Topics.Repositories; @@ -38,7 +39,7 @@ namespace Ignia.Topics.Web.Mvc.Controllers { /// abstract and suffixed with Base. /// /// - public abstract class LayoutControllerBase : Controller where T : class, INavigationTopicViewModel, new() { + public abstract class LayoutControllerBase : AsyncController where T : class, INavigationTopicViewModel, new() { /*========================================================================================================================== | PRIVATE VARIABLES @@ -96,7 +97,7 @@ protected Topic CurrentTopic { /// /// Provides the global menu for the site layout, which exposes the top two tiers of navigation. /// - public virtual PartialViewResult Menu() { + public async virtual Task Menu() { /*------------------------------------------------------------------------------------------------------------------------ | Establish variables @@ -115,7 +116,7 @@ public virtual PartialViewResult Menu() { | Construct view model \-----------------------------------------------------------------------------------------------------------------------*/ var navigationViewModel = new NavigationViewModel() { - NavigationRoot = AddNestedTopics(navigationRootTopic, false, 3), + NavigationRoot = await AddNestedTopicsAsync(navigationRootTopic, false, 3), CurrentKey = CurrentTopic?.GetUniqueKey() }; @@ -192,7 +193,7 @@ private static int DistanceFromRoot(Topic sourceTopic) { /// The to pull the values from. /// Determines whether s should be crawled. /// Determines how many tiers of children should be included in the graph. - protected T AddNestedTopics( + protected async Task AddNestedTopicsAsync( Topic sourceTopic, bool allowPageGroups = true, int tiers = 1 @@ -201,11 +202,11 @@ protected T AddNestedTopics( if (sourceTopic == null) { return null; } - var viewModel = _topicMappingService.Map(sourceTopic, Relationships.None); + var viewModel = await _topicMappingService.MapAsync(sourceTopic, Relationships.None); if (tiers >= 0 && (allowPageGroups || !sourceTopic.ContentType.Equals("PageGroup")) && viewModel.Children.Count == 0) { foreach (var topic in sourceTopic.Children.Where(t => t.IsVisible())) { viewModel.Children.Add( - AddNestedTopics( + await AddNestedTopicsAsync( topic, allowPageGroups, tiers diff --git a/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs b/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs index 2e1b66e8..eddad172 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/TopicController.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics.Contracts; using System.Linq; +using System.Threading.Tasks; using System.Web.Mvc; using Ignia.Topics.Mapping; using Ignia.Topics.Repositories; @@ -20,7 +21,7 @@ namespace Ignia.Topics.Web.Mvc.Controllers { /// identifying the topic associated with the given path, determining its content type, and returning a view associated with /// that content type (with potential overrides for multiple views). /// - public class TopicController : Controller { + public class TopicController : AsyncController { /*========================================================================================================================== | PRIVATE VARIABLES @@ -92,12 +93,12 @@ protected Topic CurrentTopic { /// query string or topic's view. /// /// A view associated with the requested topic's Content Type and view. - public virtual ActionResult Index(string path) { + public async virtual Task IndexAsync(string path) { /*------------------------------------------------------------------------------------------------------------------------ | Establish default view model \-----------------------------------------------------------------------------------------------------------------------*/ - var topicViewModel = _topicMappingService.Map(CurrentTopic); + var topicViewModel = await _topicMappingService.MapAsync(CurrentTopic); /*------------------------------------------------------------------------------------------------------------------------ | Return topic view diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 257eeb10..498cc0de 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1764.0")] +[assembly: AssemblyFileVersion("3.5.1770.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index ae7321e9..50b4fcd2 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1762.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/CachedTopicMappingService.cs b/Ignia.Topics/Mapping/CachedTopicMappingService.cs index 4d304166..9ef050ea 100644 --- a/Ignia.Topics/Mapping/CachedTopicMappingService.cs +++ b/Ignia.Topics/Mapping/CachedTopicMappingService.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics.Contracts; +using System.Threading.Tasks; namespace Ignia.Topics.Mapping { @@ -53,7 +54,7 @@ public CachedTopicMappingService(ITopicMappingService topicMappingService) { /// . These results may need to be cast to a specific type, depending on the context. That said, /// strongly-typed views should be able to cast the object to the appropriate View Model type. If the type of the View /// Model is known upfront, and it is imperative that it be strongly-typed, then prefer . + /// cref="MapAsync{T}(Topic, Relationships)"/>. /// /// /// Because the target object is being dynamically constructed by the underlying implementation, it must implement a @@ -63,7 +64,7 @@ public CachedTopicMappingService(ITopicMappingService topicMappingService) { /// The entity to derive the data from. /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. - public object Map(Topic topic, Relationships relationships = Relationships.All) { + public async Task MapAsync(Topic topic, Relationships relationships = Relationships.All) { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated @@ -76,7 +77,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /*---------------------------------------------------------------------------------------------------------------------- | Return cached result \---------------------------------------------------------------------------------------------------------------------*/ - return CacheViewModel(topic.ContentType, _topicMappingService.Map(topic, relationships), cacheKey); + return CacheViewModel(topic.ContentType, await _topicMappingService.MapAsync(topic, relationships), cacheKey); } @@ -98,7 +99,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// /// An instance of the requested View Model with properties appropriately mapped. /// - public T Map(Topic topic, Relationships relationships = Relationships.All) where T : class, new() { + public async Task MapAsync(Topic topic, Relationships relationships = Relationships.All) where T : class, new() { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated @@ -111,7 +112,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /*---------------------------------------------------------------------------------------------------------------------- | Return cached result \---------------------------------------------------------------------------------------------------------------------*/ - return CacheViewModel(topic.ContentType, _topicMappingService.Map(topic, relationships), cacheKey) as T; + return CacheViewModel(topic.ContentType, await _topicMappingService.MapAsync(topic, relationships), cacheKey) as T; } @@ -127,7 +128,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// /// The target view model with the properties appropriately mapped. /// - public object Map(Topic topic, object target, Relationships relationships = Relationships.All) { + public async Task MapAsync(Topic topic, object target, Relationships relationships = Relationships.All) { /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated @@ -140,7 +141,7 @@ public object Map(Topic topic, object target, Relationships relationships = Rela /*---------------------------------------------------------------------------------------------------------------------- | Return cached result \---------------------------------------------------------------------------------------------------------------------*/ - return CacheViewModel(topic.ContentType, _topicMappingService.Map(topic, relationships), cacheKey); + return CacheViewModel(topic.ContentType, await _topicMappingService.MapAsync(topic, relationships), cacheKey); } diff --git a/Ignia.Topics/Mapping/ITopicMappingService.cs b/Ignia.Topics/Mapping/ITopicMappingService.cs index 80857e40..986e4ada 100644 --- a/Ignia.Topics/Mapping/ITopicMappingService.cs +++ b/Ignia.Topics/Mapping/ITopicMappingService.cs @@ -4,6 +4,7 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Threading.Tasks; namespace Ignia.Topics.Mapping { @@ -28,7 +29,7 @@ public interface ITopicMappingService { /// Because the class is using reflection to determine the target View Models, the return type is . /// These results may need to be cast to a specific type, depending on the context. That said, strongly-typed views /// should be able to cast the object to the appropriate View Model type. If the type of the View Model is known - /// upfront, and it is imperative that it be strongly-typed, then prefer . + /// upfront, and it is imperative that it be strongly-typed, then prefer . /// /// /// Because the target object is being dynamically constructed, it must implement a default constructor. @@ -37,7 +38,7 @@ public interface ITopicMappingService { /// The entity to derive the data from. /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. - object Map(Topic topic, Relationships relationships = Relationships.All); + Task MapAsync(Topic topic, Relationships relationships = Relationships.All); /*========================================================================================================================== | METHOD: MAP (GENERIC) @@ -56,7 +57,7 @@ public interface ITopicMappingService { /// /// An instance of the requested View Model with properties appropriately mapped. /// - T Map(Topic topic, Relationships relationships = Relationships.All) where T : class, new(); + Task MapAsync(Topic topic, Relationships relationships = Relationships.All) where T : class, new(); /*========================================================================================================================== | METHOD: MAP (INSTANCES) @@ -71,7 +72,7 @@ public interface ITopicMappingService { /// /// An instance of the requested View Model instance with properties appropriately mapped. /// - object Map(Topic topic, object target, Relationships relationships = Relationships.All); + Task MapAsync(Topic topic, object target, Relationships relationships = Relationships.All); } //Interface } //Namespace diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index a6a1876c..d6cf69f1 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -9,6 +9,7 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Ignia.Topics.Collections; using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; @@ -61,7 +62,7 @@ public TopicMappingService(ITopicRepository topicRepository, ITypeLookupService /// Because the class is using reflection to determine the target View Models, the return type is . /// These results may need to be cast to a specific type, depending on the context. That said, strongly-typed views /// should be able to cast the object to the appropriate View Model type. If the type of the View Model is known - /// upfront, and it is imperative that it be strongly-typed, then prefer . + /// upfront, and it is imperative that it be strongly-typed, prefer . /// /// /// Because the target object is being dynamically constructed, it must implement a default constructor. @@ -70,8 +71,8 @@ public TopicMappingService(ITopicRepository topicRepository, ITypeLookupService /// The entity to derive the data from. /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. - public object Map(Topic topic, Relationships relationships = Relationships.All) => - Map(topic, relationships, new Dictionary()); + public async Task MapAsync(Topic topic, Relationships relationships = Relationships.All) => + await MapAsync(topic, relationships, new Dictionary()); /// /// Given a topic, will identify any View Models named, by convention, "{ContentType}TopicViewModel" and populate them @@ -82,7 +83,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// Because the class is using reflection to determine the target View Models, the return type is . /// These results may need to be cast to a specific type, depending on the context. That said, strongly-typed views /// should be able to cast the object to the appropriate View Model type. If the type of the View Model is known - /// upfront, and it is imperative that it be strongly-typed, then prefer . + /// upfront, and it is imperative that it be strongly-typed, prefer . /// /// /// Because the target object is being dynamically constructed, it must implement a default constructor. @@ -97,7 +98,7 @@ public object Map(Topic topic, Relationships relationships = Relationships.All) /// Determines what relationships the mapping should follow, if any. /// A cache to keep track of already-mapped object instances. /// An instance of the dynamically determined View Model with properties appropriately mapped. - private object Map(Topic topic, Relationships relationships, Dictionary cache) { + private async Task MapAsync(Topic topic, Relationships relationships, Dictionary cache) { /*---------------------------------------------------------------------------------------------------------------------- | Handle null source @@ -120,7 +121,7 @@ private object Map(Topic topic, Relationships relationships, Dictionary /// An instance of the requested View Model with properties appropriately mapped. /// - public T Map(Topic topic, Relationships relationships = Relationships.All) where T : class, new() { + public async Task MapAsync(Topic topic, Relationships relationships = Relationships.All) where T : class, new() { if (typeof(Topic).IsAssignableFrom(typeof(T))) { return topic as T; } - return (T)Map(topic, new T(), relationships); + return (T)await MapAsync(topic, new T(), relationships); } /*========================================================================================================================== @@ -160,8 +161,8 @@ private object Map(Topic topic, Relationships relationships, Dictionary /// The target view model with the properties appropriately mapped. /// - public object Map(Topic topic, object target, Relationships relationships = Relationships.All) => - Map(topic, target, relationships, new Dictionary()); + public async Task MapAsync(Topic topic, object target, Relationships relationships = Relationships.All) => + await MapAsync(topic, target, relationships, new Dictionary()); /// /// Given a topic and an instance of a DTO, will populate the DTO according to the default mapping rules. @@ -178,7 +179,7 @@ public object Map(Topic topic, object target, Relationships relationships = Rela /// /// The target view model with the properties appropriately mapped. /// - private object Map(Topic topic, object target, Relationships relationships, Dictionary cache) { + private async Task MapAsync(Topic topic, object target, Relationships relationships, Dictionary cache) { /*------------------------------------------------------------------------------------------------------------------------ | Validate input @@ -207,9 +208,11 @@ private object Map(Topic topic, object target, Relationships relationships, Dict /*------------------------------------------------------------------------------------------------------------------------ | Loop through properties, mapping each one \-----------------------------------------------------------------------------------------------------------------------*/ + var taskQueue = new List(); foreach (var property in _typeCache.GetMembers(target.GetType())) { - SetProperty(topic, target, relationships, property, cache); + taskQueue.Add(SetPropertyAsync(topic, target, relationships, property, cache)); } + await Task.WhenAll(taskQueue.ToArray()); /*------------------------------------------------------------------------------------------------------------------------ | Return result @@ -219,7 +222,7 @@ private object Map(Topic topic, object target, Relationships relationships, Dict } /*========================================================================================================================== - | PROTECTED: SET PROPERTY + | PROTECTED: SET PROPERTY (ASYNC) \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Helper function that evaluates each property on the target object and attempts to retrieve a value from the source @@ -230,7 +233,7 @@ private object Map(Topic topic, object target, Relationships relationships, Dict /// Determines what relationships the mapping should follow, if any. /// Information related to the current property. /// A cache to keep track of already-mapped object instances. - protected void SetProperty( + protected async Task SetPropertyAsync( Topic source, object target, Relationships relationships, @@ -258,14 +261,14 @@ Dictionary cache SetScalarValue(source, target, configuration); } else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { - SetCollectionValue(source, target, relationships, configuration, cache); + await SetCollectionValueAsync(source, target, relationships, configuration, cache); } else if (configuration.AttributeKey.Equals("Parent") && relationships.HasFlag(Relationships.Parents)) { - SetTopicReference(source.Parent, target, configuration, cache); + await SetTopicReferenceAsync(source.Parent, target, configuration, cache); } else if (topicReferenceId > 0 && relationships.HasFlag(Relationships.References)) { var topicReference = _topicRepository.Load(topicReferenceId); - SetTopicReference(topicReference, target, configuration, cache); + await SetTopicReferenceAsync(topicReference, target, configuration, cache); } /*------------------------------------------------------------------------------------------------------------------------ @@ -357,7 +360,7 @@ protected static void SetScalarValue(Topic source, object target, PropertyConfig /// The with details about the property's attributes. /// /// A cache to keep track of already-mapped object instances. - protected void SetCollectionValue( + protected async Task SetCollectionValueAsync( Topic source, object target, Relationships relationships, @@ -392,7 +395,7 @@ Dictionary cache /*------------------------------------------------------------------------------------------------------------------------ | Map the topics from the source collection, and add them to the target collection \-----------------------------------------------------------------------------------------------------------------------*/ - PopulateTargetCollection(sourceList, targetList, configuration, cache); + await PopulateTargetCollectionAsync(sourceList, targetList, configuration, cache); } @@ -507,7 +510,7 @@ IList GetRelationship(RelationshipType relationship, Func c /// The with details about the property's attributes. /// /// A cache to keep track of already-mapped object instances. - protected void PopulateTargetCollection( + protected async Task PopulateTargetCollectionAsync( IList sourceList, IList targetList, PropertyConfiguration configuration, @@ -524,8 +527,10 @@ Dictionary cache } /*------------------------------------------------------------------------------------------------------------------------ - | Populate the target collection + | Queue up mapping tasks \-----------------------------------------------------------------------------------------------------------------------*/ + var taskQueue = new List>(); + foreach (var childTopic in sourceList) { //Ensure the source topic matches any [FilterByAttribute()] settings @@ -541,21 +546,37 @@ Dictionary cache //Map child topic to target DTO var childDto = (object)childTopic; if (!typeof(Topic).IsAssignableFrom(listType)) { - childDto = Map(childTopic, configuration.CrawlRelationships, cache); + taskQueue.Add(MapAsync(childTopic, configuration.CrawlRelationships, cache)); + } else { + AddToList(childDto); } - //Ensure the list item derives from the list type - if (listType.IsAssignableFrom(childDto.GetType())) { + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Process mapping tasks + \-----------------------------------------------------------------------------------------------------------------------*/ + while (taskQueue.Count > 0) { + var dtoTask = await Task.WhenAny(taskQueue); + taskQueue.Remove(dtoTask); + AddToList(await dtoTask); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Function: Add to List + \-----------------------------------------------------------------------------------------------------------------------*/ + void AddToList(object dto) { + if (listType.IsAssignableFrom(dto.GetType())) { try { - targetList.Add(childDto); + targetList.Add(dto); } catch (ArgumentException) { //Ignore exceptions caused by duplicate keys, in case the IList represents a keyed collection //We would defensively check for this, except IList doesn't provide a suitable method to do so } } - } + } /*========================================================================================================================== @@ -570,13 +591,13 @@ Dictionary cache /// The with details about the property's attributes. /// /// A cache to keep track of already-mapped object instances. - protected void SetTopicReference( + protected async Task SetTopicReferenceAsync( Topic source, object target, PropertyConfiguration configuration, Dictionary cache ) { - var topicDto = Map(source, configuration.CrawlRelationships, cache); + var topicDto = await MapAsync(source, configuration.CrawlRelationships, cache); if (topicDto != null && configuration.Property.PropertyType.IsAssignableFrom(topicDto.GetType())) { configuration.Property.SetValue(target, topicDto); } diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 33cccf65..dccba9fc 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1766.0")] +[assembly: AssemblyFileVersion("3.5.1770.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 1c254e7e0d55360ea75580ae603ef9578beb5f5f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 18:48:35 -0700 Subject: [PATCH 109/149] Removed completed `### TODO` (from 2013!) --- Ignia.Topics/Querying/Topic.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Ignia.Topics/Querying/Topic.cs b/Ignia.Topics/Querying/Topic.cs index 976fe840..eefa01d9 100644 --- a/Ignia.Topics/Querying/Topic.cs +++ b/Ignia.Topics/Querying/Topic.cs @@ -104,9 +104,6 @@ public static Target.Topic FindFirst(this Target.Topic topic, Func=========================================================================================================================== - | ###TODO JJC080313: Consider adding an overload of the out-of-the-box FindAll() method that supports recursion, thus - | allowing a search by any criteria - including attributes. \-------------------------------------------------------------------------------------------------------------------------*/ /// /// Retrieves a collection of topics based on an attribute name and value. From b79075bd3508a6093a8d93a410d7f126f4ee7a95 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Mon, 7 May 2018 18:49:18 -0700 Subject: [PATCH 110/149] Converted local cache to thread-safe `ConcurrentDictionary<>` --- Ignia.Topics/Mapping/TopicMappingService.cs | 32 ++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index d6cf69f1..683eff51 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -5,6 +5,7 @@ \=============================================================================================================================*/ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; @@ -72,7 +73,7 @@ public TopicMappingService(ITopicRepository topicRepository, ITypeLookupService /// Determines what relationships the mapping should follow, if any. /// An instance of the dynamically determined View Model with properties appropriately mapped. public async Task MapAsync(Topic topic, Relationships relationships = Relationships.All) => - await MapAsync(topic, relationships, new Dictionary()); + await MapAsync(topic, relationships, new ConcurrentDictionary()); /// /// Given a topic, will identify any View Models named, by convention, "{ContentType}TopicViewModel" and populate them @@ -98,7 +99,7 @@ public async Task MapAsync(Topic topic, Relationships relationships = Re /// Determines what relationships the mapping should follow, if any. /// A cache to keep track of already-mapped object instances. /// An instance of the dynamically determined View Model with properties appropriately mapped. - private async Task MapAsync(Topic topic, Relationships relationships, Dictionary cache) { + private async Task MapAsync(Topic topic, Relationships relationships, ConcurrentDictionary cache) { /*---------------------------------------------------------------------------------------------------------------------- | Handle null source @@ -108,8 +109,8 @@ private async Task MapAsync(Topic topic, Relationships relationships, Di /*------------------------------------------------------------------------------------------------------------------------ | Handle cached objects \-----------------------------------------------------------------------------------------------------------------------*/ - if (cache.ContainsKey(topic.Id)) { - return cache[topic.Id]; + if (cache.TryGetValue(topic.Id, out var dto)) { + return dto; } /*---------------------------------------------------------------------------------------------------------------------- @@ -162,7 +163,7 @@ private async Task MapAsync(Topic topic, Relationships relationships, Di /// The target view model with the properties appropriately mapped. /// public async Task MapAsync(Topic topic, object target, Relationships relationships = Relationships.All) => - await MapAsync(topic, target, relationships, new Dictionary()); + await MapAsync(topic, target, relationships, new ConcurrentDictionary()); /// /// Given a topic and an instance of a DTO, will populate the DTO according to the default mapping rules. @@ -179,7 +180,12 @@ public async Task MapAsync(Topic topic, object target, Relationships rel /// /// The target view model with the properties appropriately mapped. /// - private async Task MapAsync(Topic topic, object target, Relationships relationships, Dictionary cache) { + private async Task MapAsync( + Topic topic, + object target, + Relationships relationships, + ConcurrentDictionary cache + ) { /*------------------------------------------------------------------------------------------------------------------------ | Validate input @@ -198,11 +204,11 @@ private async Task MapAsync(Topic topic, object target, Relationships re /*------------------------------------------------------------------------------------------------------------------------ | Handle cached objects \-----------------------------------------------------------------------------------------------------------------------*/ - if (cache.ContainsKey(topic.Id)) { - return cache[topic.Id]; + if (cache.TryGetValue(topic.Id, out var dto)) { + return dto; } else if (topic.Id > 0) { - cache.Add(topic.Id, target); + cache.GetOrAdd(topic.Id, target); } /*------------------------------------------------------------------------------------------------------------------------ @@ -238,7 +244,7 @@ protected async Task SetPropertyAsync( object target, Relationships relationships, PropertyInfo property, - Dictionary cache + ConcurrentDictionary cache ) { /*------------------------------------------------------------------------------------------------------------------------ @@ -365,7 +371,7 @@ protected async Task SetCollectionValueAsync( object target, Relationships relationships, PropertyConfiguration configuration, - Dictionary cache + ConcurrentDictionary cache ) { /*------------------------------------------------------------------------------------------------------------------------ @@ -514,7 +520,7 @@ protected async Task PopulateTargetCollectionAsync( IList sourceList, IList targetList, PropertyConfiguration configuration, - Dictionary cache + ConcurrentDictionary cache ) { /*------------------------------------------------------------------------------------------------------------------------ @@ -595,7 +601,7 @@ protected async Task SetTopicReferenceAsync( Topic source, object target, PropertyConfiguration configuration, - Dictionary cache + ConcurrentDictionary cache ) { var topicDto = await MapAsync(source, configuration.CrawlRelationships, cache); if (topicDto != null && configuration.Property.PropertyType.IsAssignableFrom(topicDto.GetType())) { From 1c5ee2757a7208c09c1240ba076571df7b66b3e5 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 8 May 2018 19:35:43 -0700 Subject: [PATCH 111/149] Made lookups case-insensitive As a best practice, Content Types and View Models should use the same casing. There are some situations where this may not be practical. To avoid this scenario, make sure lookups are case insensitive. --- Ignia.Topics/StaticTypeLookupService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/StaticTypeLookupService.cs b/Ignia.Topics/StaticTypeLookupService.cs index cd04c576..327751b7 100644 --- a/Ignia.Topics/StaticTypeLookupService.cs +++ b/Ignia.Topics/StaticTypeLookupService.cs @@ -32,7 +32,10 @@ public class StaticTypeLookupService: KeyedCollection, ITypeLookup /// /// The list of instances to expose as part of this service. /// The default type to return if no match can be found. Defaults to object. - public StaticTypeLookupService(IEnumerable types = null, Type defaultType = null) { + public StaticTypeLookupService( + IEnumerable types = null, + Type defaultType = null + ): base(StringComparer.InvariantCultureIgnoreCase) { /*---------------------------------------------------------------------------------------------------------------------- | Set default type From ea8ed3a24361c335abcb7e6e63fd6141cb539da4 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:01:01 -0700 Subject: [PATCH 112/149] Bug Fix: Second cache for null type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The second cache attempt is intended to be for the default implementation—i.e., one that is of the type `{ContentType}TopicViewModel`—and to be used when the type is not explicitly specified via e.g. `MapAsync(…)`. It was using the same cache key as the first cache, however. This would actually work in the majority of cases, but to extend it's usefulness this creates a new cache key with a null type for the second cache. This allows the cache to be set even when `MapAsync()` is used to explicitly request the default mapping (otherwise, the first would have been used with `MapAsync()` and the second would have been used with `MapAsync()`, potentially requiring two mappings of the same object). Because the caching logic is complex, also extended the XmlDoc. --- .../Mapping/CachedTopicMappingService.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Ignia.Topics/Mapping/CachedTopicMappingService.cs b/Ignia.Topics/Mapping/CachedTopicMappingService.cs index 9ef050ea..a79eeb2e 100644 --- a/Ignia.Topics/Mapping/CachedTopicMappingService.cs +++ b/Ignia.Topics/Mapping/CachedTopicMappingService.cs @@ -152,6 +152,33 @@ public async Task MapAsync(Topic topic, object target, Relationships rel /// Given a view model, determines if it is appropriate to cache and, if so, adds it to the cache. Regardless, returns the /// view model back to the consumer. /// + /// + /// The internal will potentially add two entries to the cache for every view model. + /// + /// + /// The first will be bound to the , view model , and the mapped. + /// + /// + /// The second will assume a null , and can be used for scenarios where the is + /// not known—and, thus, assumed to be the default mapping. + /// + /// + /// In all cases, the must be greater than zero, to ensure that it's a saved entity (otherwise, + /// multiple distinct entities will have the default of -1). In addition, the following + /// conditions apply, respectively, to each of the caches: + /// + /// + /// The first must have a view model type that is not an , since it is meant to map to a specific + /// view model type. + /// + /// + /// The second will assume that the view model type of the naming convention {ContentType}TopicViewModel—i.e., + /// it is the default implementation for the given view model type, and not a specially cast version such as e.g., + /// NavigationTopicViewModel or TopicViewModel. + /// + /// + /// /// The content type associated with the associated . /// The view model object to cache; can be any POCO object. /// A Tuple{T1, T2, T3} representing the cache key. @@ -160,6 +187,9 @@ private object CacheViewModel(string contentType, object viewModel, Tuple 0 && cacheKey.Item2 != null && !viewModel.GetType().Equals(typeof(object))) { _cache.TryAdd(cacheKey, viewModel); } + if (cacheKey.Item2 != null) { + cacheKey = new Tuple(cacheKey.Item1, null, cacheKey.Item3); + } if (cacheKey.Item1 > 0 && viewModel.GetType().Name.Equals(contentType + "TopicViewModel")) { _cache.TryAdd(cacheKey, viewModel); } From 8f9a07df3562ea8c055fe700ec765a079770af66 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:06:45 -0700 Subject: [PATCH 113/149] Migrated to implicit Tuple format The implicit format is shorter, and removed the benefit of the `GetCacheKey()` helper function to construct new `Tuple<>` instances. All around, this is a bit cleaner. --- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- .../Mapping/CachedTopicMappingService.cs | 28 +++++-------------- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 14 insertions(+), 28 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 35048380..e130f59d 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1770.0")] +[assembly: AssemblyFileVersion("3.5.1771.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index bd0d59e8..63e51450 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index dedb883a..7aedf80a 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1786.0")] +[assembly: AssemblyFileVersion("3.5.1787.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 696577f2..65a6659a 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1769.0")] +[assembly: AssemblyFileVersion("3.5.1770.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 498cc0de..5008da6d 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1770.0")] +[assembly: AssemblyFileVersion("3.5.1771.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 50b4fcd2..b8cd62ea 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -22,6 +22,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/CachedTopicMappingService.cs b/Ignia.Topics/Mapping/CachedTopicMappingService.cs index a79eeb2e..fbae6bd7 100644 --- a/Ignia.Topics/Mapping/CachedTopicMappingService.cs +++ b/Ignia.Topics/Mapping/CachedTopicMappingService.cs @@ -27,8 +27,8 @@ public class CachedTopicMappingService : ITopicMappingService { /*========================================================================================================================== | ESTABLISH CACHE \-------------------------------------------------------------------------------------------------------------------------*/ - private readonly ConcurrentDictionary, object> _cache = - new ConcurrentDictionary, object>(); + private readonly ConcurrentDictionary<(int, Type, Relationships), object> _cache = + new ConcurrentDictionary<(int, Type, Relationships), object>(); /*========================================================================================================================== | CONSTRUCTOR @@ -69,7 +69,7 @@ public async Task MapAsync(Topic topic, Relationships relationships = Re /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - var cacheKey = GetCacheKey(topic.Id, null, relationships); + var cacheKey = (topic.Id, (Type)null, relationships); if(_cache.TryGetValue(cacheKey, out var viewModel)) { return viewModel; } @@ -104,7 +104,7 @@ public async Task MapAsync(Topic topic, Relationships relationships = Re /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - var cacheKey = GetCacheKey(topic.Id, typeof(T), relationships); + var cacheKey = (topic.Id, typeof(T), relationships); if (_cache.TryGetValue(cacheKey, out var viewModel)) { return (T)viewModel; } @@ -133,7 +133,7 @@ public async Task MapAsync(Topic topic, object target, Relationships rel /*---------------------------------------------------------------------------------------------------------------------- | Ensure cache is populated \---------------------------------------------------------------------------------------------------------------------*/ - var cacheKey = GetCacheKey(topic.Id, target.GetType(), relationships); + var cacheKey = (topic.Id, target.GetType(), relationships); if (_cache.TryGetValue(cacheKey, out var viewModel)) { return viewModel; } @@ -183,12 +183,12 @@ public async Task MapAsync(Topic topic, object target, Relationships rel /// The view model object to cache; can be any POCO object. /// A Tuple{T1, T2, T3} representing the cache key. /// The . - private object CacheViewModel(string contentType, object viewModel, Tuple cacheKey) { + private object CacheViewModel(string contentType, object viewModel, (int, Type, Relationships) cacheKey) { if (cacheKey.Item1 > 0 && cacheKey.Item2 != null && !viewModel.GetType().Equals(typeof(object))) { _cache.TryAdd(cacheKey, viewModel); } if (cacheKey.Item2 != null) { - cacheKey = new Tuple(cacheKey.Item1, null, cacheKey.Item3); + cacheKey = (cacheKey.Item1, null, cacheKey.Item3); } if (cacheKey.Item1 > 0 && viewModel.GetType().Name.Equals(contentType + "TopicViewModel")) { _cache.TryAdd(cacheKey, viewModel); @@ -196,19 +196,5 @@ private object CacheViewModel(string contentType, object viewModel, Tuple - /// Given a , , and reference, produces a cache key of - /// type - /// - /// The of the entity to derive the data from. - /// The type of the target object that the will be mapped to. - /// The relationships the mapping will follow, if any. - /// A representing the unique cache key. - private static Tuple GetCacheKey(int topicId, Type type, Relationships relationships) => - new Tuple(topicId, type, relationships); - } //Class } //Namespace diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index dccba9fc..f744cc74 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -23,7 +23,7 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1770.0")] +[assembly: AssemblyFileVersion("3.5.1771.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 731c481ac14a1ff1133111820dea1597c269e802 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:10:14 -0700 Subject: [PATCH 114/149] Configured auto-versioning per project --- .../Ignia.Topics.Data.Caching.csproj | 10 ++++++++++ Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 10 ++++++++++ Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj | 8 ++++---- Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj | 10 ++++++++++ Ignia.Topics.Web/Ignia.Topics.Web.csproj | 10 ++++++++++ Ignia.Topics/Ignia.Topics.csproj | 10 ++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj index 6629e0dd..fc265b9a 100644 --- a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj +++ b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj @@ -46,6 +46,16 @@ Full 0 + True + False + True + True + False + None.None.IncrementOnDemand.None + False + SettingsVersion + None + None.None.IncrementOnDemand.None true diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index 41e8babe..84d7c299 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -20,6 +20,16 @@ + True + False + True + True + False + None.None.IncrementOnDemand.None + False + SettingsVersion + None + None.None.IncrementOnDemand.None true diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index a20067a7..2c80d461 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -14,14 +14,14 @@ True False - False - False + True + True False - - + None.None.IncrementOnDemand.None False SettingsVersion None + None.None.IncrementOnDemand.None true diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index ec2f7bdd..e09bd8a6 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -15,6 +15,16 @@ + True + False + True + True + False + None.None.IncrementOnDemand.None + False + SettingsVersion + None + None.None.IncrementOnDemand.None true diff --git a/Ignia.Topics.Web/Ignia.Topics.Web.csproj b/Ignia.Topics.Web/Ignia.Topics.Web.csproj index ce9a9af4..7d1025c7 100644 --- a/Ignia.Topics.Web/Ignia.Topics.Web.csproj +++ b/Ignia.Topics.Web/Ignia.Topics.Web.csproj @@ -48,6 +48,16 @@ Full 0 CS0618 + True + False + True + True + False + None.None.IncrementOnDemand.None + False + SettingsVersion + None + None.None.IncrementOnDemand.None true diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index 433fd815..c3111cc3 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -47,6 +47,16 @@ Full 0 + True + False + True + True + False + None.None.IncrementOnDemand.None + False + SettingsVersion + None + None.None.IncrementOnDemand.None true From 7cd6c6859982881c733209e5c054928c9150d505 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:40:30 -0700 Subject: [PATCH 115/149] Moved `Title` to `ITopicViewModel` `Title` is actually a property of the base `ContentTypes` content type, and should thus be set on `ITopicViewModel`, not `IPageTopicViewModel`. The majority of view models should need access to a title, as it's a core label. (It is also set on the `TopicViewModel` already.) --- Ignia.Topics/ViewModels/IPageTopicViewModel.cs | 16 ---------------- Ignia.Topics/ViewModels/ITopicViewModel.cs | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Ignia.Topics/ViewModels/IPageTopicViewModel.cs b/Ignia.Topics/ViewModels/IPageTopicViewModel.cs index ba04d010..8b654ba4 100644 --- a/Ignia.Topics/ViewModels/IPageTopicViewModel.cs +++ b/Ignia.Topics/ViewModels/IPageTopicViewModel.cs @@ -31,22 +31,6 @@ public interface IPageTopicViewModel : ITopicViewModel { /// string WebPath { get; set; } - /*========================================================================================================================== - | PROPERTY: TITLE - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Gets or sets the Title attribute, which represents the friendly name of the topic. - /// - /// - /// While the may not contain, for instance, spaces or symbols, there are no - /// restrictions on what characters can be used in the title. For this reason, it provides the default public value for - /// referencing topics. - /// - /// - /// !string.IsNullOrWhiteSpace(value) - /// - string Title { get; set; } - /*========================================================================================================================== | PROPERTY: META KEYWORDS \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/ViewModels/ITopicViewModel.cs b/Ignia.Topics/ViewModels/ITopicViewModel.cs index 93543f70..1d187866 100644 --- a/Ignia.Topics/ViewModels/ITopicViewModel.cs +++ b/Ignia.Topics/ViewModels/ITopicViewModel.cs @@ -90,5 +90,21 @@ public interface ITopicViewModel { /// bool IsHidden { get; set; } + /*========================================================================================================================== + | PROPERTY: TITLE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Gets or sets the Title attribute, which represents the friendly name of the topic. + /// + /// + /// While the may not contain, for instance, spaces or symbols, there are no + /// restrictions on what characters can be used in the title. For this reason, it provides the default public value for + /// referencing topics. + /// + /// + /// !string.IsNullOrWhiteSpace(value) + /// + string Title { get; set; } + } //Class } //Namespace From 0f8edb12339712f12518f8e28b97c09f51722fa2 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:41:11 -0700 Subject: [PATCH 116/149] Added `UniqueKey` to interface definition This is frequently needed, and should be a standard part of any `ITopicViewModel` implementation. --- Ignia.Topics/ViewModels/ITopicViewModel.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Ignia.Topics/ViewModels/ITopicViewModel.cs b/Ignia.Topics/ViewModels/ITopicViewModel.cs index 1d187866..d9e7c19d 100644 --- a/Ignia.Topics/ViewModels/ITopicViewModel.cs +++ b/Ignia.Topics/ViewModels/ITopicViewModel.cs @@ -45,6 +45,17 @@ public interface ITopicViewModel { /// string Key { get; set; } + /*========================================================================================================================== + | PROPERTY: UNIQUE KEY + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Gets or sets the topic's attribute, the unique text identifier for the topic. + /// + /// + /// value != null + /// + string UniqueKey { get; set; } + /*========================================================================================================================== | PROPERTY: CONTENT TYPE \-------------------------------------------------------------------------------------------------------------------------*/ From bb469237077dbc2a7eb9cd79e0f92edb090c3d6a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 11:48:22 -0700 Subject: [PATCH 117/149] Modified `INavigationTyopicViewModel` to derive from `ITopicViewModel` `IPageTopicViewModel` and, more broadly, `PageTopicViewModel`, bring with them a bit of overhead, such as metadata fields and body, which increase processing time and memory, with little expected benefit. Mitigated this by trimming down the `NavigationTopicViewModel` class and its associated interface. --- .../NavigationTopicViewModel.cs | 4 +++- .../Models/NavigationViewModel{T}.cs | 8 ++++---- .../INavigationTopicViewModel{T}.cs | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs b/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs index eeb150ef..5f7445a0 100644 --- a/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs @@ -26,8 +26,10 @@ namespace Ignia.Topics.ViewModels { /// cref="NavigationTopicViewModel"/> class is marked as sealed. /// /// - public sealed class NavigationTopicViewModel : PageTopicViewModel, INavigationTopicViewModel { + public sealed class NavigationTopicViewModel : TopicViewModel, INavigationTopicViewModel { + public string WebPath { get; set; } + public string ShortTitle { get; set; } public Collection Children { get; set; } public bool IsSelected(string uniqueKey) => uniqueKey?.StartsWith(UniqueKey) ?? false; diff --git a/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs b/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs index 59520b3f..0d6a35c2 100644 --- a/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs +++ b/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs @@ -20,13 +20,13 @@ namespace Ignia.Topics.Web.Mvc.Models { /// constructed by the . /// /// - /// The can be any view model that implements , + /// The can be any view model that implements , /// which provides a base level of support for properties associated with the typical Page content type as well as - /// a method for determining if a given instance is the currently-selected - /// topic. Implementations may support additional properties, as appropriate. + /// a method for determining if a given instance is the currently-selected topic. + /// Implementations may support additional properties, as appropriate. /// /// - public class NavigationViewModel where T: IPageTopicViewModel { + public class NavigationViewModel where T: INavigationTopicViewModel { public T NavigationRoot { get; set; } public string CurrentKey { get; set; } diff --git a/Ignia.Topics/ViewModels/INavigationTopicViewModel{T}.cs b/Ignia.Topics/ViewModels/INavigationTopicViewModel{T}.cs index 78e218c8..0023014c 100644 --- a/Ignia.Topics/ViewModels/INavigationTopicViewModel{T}.cs +++ b/Ignia.Topics/ViewModels/INavigationTopicViewModel{T}.cs @@ -17,7 +17,24 @@ namespace Ignia.Topics.ViewModels { /// No topics are expected to have a Navigation content type. Instead, implementers of this view model are expected /// to manually construct instances. /// - public interface INavigationTopicViewModel : IPageTopicViewModel where T: INavigationTopicViewModel { + public interface INavigationTopicViewModel : ITopicViewModel where T: INavigationTopicViewModel { + + /*========================================================================================================================== + | PROPERTY: WEBPATH + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Represents the HTTP routing path for the corresponding . + /// + string WebPath { get; set; } + + /*========================================================================================================================== + | PROPERTY: SHORT TITLE + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// In addition to the Title, a site may opt to define a Short Title used exclusively in the navigation. If present, this + /// value should be used instead of Title. + /// + string ShortTitle { get; set; } /*========================================================================================================================== | PROPERTY: CHILDREN From 0f89cd24b7574544a87d6f4e80aec246a234cb5b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 12:14:31 -0700 Subject: [PATCH 118/149] Added concurrency to `AddNestedTopicsAsync` `AddNestedTopicsAsync()` now implements a local `taskQueue` so that it can process multiple children simultaneously. It also implements a lock pattern so that those children are only added to the source collection once they're fully populated; this avoids potential race conditions with concurrency where duplicates could be added to the cached topic. --- .../Controllers/LayoutControllerBase{T}.cs | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs index 67b0502a..bafa9b0d 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs @@ -4,6 +4,7 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web.Mvc; @@ -198,22 +199,59 @@ protected async Task AddNestedTopicsAsync( bool allowPageGroups = true, int tiers = 1 ) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Validate preconditions + \-----------------------------------------------------------------------------------------------------------------------*/ tiers--; if (sourceTopic == null) { return null; } - var viewModel = await _topicMappingService.MapAsync(sourceTopic, Relationships.None); + + /*------------------------------------------------------------------------------------------------------------------------ + | Establish variables + \-----------------------------------------------------------------------------------------------------------------------*/ + var taskQueue = new List>(); + var children = new List(); + var viewModel = (T)null; + + /*------------------------------------------------------------------------------------------------------------------------ + | Map object + \-----------------------------------------------------------------------------------------------------------------------*/ + viewModel = await _topicMappingService.MapAsync(sourceTopic, Relationships.None); + + /*------------------------------------------------------------------------------------------------------------------------ + | Request mapping of children + \-----------------------------------------------------------------------------------------------------------------------*/ if (tiers >= 0 && (allowPageGroups || !sourceTopic.ContentType.Equals("PageGroup")) && viewModel.Children.Count == 0) { foreach (var topic in sourceTopic.Children.Where(t => t.IsVisible())) { - viewModel.Children.Add( - await AddNestedTopicsAsync( - topic, - allowPageGroups, - tiers - ) - ); + taskQueue.Add(AddNestedTopicsAsync(topic, allowPageGroups, tiers)); } } + + /*------------------------------------------------------------------------------------------------------------------------ + | Process children + \-----------------------------------------------------------------------------------------------------------------------*/ + while (taskQueue.Count > 0 && viewModel.Children.Count == 0) { + var dtoTask = await Task.WhenAny(taskQueue); + taskQueue.Remove(dtoTask); + children.Add(await dtoTask); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Add children to view model + \-----------------------------------------------------------------------------------------------------------------------*/ + if (viewModel.Children.Count == 0) { + lock (viewModel) { + if (viewModel.Children.Count == 0) { + children.ForEach(c => viewModel.Children.Add(c)); + } + } + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Return view model + \-----------------------------------------------------------------------------------------------------------------------*/ return viewModel; } From 66774f7ac5b38931db87d8c223601611b92b3bbc Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 12:15:37 -0700 Subject: [PATCH 119/149] Reconfigured auto-version to auto-increment The increment on build didn't work as expected. Returning to the original configuration, but on a per project basis. This should ensure that only projects that have changed will increment. --- Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj | 4 ++-- Ignia.Topics.Tests/Ignia.Topics.Tests.csproj | 4 ++-- Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj | 4 ++-- Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj | 4 ++-- Ignia.Topics.Web/Ignia.Topics.Web.csproj | 4 ++-- Ignia.Topics/Ignia.Topics.csproj | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj index fc265b9a..3816ab05 100644 --- a/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj +++ b/Ignia.Topics.Data.Caching/Ignia.Topics.Data.Caching.csproj @@ -51,11 +51,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true diff --git a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj index 84d7c299..16447f63 100644 --- a/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj +++ b/Ignia.Topics.Tests/Ignia.Topics.Tests.csproj @@ -25,11 +25,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true diff --git a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj index 2c80d461..473bddfe 100644 --- a/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj +++ b/Ignia.Topics.ViewModels/Ignia.Topics.ViewModels.csproj @@ -17,11 +17,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index e09bd8a6..2dd81364 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -20,11 +20,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true diff --git a/Ignia.Topics.Web/Ignia.Topics.Web.csproj b/Ignia.Topics.Web/Ignia.Topics.Web.csproj index 7d1025c7..e5f3a545 100644 --- a/Ignia.Topics.Web/Ignia.Topics.Web.csproj +++ b/Ignia.Topics.Web/Ignia.Topics.Web.csproj @@ -53,11 +53,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index c3111cc3..de852b3b 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -52,11 +52,11 @@ True True False - None.None.IncrementOnDemand.None + None.None.Increment.None False SettingsVersion None - None.None.IncrementOnDemand.None + None.None.Increment.None true From 4c3faf005c79ad3da470fce0d2c612762f8a71a9 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 15:10:24 -0700 Subject: [PATCH 120/149] Fixed partial match bug in `IsSelected` Previously, `IsSelected()` would mark `/Web/Cat` as being selected if the user was on `/Web/Catacomb` because it relied on a `StartsWith()` query against `UniqueKey`. Fixed this by appending `:` to both the source and the target to ensure that the last `Key` in the `UniqueKey` was a full match, not a partial match. --- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.ViewModels/NavigationTopicViewModel.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 7aedf80a..3bd36a93 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1787.0")] +[assembly: AssemblyVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1791.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs b/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs index 5f7445a0..1645e51c 100644 --- a/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs +++ b/Ignia.Topics.ViewModels/NavigationTopicViewModel.cs @@ -31,7 +31,7 @@ public sealed class NavigationTopicViewModel : TopicViewModel, INavigationTopicV public string WebPath { get; set; } public string ShortTitle { get; set; } public Collection Children { get; set; } - public bool IsSelected(string uniqueKey) => uniqueKey?.StartsWith(UniqueKey) ?? false; + public bool IsSelected(string uniqueKey) => $"{uniqueKey}:"?.StartsWith($"{UniqueKey}:") ?? false; } // Class diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 65a6659a..a29cf867 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1770.0")] +[assembly: AssemblyVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1772.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] From dabc79ff0e089c88abe254881f4e302881eb8f88 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 16:10:26 -0700 Subject: [PATCH 121/149] Established `CachedLayoutControllerBase{T}` Generally, local caching is not an optimal solution; a decorator is much preferred. That said, the `CachedTopicMappingService` decorator is ineffective in this particular case because the `LayoutControllerBase{T}` manually creates children. As a result, instead of the entire view model _graph_ being cached, each individual _item_ is cached. This introduces undesirable behavior when those view model graphs overlap, as e.g. the `INavigationTopicViewModel` for `Menu` might end up with additional children from the `INavigationTopicViewModel` for `PageLevelNavigation`. This is generally not a problem for mapping, but `LayoutControllerBase{}` needs tighter control over the boundaries of its edges than most applications (where unnecessary spillage would not interfere with the functionality). To mitigate this, introduces the `CachedLayoutControllerBase{T}`. To better differentiate between the methods, renamed `AddNestedTopicsAsync()` to `GetViewModelAsync()`, and introduced a new "bootstrap" method, `GetRootModelAsync()`, which kickstarts the process, thus allowing implementers, such as the `CachedLayoutControllerBase`, to differentiate between the root view model, and its descendents. Also updated documentation to call out this behavior. --- .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../CachedLayoutControllerBase{T}.cs | 113 ++++++++++++++++++ .../Controllers/LayoutControllerBase{T}.cs | 35 +++++- .../Ignia.Topics.Web.Mvc.csproj | 1 + .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Web.Mvc/README.md | 1 + Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 +- Ignia.Topics/Mapping/README.md | 12 +- Ignia.Topics/Properties/AssemblyInfo.cs | 4 +- 11 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 Ignia.Topics.Web.Mvc/Controllers/CachedLayoutControllerBase{T}.cs diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index e130f59d..661b1a59 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1771.0")] +[assembly: AssemblyVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1773.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 3bd36a93..3b5f8fe4 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1743.0")] -[assembly: AssemblyFileVersion("3.5.1791.0")] +[assembly: AssemblyVersion("3.5.1747.0")] +[assembly: AssemblyFileVersion("3.5.1795.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index a29cf867..2ea16efd 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1741.0")] -[assembly: AssemblyFileVersion("3.5.1772.0")] +[assembly: AssemblyVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1773.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Controllers/CachedLayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/CachedLayoutControllerBase{T}.cs new file mode 100644 index 00000000..d6721bdf --- /dev/null +++ b/Ignia.Topics.Web.Mvc/Controllers/CachedLayoutControllerBase{T}.cs @@ -0,0 +1,113 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Ignia.Topics.Mapping; +using Ignia.Topics.Repositories; +using Ignia.Topics.ViewModels; + +namespace Ignia.Topics.Web.Mvc.Controllers { + + /*============================================================================================================================ + | CLASS: CACHED LAYOUT CONTROLLER + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Provides access to views for populating specific layout dependencies, such as the , while caching + /// the graphs for performance. + /// + /// + /// + /// As a best practice, global data required by the layout view are requested independently of the current page. This + /// allows each layout element to be provided with its own layout data, in the form of s, instead of needing to add this data to every view model returned by . The facilitates this by not only providing a default + /// implementation for , but additionally providing protected helper methods that aid in locating and + /// assembling and references that are relevant to + /// specific layout elements. + /// + /// + /// In order to remain view model agnostic, the does not assume that a particular view + /// model will be used, and instead accepts a generic argument for any view model that implements the interface . Since generic controllers cannot be effectively routed to, however, that means + /// implementors must, at minimum, provide a local instance of which sets the generic + /// value to the desired view model. To help enforce this, while avoiding ambiguity, this class is marked as + /// abstract and suffixed with Base. + /// + /// + /// By comparison to the , the will + /// automatically cache the graph for each action that uses the protected method to construct the graph. This is preferable over using e.g. + /// the since the requires tight control + /// over the shape of the graph. For instance, using a generic caching + /// decorator for the mapping might result in the edges of the action being expanded due to other + /// actions reusing cached instances (e.g., for page-level navigation). To mitigate this, the handles top-level caching at the level of the navigation root. + /// + /// + public abstract class CachedLayoutControllerBase : LayoutControllerBase + where T : class, INavigationTopicViewModel, new() { + + /*========================================================================================================================== + | STATIC VARIABLES + \-------------------------------------------------------------------------------------------------------------------------*/ + private static ConcurrentDictionary _cache = new ConcurrentDictionary(); + + /*========================================================================================================================== + | CONSTRUCTOR + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Initializes a new instance of a Topic Controller with necessary dependencies. + /// + /// A topic controller for loading OnTopic views. + protected CachedLayoutControllerBase( + ITopicRepository topicRepository, + ITopicRoutingService topicRoutingService, + ITopicMappingService topicMappingService + ) : base(topicRepository, topicRoutingService, topicMappingService) {} + + /*========================================================================================================================== + | GET ROOT VIEW MODEL (ASYNC) + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Given a , maps a , as well as of + /// . If the graph has been + /// mapped before, then a cached instance is returned. Optionally excludes instance with the + /// ContentType of PageGroup. + /// + /// The to pull the values from. + /// Determines whether s should be crawled. + /// Determines how many tiers of children should be included in the graph. + protected override async Task GetRootViewModelAsync( + Topic sourceTopic, + bool allowPageGroups = true, + int tiers = 1 + ) { + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle empty results + \-----------------------------------------------------------------------------------------------------------------------*/ + if (sourceTopic == null) { + return await Task.FromResult(null); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Handle cache hits + \-----------------------------------------------------------------------------------------------------------------------*/ + if (_cache.TryGetValue(sourceTopic.Id, out var dto)) { + return await Task.FromResult(dto); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Cache and return new version + \-----------------------------------------------------------------------------------------------------------------------*/ + var viewModel = await GetViewModelAsync(sourceTopic, allowPageGroups, tiers); + return _cache.GetOrAdd(sourceTopic.Id, viewModel); + + } + + } // Class + +} // Namespace \ No newline at end of file diff --git a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs index bafa9b0d..8b4a5ed9 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs @@ -117,7 +117,7 @@ public async virtual Task Menu() { | Construct view model \-----------------------------------------------------------------------------------------------------------------------*/ var navigationViewModel = new NavigationViewModel() { - NavigationRoot = await AddNestedTopicsAsync(navigationRootTopic, false, 3), + NavigationRoot = await GetRootViewModelAsync(navigationRootTopic, false, 3), CurrentKey = CurrentTopic?.GetUniqueKey() }; @@ -186,15 +186,40 @@ private static int DistanceFromRoot(Topic sourceTopic) { } /*========================================================================================================================== - | ADD NESTED TOPICS + | GET ROOT VIEW MODEL (ASYNC) \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// A helper function that allows a set number of tiers to be added to a tree. + /// Given a , maps a , as well as of + /// . Optionally excludes instance with the + /// ContentType of PageGroup. + /// + /// + /// In the out-of-the-box implementation, and provide the same functionality. It is recommended that actions call + /// , however, as it allows implementers the flexibility to + /// differentiate between the root view model (which the client application will be binding to) and any child view models + /// (which the client application may optionally iterate over). + /// + /// The to pull the values from. + /// Determines whether s should be crawled. + /// Determines how many tiers of children should be included in the graph. + protected virtual async Task GetRootViewModelAsync( + Topic sourceTopic, + bool allowPageGroups = true, + int tiers = 1 + ) => await GetViewModelAsync(sourceTopic, allowPageGroups, tiers); + + /*========================================================================================================================== + | GET VIEW MODEL (ASYNC) + \-------------------------------------------------------------------------------------------------------------------------*/ + /// Given a , maps a , as well as of + /// . Optionally excludes instance with the + /// ContentType of PageGroup. /// /// The to pull the values from. /// Determines whether s should be crawled. /// Determines how many tiers of children should be included in the graph. - protected async Task AddNestedTopicsAsync( + protected async Task GetViewModelAsync( Topic sourceTopic, bool allowPageGroups = true, int tiers = 1 @@ -225,7 +250,7 @@ protected async Task AddNestedTopicsAsync( \-----------------------------------------------------------------------------------------------------------------------*/ if (tiers >= 0 && (allowPageGroups || !sourceTopic.ContentType.Equals("PageGroup")) && viewModel.Children.Count == 0) { foreach (var topic in sourceTopic.Children.Where(t => t.IsVisible())) { - taskQueue.Add(AddNestedTopicsAsync(topic, allowPageGroups, tiers)); + taskQueue.Add(GetViewModelAsync(topic, allowPageGroups, tiers)); } } diff --git a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj index 2dd81364..0a5c026c 100644 --- a/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj +++ b/Ignia.Topics.Web.Mvc/Ignia.Topics.Web.Mvc.csproj @@ -77,6 +77,7 @@ + diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 5008da6d..da6ea678 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1771.0")] +[assembly: AssemblyVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1777.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web.Mvc/README.md b/Ignia.Topics.Web.Mvc/README.md index b1982524..ec3b7b81 100644 --- a/Ignia.Topics.Web.Mvc/README.md +++ b/Ignia.Topics.Web.Mvc/README.md @@ -24,6 +24,7 @@ There are six main controllers that ship with the MVC implementation. In additio - **`ErrorControllerBase`**: Provides support for `Error`, `NotFound`, and `InternalServer` actions. Can accept any `IPageTopicViewModel` as a generic argument; that will be used as the view model. - **`FallbackController`**: Used in a [Controller Factory](#controller-factory) as a fallback, in case no other controllers can accept the request. Simply returns a `NotFoundResult` with a predefined message. - **`LayoutControllerBase`**: Provides support for a navigation menu by automatically mapping the top three tiers of the current namespace (e.g., `Web`, its children, and grandchildren). Can accept any `INavigationTopicViewModel` as a generic argument; that will be used as the view model for each mapped instance. + - **`CachedLayoutControllerBase`**: Introduces specialized caching of `INavigationTopicViewModel` graphs whenever a call to the `GetNavigationRoot()` is called. This avoids mapping the entirety of the navigation on each request. - **`RedirectController`**: Provides a single `Redirect` action which can be bound to a route such as `/Topic/{ID}/`; this provides support for permanent URLs that are independent of the `GetWebPath()`. - **`SitemapController`**: Provides a single `Sitemap` action which returns a reference to the `ITopicRepository`, thus allowing a sitemap view to recurse over the entire Topic graph, including all attributes. diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index b8cd62ea..925ebd3e 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1763.0")] +[assembly: AssemblyVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1765.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Mapping/README.md b/Ignia.Topics/Mapping/README.md index 0ecababf..7a5a89cc 100644 --- a/Ignia.Topics/Mapping/README.md +++ b/Ignia.Topics/Mapping/README.md @@ -156,7 +156,7 @@ This can be useful for filtering a collection. For instance, if a `CompanyTopicV While it's not a best practice, this also works for strongly-typed collections of `Topic` objects. Typically, collections should return view models, but if the collection is strongly-typed to `Topic` (or a derivative) then the source `Topic` will not be mapped, and will be used as-is assuming it implements (or derives from) the target `Topic` type. This can be useful for scenarios where a view needs full access to the object graph (such as the `SitemapController`). In such cases, it is impractical to map the entirety of an object graph, along with all attributes, to a corresponding view model graph, and makes more sense to simply return the `Topic` graph. ## Caching -By default, the `TopicMappingService` will cache a reference to all types discovered that end with `TopicViewModel`, as well as all `MemberInfo` objects associated with each of those types. That mitigates much of the performance hit associated with the use of reflection. Despite that, simply setting properties—and, especially, on large object graphs—can require a lot of processing time. To mitigate this, OnTopic also offers two approaches. +By default, the `TopicMappingService` will cache a reference to all `MemberInfo` objects associated with each of view model it maps. That mitigates much of the performance hit associated with the use of reflection. Despite that, simply setting properties—and, especially, on large object graphs—can require a lot of processing time. To address this, OnTopic also offers two approaches. ### Internal Caching When a request is made to `TopicMappingService`, and internal cache is constructed. If any mapping requests refer to a `Topic` that's already been mapped as part of the _current_ object graph, then that object will be returned. This prevents unnecessary duplication of mapping, and also avoids the potential for infinite loops. For instance, if a view model includes `Children`, and those children are set to `[Follow(Relationships.Parents)]`, the `TopicMappingService` will point back to the originally-mapped `Parent` object, instead of mapping a new instance of that `Topic`. @@ -174,6 +174,12 @@ var topicMappingService = new TopicMappingService(topicRepository); var cachedTopicMappingService = new CachedTopicMappingService(topicMappingService); ``` -> *Note:* Be aware that the `CachedTopicMappingService` may take up considerable memory, depending on how many permutations of mapped objects the application has. This is especially true since it caches each unique object graph; no effort is made to centralize references to e.g. relationships that reference the same object instance. +> _**Important**_: Due to limitations discussed below, the application of the `CachedTopicMappingService` is quite restricted. It is likely inapprorpiate for page content, since that wouldn't reflect changes made via the editor. And it isn't appropriate for e.g. the `LayoutControllerBase{T}`, since it manually constructs its tree. -> *Note:* The `CachedTopicMappingService` makes no effort to validate or evict cache entries. Topics whose values change during the lifetime of the `CachedTopicMappingService` will not be reflected in the mapped responses. \ No newline at end of file + +#### Limitations +While the `CachedTopicMappingService` can be useful for particular scenarios, it introduces several limitations that should be accounted for. + +1. It may take up considerable memory, depending on how many permutations of mapped objects the application has. This is especially true since it caches each unique object graph; no effort is made to centralize object instances referenced by e.g. relationships in multiple graphs. +2. It makes no effort to validate or evict cache entries. Topics whose values change during the lifetime of the `CachedTopicMappingService` will not be reflected in the mapped responses. +3. If a graph is manually constructed (by e.g. programmatically mapping `Children`) then each instance will be separated cached, thus potentially allowing an instance to be shared between multiple graphs. This can introduce concerns if edge maintenance is important. diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f744cc74..c8f27060 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] -[assembly: AssemblyFileVersion("3.5.1771.0")] +[assembly: AssemblyVersion("3.5.1741.0")] +[assembly: AssemblyFileVersion("3.5.1773.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 3e2780b92894b950d8855e7c4163f032b16efc43 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 17:12:12 -0700 Subject: [PATCH 122/149] Reduced `FakeTopicRepository`'s descendents Was previously creating 27 topics under `/Web`. Reduced this to 12 to shore up performance of the tests. (It doesn't reduce it that dramatically, but it does help.) --- .../Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics.Tests/ITopicRepositoryTest.cs | 28 +++++++++--------- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +-- .../TestDoubles/FakeTopicRepository.cs | 4 ++- Ignia.Topics.Tests/TopicControllerTest.cs | 29 +++++++++---------- Ignia.Topics.Tests/TopicRoutingServiceTest.cs | 8 ++--- .../Properties/AssemblyInfo.cs | 4 +-- .../Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics/Properties/AssemblyInfo.cs | 4 +-- 10 files changed, 46 insertions(+), 47 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 661b1a59..39078fc1 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1741.0")] -[assembly: AssemblyFileVersion("3.5.1773.0")] +[assembly: AssemblyVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1775.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/ITopicRepositoryTest.cs b/Ignia.Topics.Tests/ITopicRepositoryTest.cs index 0903daed..d9beac59 100644 --- a/Ignia.Topics.Tests/ITopicRepositoryTest.cs +++ b/Ignia.Topics.Tests/ITopicRepositoryTest.cs @@ -121,17 +121,17 @@ public void ITopicRepository_MoveTest() { var rootTopic = _topicRepository.Load(); var source = _topicRepository.Load("Root:Web:Web_0"); var destination = _topicRepository.Load("Root:Web:Web_1"); - var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_2"); + var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1"); Assert.ReferenceEquals(topic.Parent, source); - Assert.AreEqual(3, destination.Children.Count()); - Assert.AreEqual(3, source.Children.Count()); + Assert.AreEqual(2, destination.Children.Count()); + Assert.AreEqual(2, source.Children.Count()); _topicRepository.Move(topic, destination); Assert.ReferenceEquals(topic.Parent, destination); - Assert.AreEqual(2, source.Children.Count()); - Assert.AreEqual(4, destination.Children.Count()); + Assert.AreEqual(1, source.Children.Count()); + Assert.AreEqual(3, destination.Children.Count()); } @@ -151,12 +151,12 @@ public void ITopicRepository_MoveToSiblingTest() { Assert.ReferenceEquals(topic.Parent, parent); Assert.AreEqual("Web_0_0", parent.Children.First().Key); - Assert.AreEqual(3, parent.Children.Count()); + Assert.AreEqual(2, parent.Children.Count()); _topicRepository.Move(topic, parent, sibling); Assert.ReferenceEquals(topic.Parent, parent); - Assert.AreEqual(3, parent.Children.Count()); + Assert.AreEqual(2, parent.Children.Count()); Assert.AreEqual("Web_0_1", parent.Children.First().Key); Assert.AreEqual("Web_0_0", parent.Children[1].Key); @@ -171,18 +171,18 @@ public void ITopicRepository_MoveToSiblingTest() { [TestMethod] public void ITopicRepository_DeleteTest() { - var parent = _topicRepository.Load("Root:Web:Web_2"); - var topic = _topicRepository.Load("Root:Web:Web_2:Web_2_2"); - var child = _topicRepository.Load("Root:Web:Web_2:Web_2_2:Web_2_2_0"); + var parent = _topicRepository.Load("Root:Web:Web_1"); + var topic = _topicRepository.Load("Root:Web:Web_1:Web_1_1"); + var child = _topicRepository.Load("Root:Web:Web_1:Web_1_1:Web_1_1_0"); - Assert.AreEqual(3, parent.Children.Count()); + Assert.AreEqual(2, parent.Children.Count()); _topicRepository.Delete(topic); - Assert.AreEqual(2, parent.Children.Count()); + Assert.AreEqual(1, parent.Children.Count()); - topic = _topicRepository.Load("Root:Web:Web_2:Web_2_2"); - child = _topicRepository.Load("Root:Web:Web_2:Web_2_2:Web_2_2_0"); + topic = _topicRepository.Load("Root:Web:Web_1:Web_1_1"); + child = _topicRepository.Load("Root:Web:Web_1:Web_1_1:Web_1_1_0"); Assert.IsNull(topic); Assert.IsNull(child); diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 3b5f8fe4..baa7b51c 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1747.0")] -[assembly: AssemblyFileVersion("3.5.1795.0")] +[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1810.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index 55c65840..462d9ec4 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -4,7 +4,9 @@ | Project Topics Library \=============================================================================================================================*/ using System; +using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Threading.Tasks; using Ignia.Topics.Collections; using Ignia.Topics.Repositories; @@ -251,7 +253,7 @@ private void CreateFakeData() { \-----------------------------------------------------------------------------------------------------------------------*/ var web = TopicFactory.Create("Web", "Page", 10000, rootTopic); - CreateFakeData(web, 3, 3); + CreateFakeData(web, 2, 3); /*------------------------------------------------------------------------------------------------------------------------ | Set to cache diff --git a/Ignia.Topics.Tests/TopicControllerTest.cs b/Ignia.Topics.Tests/TopicControllerTest.cs index e37232e3..ca72efa0 100644 --- a/Ignia.Topics.Tests/TopicControllerTest.cs +++ b/Ignia.Topics.Tests/TopicControllerTest.cs @@ -34,6 +34,9 @@ public class TopicControllerTest { | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ ITopicRepository _topicRepository = null; + RouteData _routeData = new RouteData(); + Uri _uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); + Topic _topic = null; /*========================================================================================================================== | CONSTRUCTOR @@ -45,10 +48,12 @@ public class TopicControllerTest { /// This uses the to provide data, and then to /// manage the in-memory representation of the data. While this introduces some overhead to the tests, the latter is a /// relatively lightweight façade to any , and prevents the need to duplicate logic for - /// crawling the object graph. + /// crawling the object graph. In addition, it initializes a shared reference to use for the various + /// tests. /// public TopicControllerTest() { - _topicRepository = new CachedTopicRepository(new FakeTopicRepository()); + _topicRepository = new CachedTopicRepository(new FakeTopicRepository()); + _topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); } /*========================================================================================================================== @@ -60,15 +65,11 @@ public TopicControllerTest() { [TestMethod] public async Task TopicController_IndexTestAsync() { - var routes = new RouteData(); - var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); - var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); - - var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); + var topicRoutingService = new MvcTopicRoutingService(_topicRepository, _uri, _routeData); var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var controller = new TopicController(_topicRepository, topicRoutingService, mappingService); - var result = await controller.IndexAsync(topic.GetWebPath()) as TopicViewResult; + var result = await controller.IndexAsync(_topic.GetWebPath()) as TopicViewResult; var model = result.Model as PageTopicViewModel; Assert.IsNotNull(model); @@ -201,11 +202,7 @@ public void SitemapController_IndexTest() { [TestMethod] public async Task LayoutController_MenuTest() { - var routes = new RouteData(); - var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); - var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); - - var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); + var topicRoutingService = new MvcTopicRoutingService(_topicRepository, _uri, _routeData); var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var controller = new LayoutController(_topicRepository, topicRoutingService, mappingService); @@ -213,10 +210,10 @@ public async Task LayoutController_MenuTest() { var model = result.Model as NavigationViewModel; Assert.IsNotNull(model); - Assert.AreEqual(topic.GetUniqueKey(), model.CurrentKey); + Assert.AreEqual(_topic.GetUniqueKey(), model.CurrentKey); Assert.AreEqual("Root:Web", model.NavigationRoot.UniqueKey); - Assert.AreEqual(3, model.NavigationRoot.Children.Count()); - Assert.IsTrue(model.NavigationRoot.IsSelected(topic.GetUniqueKey())); + Assert.AreEqual(2, model.NavigationRoot.Children.Count()); + Assert.IsTrue(model.NavigationRoot.IsSelected(_topic.GetUniqueKey())); } diff --git a/Ignia.Topics.Tests/TopicRoutingServiceTest.cs b/Ignia.Topics.Tests/TopicRoutingServiceTest.cs index a778e56f..edae1cb7 100644 --- a/Ignia.Topics.Tests/TopicRoutingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicRoutingServiceTest.cs @@ -57,14 +57,14 @@ public void TopicRoutingService_TopicRouteTest() { var topic = _topicRepository.Load("Root:Web:Web_0:Web_0_1:Web_0_1_1"); routes.Values.Add("rootTopic", "Web"); - routes.Values.Add("path", "Web_0/Web_0_1/Web_0_1_2"); + routes.Values.Add("path", "Web_0/Web_0_1/Web_0_1_1"); var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); var currentTopic = topicRoutingService.GetCurrentTopic(); Assert.IsNotNull(currentTopic); Assert.ReferenceEquals(topic, currentTopic); - Assert.AreEqual("Web_0_1_2", currentTopic.Key); + Assert.AreEqual("Web_0_1_1", currentTopic.Key); } @@ -105,14 +105,14 @@ public void TopicRoutingService_RoutesTest() { var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); routes.Values.Add("rootTopic", "Web"); - routes.Values.Add("path", "Web_0/Web_0_1/Web_0_1_2"); + routes.Values.Add("path", "Web_0/Web_0_1/Web_0_1_1"); var topicRoutingService = new MvcTopicRoutingService(_topicRepository, uri, routes); var currentTopic = topicRoutingService.GetCurrentTopic(); Assert.IsNotNull(currentTopic); Assert.AreEqual("Web", routes.GetRequiredString("rootTopic")); - Assert.AreEqual("Web_0/Web_0_1/Web_0_1_2", routes.GetRequiredString("path")); + Assert.AreEqual("Web_0/Web_0_1/Web_0_1_1", routes.GetRequiredString("path")); Assert.AreEqual("Page", routes.GetRequiredString("contenttype")); } diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 2ea16efd..7898a431 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1742.0")] -[assembly: AssemblyFileVersion("3.5.1773.0")] +[assembly: AssemblyVersion("3.5.1744.0")] +[assembly: AssemblyFileVersion("3.5.1775.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index da6ea678..5bf425bc 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1745.0")] -[assembly: AssemblyFileVersion("3.5.1777.0")] +[assembly: AssemblyVersion("3.5.1748.0")] +[assembly: AssemblyFileVersion("3.5.1780.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 925ebd3e..72b4997a 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1741.0")] -[assembly: AssemblyFileVersion("3.5.1765.0")] +[assembly: AssemblyVersion("3.5.1742.0")] +[assembly: AssemblyFileVersion("3.5.1766.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index c8f27060..4470c810 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1741.0")] -[assembly: AssemblyFileVersion("3.5.1773.0")] +[assembly: AssemblyVersion("3.5.1743.0")] +[assembly: AssemblyFileVersion("3.5.1775.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 4dfa9256ed3ce75068c0222a3bc9684535812589 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Wed, 9 May 2018 17:12:54 -0700 Subject: [PATCH 123/149] Fixed generic test The "generic" test wasn't actually testing the generic overload. Also, removed some unnecessary `SetValue()` instances that weren't being tested. --- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index 3ba392b4..b659b32a 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -47,11 +47,10 @@ public TopicMappingServiceTest() { } /*========================================================================================================================== - | TEST: MAP (INSTANCE) + | TEST: MAP (GENERIC) \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Establishes a and tests setting basic scalar values by providing it with an already - /// constructed instance of a DTO. + /// Establishes a and tests setting basic scalar values by specifying an explicit type. /// [TestMethod] public async Task TopicMappingService_MapGeneric() { @@ -63,7 +62,7 @@ public async Task TopicMappingService_MapGeneric() { topic.Attributes.SetValue("Title", "Value1"); topic.Attributes.SetValue("IsHidden", "1"); - var target = (PageTopicViewModel)await mappingService.MapAsync(topic, new PageTopicViewModel()); + var target = await mappingService.MapAsync(topic); Assert.AreEqual("ValueA", target.MetaTitle); Assert.AreEqual("Value1", target.Title); @@ -115,10 +114,8 @@ public async Task TopicMappingService_MapParents() { topic.Attributes.SetValue("IsHidden", "1"); parent.Attributes.SetValue("Title", "Value2"); - parent.Attributes.SetValue("IsHidden", "1"); grandParent.Attributes.SetValue("Title", "Value3"); - grandParent.Attributes.SetValue("IsHidden", "1"); grandParent.Attributes.SetValue("Property", "ValueB"); var viewModel = (PageTopicViewModel) await mappingService.MapAsync(topic); From 5f43dd65dd62a689848d14a3da7b653f028720f4 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Thu, 10 May 2018 11:30:38 -0700 Subject: [PATCH 124/149] Simplified test names Removed the class name prefix (since it's listed in the class name, and tests can be grouped by class) and removed the "Test" suffix (since these are all test methods in test classes, this should be implicit). We may revisit these again in the future, but for now they're more succinct and easier to read in Visual Studio's Test Explorer. --- .../AttributeValueCollectionTest.cs | 24 ++++----- Ignia.Topics.Tests/ITopicRepositoryTest.cs | 12 ++--- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +- .../RelatedTopicCollectionTest.cs | 10 ++-- Ignia.Topics.Tests/TopicCollectionTest.cs | 6 +-- Ignia.Topics.Tests/TopicControllerTest.cs | 24 ++++----- Ignia.Topics.Tests/TopicMappingServiceTest.cs | 50 +++++++++---------- Ignia.Topics.Tests/TopicRoutingServiceTest.cs | 6 +-- Ignia.Topics.Tests/TopicTest.cs | 24 ++++----- Ignia.Topics.Tests/TypeCollectionTest.cs | 12 ++--- 10 files changed, 86 insertions(+), 86 deletions(-) diff --git a/Ignia.Topics.Tests/AttributeValueCollectionTest.cs b/Ignia.Topics.Tests/AttributeValueCollectionTest.cs index 7af5937b..a2c2e887 100644 --- a/Ignia.Topics.Tests/AttributeValueCollectionTest.cs +++ b/Ignia.Topics.Tests/AttributeValueCollectionTest.cs @@ -26,7 +26,7 @@ public class AttributeValueCollectionTest { /// Creates a new topic and ensures that the key can be returned as an attribute. /// [TestMethod] - public void AttributeValueCollection_GetValueTest() { + public void GetValue() { var topic = TopicFactory.Create("Test", "Container"); Assert.AreEqual("Test", topic.Attributes.GetValue("Key")); } @@ -38,7 +38,7 @@ public void AttributeValueCollection_GetValueTest() { /// Ensures that integer values can be set and retrieved as expected. /// [TestMethod] - public void AttributeValueCollection_GetIntegerTest() { + public void GetInteger() { var topic = TopicFactory.Create("Test", "Container"); @@ -60,7 +60,7 @@ public void AttributeValueCollection_GetIntegerTest() { /// Ensures that integer values can be set and retrieved as expected. /// [TestMethod] - public void AttributeValueCollection_GetDateTimeTest() { + public void GetDateTime() { var topic = TopicFactory.Create("Test", "Container"); var dateTime1 = new DateTime(1976, 10, 15); @@ -84,7 +84,7 @@ public void AttributeValueCollection_GetDateTimeTest() { /// Ensures that boolean values can be set and retrieved as expected. /// [TestMethod] - public void AttributeValueCollection_GetBooleanTest() { + public void GetBoolean() { var topic = TopicFactory.Create("Test", "Container"); @@ -110,7 +110,7 @@ public void AttributeValueCollection_GetBooleanTest() { /// Creates a new topic and requests an invalid attribute; ensures falls back to the default. /// [TestMethod] - public void AttributeValueCollection_DefaultValueTest() { + public void DefaultValue() { var topic = TopicFactory.Create("Test", "Container"); Assert.AreEqual("Foo", topic.Attributes.GetValue("InvalidAttribute", "Foo")); } @@ -122,7 +122,7 @@ public void AttributeValueCollection_DefaultValueTest() { /// Sets a custom attribute on a topic and ensures it can be retrieved. /// [TestMethod] - public void AttributeValueCollection_SetValueTest() { + public void SetValue() { var topic = TopicFactory.Create("Test", "Container"); topic.Attributes.SetValue("Foo", "Bar"); Assert.AreEqual("Bar", topic.Attributes.GetValue("Foo")); @@ -135,7 +135,7 @@ public void AttributeValueCollection_SetValueTest() { /// Modifies the value of a custom attribute on a topic and ensures it is marked as IsDirty. /// [TestMethod] - public void AttributeValueCollection_SetValue_IsDirtyTest() { + public void SetValue_IsDirtyTest() { var topic = TopicFactory.Create("Test", "Container"); @@ -159,7 +159,7 @@ public void AttributeValueCollection_SetValue_IsDirtyTest() { /// retrieved. /// [TestMethod] - public void AttributeValueCollection_SetValue_BackdoorTest() { + public void SetValue_BackdoorTest() { var topic = TopicFactory.Create("Test", "Container"); @@ -179,7 +179,7 @@ public void AttributeValueCollection_SetValue_BackdoorTest() { /// [TestMethod] [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a back door, without routing it through the Key property.")] - public void AttributeValueCollection_EnforceBusinessLogicTest() { + public void EnforceBusinessLogic() { var topic = TopicFactory.Create("Test", "Container"); topic.Attributes.SetValue("Key", "# ?"); } @@ -192,7 +192,7 @@ public void AttributeValueCollection_EnforceBusinessLogicTest() { /// [TestMethod] [ExpectedException(typeof(TargetInvocationException), "The topic allowed a key to be set via a back door, without routing it through the Key property.")] - public void AttributeValueCollection_EnforceBusinessLogic_BackdoorTest() { + public void EnforceBusinessLogic_Backdoor() { var topic = TopicFactory.Create("Test", "Container"); topic.Attributes.Remove("Key"); topic.Attributes.Add(new AttributeValue("Key", "# ?")); @@ -205,7 +205,7 @@ public void AttributeValueCollection_EnforceBusinessLogic_BackdoorTest() { /// Sets an attribute on the parent of a topic and ensures it can be retrieved using inheritance. /// [TestMethod] - public void AttributeValueCollection_InheritFromParentTest() { + public void InheritFromParent() { var topics = new Topic[8]; @@ -230,7 +230,7 @@ public void AttributeValueCollection_InheritFromParentTest() { /// Establishes a long tree of derives topics, and ensures that inheritance will pursue no more than five hops. /// [TestMethod] - public void AttributeValueCollection_MaxHopsTest() { + public void MaxHops() { var topics = new Topic[8]; diff --git a/Ignia.Topics.Tests/ITopicRepositoryTest.cs b/Ignia.Topics.Tests/ITopicRepositoryTest.cs index d9beac59..8a1fa9c6 100644 --- a/Ignia.Topics.Tests/ITopicRepositoryTest.cs +++ b/Ignia.Topics.Tests/ITopicRepositoryTest.cs @@ -54,7 +54,7 @@ public ITopicRepositoryTest() { /// Loads topics and ensures there are the expected number of children. /// [TestMethod] - public void ITopicRepository_LoadTest() { + public void Load() { var rootTopic = _topicRepository.Load(); var topic = _topicRepository.Load("Root:Configuration:ContentTypes:Page"); @@ -73,7 +73,7 @@ public void ITopicRepository_LoadTest() { /// Loads topic by ID and ensures it is found. /// [TestMethod] - public void ITopicRepository_LoadByIdTest() { + public void LoadById() { var topic = _topicRepository.Load(11111); @@ -89,7 +89,7 @@ public void ITopicRepository_LoadByIdTest() { /// Saves topics and ensures their identifiers are properly set. /// [TestMethod] - public void ITopicRepository_SaveTest() { + public void Save() { var rootTopic = _topicRepository.Load(); var web = _topicRepository.Load("Root:Web"); @@ -116,7 +116,7 @@ public void ITopicRepository_SaveTest() { /// Moves topics and ensures their parents are correctly set. /// [TestMethod] - public void ITopicRepository_MoveTest() { + public void Move() { var rootTopic = _topicRepository.Load(); var source = _topicRepository.Load("Root:Web:Web_0"); @@ -142,7 +142,7 @@ public void ITopicRepository_MoveTest() { /// Moves topic next to a different sibling and ensures it ends up in the correct location. /// [TestMethod] - public void ITopicRepository_MoveToSiblingTest() { + public void MoveToSibling() { var rootTopic = _topicRepository.Load(); var parent = _topicRepository.Load("Root:Web:Web_0"); @@ -169,7 +169,7 @@ public void ITopicRepository_MoveToSiblingTest() { /// Deletes a topic to ensure it is properly removed. /// [TestMethod] - public void ITopicRepository_DeleteTest() { + public void Delete() { var parent = _topicRepository.Load("Root:Web:Web_1"); var topic = _topicRepository.Load("Root:Web:Web_1:Web_1_1"); diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index baa7b51c..4a0bc393 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1762.0")] -[assembly: AssemblyFileVersion("3.5.1810.0")] +[assembly: AssemblyVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1811.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/RelatedTopicCollectionTest.cs b/Ignia.Topics.Tests/RelatedTopicCollectionTest.cs index 577ad25d..584eae75 100644 --- a/Ignia.Topics.Tests/RelatedTopicCollectionTest.cs +++ b/Ignia.Topics.Tests/RelatedTopicCollectionTest.cs @@ -26,7 +26,7 @@ public class RelatedTopicCollectionTest { /// Sets a relationship and confirms that it is accessible. /// [TestMethod] - public void RelatedTopicCollection_SetTopic() { + public void SetTopic() { var parent = TopicFactory.Create("Parent", "Page"); var related = TopicFactory.Create("Related", "Page"); @@ -44,7 +44,7 @@ public void RelatedTopicCollection_SetTopic() { /// Sets a relationship and confirms that it is accessible on incoming relationships property. /// [TestMethod] - public void RelatedTopicCollection_IncomingRelationshipTest() { + public void IncomingRelationship() { var parent = TopicFactory.Create("Parent", "Page"); var related = TopicFactory.Create("Related", "Page"); @@ -63,7 +63,7 @@ public void RelatedTopicCollection_IncomingRelationshipTest() { /// Sets relationships in multiple namespaces, and the correct number of keys are returned. /// [TestMethod] - public void RelatedTopicCollection_KeysTest() { + public void Keys() { var parent = TopicFactory.Create("Parent", "Page"); var relationships = new RelatedTopicCollection(parent); @@ -84,7 +84,7 @@ public void RelatedTopicCollection_KeysTest() { /// Sets relationships in multiple namespaces, and ensures they are all returned via GetAllTopics(). /// [TestMethod] - public void RelatedTopicCollection_GetAllTopicsTest() { + public void GetAllTopics() { var parent = TopicFactory.Create("Parent", "Page"); var relationships = new RelatedTopicCollection(parent); @@ -107,7 +107,7 @@ public void RelatedTopicCollection_GetAllTopicsTest() { /// by content type. /// [TestMethod] - public void RelatedTopicCollection_GetAllContentTypesTest() { + public void GetAllContentTypes() { var parent = TopicFactory.Create("Parent", "Page"); var relationships = new RelatedTopicCollection(parent); diff --git a/Ignia.Topics.Tests/TopicCollectionTest.cs b/Ignia.Topics.Tests/TopicCollectionTest.cs index a10715a4..0d81c2c1 100644 --- a/Ignia.Topics.Tests/TopicCollectionTest.cs +++ b/Ignia.Topics.Tests/TopicCollectionTest.cs @@ -26,7 +26,7 @@ public class TopicCollectionTest { /// Establishes a number of topics, then accesses them by key. /// [TestMethod] - public void TopicCollection_SetTopicTest() { + public void SetTopic() { var topics = new TopicCollection(); @@ -45,7 +45,7 @@ public void TopicCollection_SetTopicTest() { /// Establishes a number of topics, then seeds a new with them. /// [TestMethod] - public void TopicCollection_PrepopulateTest() { + public void Prepopulate() { var topics = new List(); @@ -66,7 +66,7 @@ public void TopicCollection_PrepopulateTest() { /// Establishes a number of topics, converts the collection to read only, and ensures they are still present. /// [TestMethod] - public void TopicCollection_AsReadOnlyTest() { + public void AsReadOnly() { var topics = new TopicCollection(); diff --git a/Ignia.Topics.Tests/TopicControllerTest.cs b/Ignia.Topics.Tests/TopicControllerTest.cs index ca72efa0..f86da300 100644 --- a/Ignia.Topics.Tests/TopicControllerTest.cs +++ b/Ignia.Topics.Tests/TopicControllerTest.cs @@ -60,10 +60,10 @@ public TopicControllerTest() { | TEST: TOPIC \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Triggers the action. + /// Triggers the action. /// [TestMethod] - public async Task TopicController_IndexTestAsync() { + public async Task TopicController_IndexAsync() { var topicRoutingService = new MvcTopicRoutingService(_topicRepository, _uri, _routeData); var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -81,10 +81,10 @@ public async Task TopicController_IndexTestAsync() { | TEST: ERROR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Triggers the action. + /// Triggers the action. /// [TestMethod] - public void ErrorController_ErrorTest() { + public void ErrorController_Error() { var controller = new ErrorController(); var result = controller.Error("ErrorPage") as ViewResult; @@ -99,10 +99,10 @@ public void ErrorController_ErrorTest() { | TEST: NOT FOUND ERROR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Triggers the action. + /// Triggers the action. /// [TestMethod] - public void ErrorController_NotFoundTest() { + public void ErrorController_NotFound() { var controller = new ErrorController(); var result = controller.Error("NotFoundPage") as ViewResult; @@ -117,10 +117,10 @@ public void ErrorController_NotFoundTest() { | TEST: INTERNAL SERVER ERROR \-------------------------------------------------------------------------------------------------------------------------*/ /// - /// Triggers the action. + /// Triggers the action. /// [TestMethod] - public void ErrorController_InternalServerTest() { + public void ErrorController_InternalServer() { var controller = new ErrorController(); var result = controller.Error("InternalServer") as ViewResult; @@ -138,7 +138,7 @@ public void ErrorController_InternalServerTest() { /// Triggers the action. /// [TestMethod] - public void FallbackController_IndexTest() { + public void FallbackController_Index() { var controller = new FallbackController(); var result = controller.Index() as HttpNotFoundResult; @@ -156,7 +156,7 @@ public void FallbackController_IndexTest() { /// Triggers the action. /// [TestMethod] - public void RedirectController_TopicRedirectTest() { + public void RedirectController_TopicRedirect() { var controller = new RedirectController(_topicRepository); var result = controller.Redirect(11110) as RedirectResult; @@ -180,7 +180,7 @@ public void RedirectController_TopicRedirectTest() { /// [TestMethod] [ExpectedException(typeof(NullReferenceException), AllowDerivedTypes=false)] - public void SitemapController_IndexTest() { + public void SitemapController_Index() { var controller = new SitemapController(_topicRepository); var result = controller.Index() as ViewResult; @@ -200,7 +200,7 @@ public void SitemapController_IndexTest() { /// Triggers the action. /// [TestMethod] - public async Task LayoutController_MenuTest() { + public async Task Menu() { var topicRoutingService = new MvcTopicRoutingService(_topicRepository, _uri, _routeData); var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); diff --git a/Ignia.Topics.Tests/TopicMappingServiceTest.cs b/Ignia.Topics.Tests/TopicMappingServiceTest.cs index b659b32a..a6ef8f35 100644 --- a/Ignia.Topics.Tests/TopicMappingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicMappingServiceTest.cs @@ -53,7 +53,7 @@ public TopicMappingServiceTest() { /// Establishes a and tests setting basic scalar values by specifying an explicit type. /// [TestMethod] - public async Task TopicMappingService_MapGeneric() { + public async Task MapGeneric() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); @@ -78,7 +78,7 @@ public async Task TopicMappingService_MapGeneric() { /// determine the instance type. /// [TestMethod] - public async Task TopicMappingService_MapDynamic() { + public async Task MapDynamic() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Page"); @@ -102,7 +102,7 @@ public async Task TopicMappingService_MapDynamic() { /// Establishes a and tests whether it successfully crawls the parent tree. /// [TestMethod] - public async Task TopicMappingService_MapParents() { + public async Task MapParents() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Sample"); @@ -142,7 +142,7 @@ public async Task TopicMappingService_MapParents() { /// . /// [TestMethod] - public async Task TopicMappingService_InheritValues() { + public async Task InheritValues() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var grandParent = TopicFactory.Create("Grandparent", "Page"); @@ -167,7 +167,7 @@ public async Task TopicMappingService_InheritValues() { /// specified by . /// [TestMethod] - public async Task TopicMappingService_AlternateAttributeKey() { + public async Task AlternateAttributeKey() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -188,7 +188,7 @@ public async Task TopicMappingService_AlternateAttributeKey() { /// Establishes a and tests whether it successfully crawls the relationships. /// [TestMethod] - public async Task TopicMappingService_MapRelationships() { + public async Task MapRelationships() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -224,7 +224,7 @@ public async Task TopicMappingService_MapRelationships() { /// RelationshipAlias, and b) source from the collection. /// [TestMethod] - public async Task TopicMappingService_AlternateRelationship() { + public async Task AlternateRelationship() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -260,7 +260,7 @@ public async Task TopicMappingService_AlternateRelationship() { /// Establishes a and tests whether it successfully crawls the nested topics. /// [TestMethod] - public async Task TopicMappingService_MapNestedTopics() { + public async Task MapNestedTopics() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -286,7 +286,7 @@ public async Task TopicMappingService_MapNestedTopics() { /// Establishes a and tests whether it successfully crawls the nested topics. /// [TestMethod] - public async Task TopicMappingService_MapChildren() { + public async Task MapChildren() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -313,7 +313,7 @@ public async Task TopicMappingService_MapChildren() { /// Establishes a and tests whether it successfully maps referenced topics. /// [TestMethod] - public async Task TopicMappingService_MapTopicReferences() { + public async Task MapTopicReferences() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -336,7 +336,7 @@ public async Task TopicMappingService_MapTopicReferences() { /// instructions of each model class. /// [TestMethod] - public async Task TopicMappingService_RecursiveRelationships() { + public async Task RecursiveRelationships() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -389,7 +389,7 @@ public async Task TopicMappingService_RecursiveRelationships() { /// collection of objects (from which . /// [TestMethod] - public async Task TopicMappingService_MapSlideshow() { + public async Task MapSlideshow() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Slideshow"); @@ -417,7 +417,7 @@ public async Task TopicMappingService_MapSlideshow() { /// instances if called for by the model. This isn't a best practice, but is maintained for edge cases. /// [TestMethod] - public async Task TopicMappingService_MapTopics() { + public async Task MapTopics() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var relatedTopic1 = TopicFactory.Create("RelatedTopic1", "Page"); @@ -448,7 +448,7 @@ public async Task TopicMappingService_MapTopics() { /// . /// [TestMethod] - public async Task TopicMappingService_MapMetadata() { + public async Task MapMetadata() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "MetadataLookup"); @@ -467,7 +467,7 @@ public async Task TopicMappingService_MapMetadata() { /// taking advantage of its internal caching mechanism. /// [TestMethod] - public async Task TopicMappingService_MapCircularReference() { + public async Task MapCircularReference() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -488,7 +488,7 @@ public async Task TopicMappingService_MapCircularReference() { /// cref="SampleTopicViewModel.Children"/> property can be filtered by . /// [TestMethod] - public async Task TopicMappingService_FilterByContentType() { + public async Task FilterByContentType() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Sample"); @@ -516,7 +516,7 @@ public async Task TopicMappingService_FilterByContentType() { /// correctly populated. /// [TestMethod] - public async Task TopicMappingService_MapGetterMethods() { + public async Task MapGetterMethods() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Sample"); @@ -536,7 +536,7 @@ public async Task TopicMappingService_MapGetterMethods() { /// Maps a content type that has a required property. Ensures that an error is not thrown if it is set. /// [TestMethod] - public async Task TopicMappingService_MapRequiredProperty() { + public async Task MapRequiredProperty() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); @@ -557,7 +557,7 @@ public async Task TopicMappingService_MapRequiredProperty() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public async Task TopicMappingService_MapRequiredPropertyException() { + public async Task MapRequiredPropertyException() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "Required"); @@ -574,7 +574,7 @@ public async Task TopicMappingService_MapRequiredPropertyException() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public async Task TopicMappingService_MapRequiredObjectPropertyException() { + public async Task MapRequiredObjectPropertyException() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "RequiredObject"); @@ -590,7 +590,7 @@ public async Task TopicMappingService_MapRequiredObjectPropertyException() { /// Maps a content type that has default properties. Ensures that each is set appropriately. /// [TestMethod] - public async Task TopicMappingService_MapDefaultValueProperties() { + public async Task MapDefaultValueProperties() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "DefaultValue"); @@ -611,7 +611,7 @@ public async Task TopicMappingService_MapDefaultValueProperties() { /// [TestMethod] [ExpectedException(typeof(ValidationException))] - public async Task TopicMappingService_MapMinimumValueProperties() { + public async Task MapMinimumValueProperties() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Topic", "MinimumLengthProperty"); @@ -631,7 +631,7 @@ public async Task TopicMappingService_MapMinimumValueProperties() { /// instances. /// [TestMethod] - public async Task TopicMappingService_FilterByAttribute() { + public async Task FilterByAttribute() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var topic = TopicFactory.Create("Test", "Filtered"); @@ -659,7 +659,7 @@ public async Task TopicMappingService_FilterByAttribute() { /// cref="FlattenChildrenTopicViewModel.Children"/> property is properly flattened. /// [TestMethod] - public async Task TopicMappingService_Flatten() { + public async Task Flatten() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); @@ -686,7 +686,7 @@ public async Task TopicMappingService_Flatten() { /// the same instance of a mapped object is turned after two calls. /// [TestMethod] - public async Task TopicMappingService_Caching() { + public async Task Caching() { var mappingService = new TopicMappingService(_topicRepository, new FakeViewModelLookupService()); var cachedMappingService = new CachedTopicMappingService(mappingService); diff --git a/Ignia.Topics.Tests/TopicRoutingServiceTest.cs b/Ignia.Topics.Tests/TopicRoutingServiceTest.cs index edae1cb7..26f6da4b 100644 --- a/Ignia.Topics.Tests/TopicRoutingServiceTest.cs +++ b/Ignia.Topics.Tests/TopicRoutingServiceTest.cs @@ -50,7 +50,7 @@ public TopicRoutingServiceTest() { /// Establishes route data and ensures that a topic is correctly identified based on that route. /// [TestMethod] - public void TopicRoutingService_TopicRouteTest() { + public void TopicRoute() { var routes = new RouteData(); var uri = new Uri("http://localhost/Topics/Web/Web_0/Web_0_1/Web_0_1_1"); @@ -75,7 +75,7 @@ public void TopicRoutingService_TopicRouteTest() { /// Establishes a URI based on a path and ensures that a topic is correctly identified based on that URI. /// [TestMethod] - public void TopicRoutingService_TopicUriTest() { + public void TopicUri() { var routes = new RouteData(); var uri = new Uri("http://localhost/Web/Web_0/Web_0_1/Web_0_1_1"); @@ -98,7 +98,7 @@ public void TopicRoutingService_TopicUriTest() { /// . /// [TestMethod] - public void TopicRoutingService_RoutesTest() { + public void Routes() { var routes = new RouteData(); var rootTopic = _topicRepository.Load(); diff --git a/Ignia.Topics.Tests/TopicTest.cs b/Ignia.Topics.Tests/TopicTest.cs index 9d9cb4ca..2850ccbe 100644 --- a/Ignia.Topics.Tests/TopicTest.cs +++ b/Ignia.Topics.Tests/TopicTest.cs @@ -27,7 +27,7 @@ public class TopicTest { /// Creates a topic using the factory method, and ensures it's correctly returned. /// [TestMethod] - public void Topic_CreateTest() { + public void Create() { var topic = TopicFactory.Create("Test", "ContentTypeDescriptor"); Assert.IsNotNull(topic); Assert.IsInstanceOfType(topic, typeof(ContentTypeDescriptor)); @@ -43,7 +43,7 @@ public void Topic_CreateTest() { /// [TestMethod] [ExpectedException(typeof(ArgumentException), "Topic permitted the ID to be reset; this should never happen.")] - public void Topic_Change_IdTest() { + public void Change_IdTest() { var topic = TopicFactory.Create("Test", "ContentTypeDescriptor", 123); topic.Id = 124; @@ -61,7 +61,7 @@ public void Topic_Change_IdTest() { /// types. /// [TestMethod] - public void Topic_IsContentTypeOf() { + public void IsContentTypeOf() { var contentType = (ContentTypeDescriptor)TopicFactory.Create("Root", "ContentTypeDescriptor"); for (var i=0; i<5; i++) { @@ -80,7 +80,7 @@ public void Topic_IsContentTypeOf() { /// Sets the parent of a topic and ensures it is correctly reflected in the object model. /// [TestMethod] - public void Topic_Set_ParentTest() { + public void Set_ParentTest() { var parentTopic = TopicFactory.Create("Parent", "ContentTypeDescriptor"); var childTopic = TopicFactory.Create("Child", "ContentTypeDescriptor"); @@ -100,7 +100,7 @@ public void Topic_Set_ParentTest() { /// Changes the parent of a topic and ensures it is correctly reflected in the object model. /// [TestMethod] - public void Topic_Change_ParentTest() { + public void Change_ParentTest() { var sourceParent = TopicFactory.Create("SourceParent", "ContentTypeDescriptor"); var targetParent = TopicFactory.Create("TargetParent", "ContentTypeDescriptor"); @@ -124,7 +124,7 @@ public void Topic_Change_ParentTest() { /// Ensures the Unique Key is correct for a deeply nested child. /// [TestMethod] - public void Topic_UniqueKeyTest() { + public void UniqueKey() { var parentTopic = TopicFactory.Create("ParentTopic", "Page"); var childTopic = TopicFactory.Create("ChildTopic", "Page"); @@ -145,7 +145,7 @@ public void Topic_UniqueKeyTest() { /// Looks for a deeply nested child topic using only the attribute value. /// [TestMethod] - public void Topic_FindAllByAttributeValueTest() { + public void FindAllByAttributeValue() { var parentTopic = TopicFactory.Create("ParentTopic", "Page", 1); var childTopic = TopicFactory.Create("ChildTopic", "Page", 5); @@ -175,7 +175,7 @@ public void Topic_FindAllByAttributeValueTest() { /// Ensures that IsVisible returns expected values based on IsHidden and IsDisabled. /// [TestMethod] - public void Topic_IsVisibleTest() { + public void IsVisible() { var hiddenTopic = TopicFactory.Create("HiddenTopic", "Page"); var disabledTopic = TopicFactory.Create("DisabledTopic", "Page"); @@ -200,7 +200,7 @@ public void Topic_IsVisibleTest() { /// Ensures that the title falls back appropriately. /// [TestMethod] - public void Topic_TitleTest() { + public void Title() { var untitledTopic = TopicFactory.Create("UntitledTopic", "Page"); var titledTopic = TopicFactory.Create("TitledTopic", "Page"); @@ -219,7 +219,7 @@ public void Topic_TitleTest() { /// Returns the last modified date using a couple of techniques, and ensures it's returned correctly. /// [TestMethod] - public void Topic_LastModifiedTest() { + public void LastModified() { var topic1 = TopicFactory.Create("Topic1", "Page"); var topic2 = TopicFactory.Create("Topic2", "Page"); @@ -245,7 +245,7 @@ public void Topic_LastModifiedTest() { /// Sets a derived topic, and ensures it is referenced correctly. /// [TestMethod] - public void Topic_DerivedTopicTest() { + public void DerivedTopic() { var topic = TopicFactory.Create("Topic", "Page"); var derivedTopic = TopicFactory.Create("DerivedTopic", "Page"); @@ -264,7 +264,7 @@ public void Topic_DerivedTopicTest() { /// correctly parsed via the property. /// [TestMethod] - public void Topic_AttributeConfigurationTest() { + public void AttributeConfiguration() { var attribute = (AttributeDescriptor)TopicFactory.Create("Topic", "AttributeDescriptor"); attribute.DefaultConfiguration = "IsRequired=\"True\" DisplayName=\"Display Name\""; diff --git a/Ignia.Topics.Tests/TypeCollectionTest.cs b/Ignia.Topics.Tests/TypeCollectionTest.cs index 6f37f5c7..18f923db 100644 --- a/Ignia.Topics.Tests/TypeCollectionTest.cs +++ b/Ignia.Topics.Tests/TypeCollectionTest.cs @@ -32,7 +32,7 @@ public class TypeCollectionTest { /// returning expected types. /// [TestMethod] - public void PropertyInfoCollection_ConstructorTest() { + public void Constructor() { var properties = new MemberInfoCollection(typeof(ContentTypeDescriptor)); @@ -51,7 +51,7 @@ public void PropertyInfoCollection_ConstructorTest() { /// functions. /// [TestMethod] - public void TypeCollection_GetPropertiesTest() { + public void GetProperties() { var types = new TypeCollection(); @@ -72,7 +72,7 @@ public void TypeCollection_GetPropertiesTest() { /// correctly returns the expected properties. /// [TestMethod] - public void TypeCollection_GetMemberTest() { + public void GetMember() { var types = new TypeCollection(); @@ -93,7 +93,7 @@ public void TypeCollection_GetMemberTest() { /// method. /// [TestMethod] - public void TypeCollection_SetPropertyTest() { + public void SetProperty() { var types = new TypeCollection(); var topic = TopicFactory.Create("Test", "ContentType"); @@ -128,7 +128,7 @@ public void TypeCollection_SetPropertyTest() { /// method. /// [TestMethod] - public void TypeCollection_SetMethodTest() { + public void SetMethod() { var types = new TypeCollection(); var source = new MethodBasedViewModel(); @@ -158,7 +158,7 @@ public void TypeCollection_SetMethodTest() { /// the number of iterations, simply increment the "totalIterations" variable. /// [TestMethod] - public void TypeCollection_ReflectionPerformanceTest() { + public void ReflectionPerformance() { var totalIterations = 1; var types = new TypeCollection(); From 7cd6dc94266ae98397bff194a977d1f11d4946a8 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 13:28:36 -0700 Subject: [PATCH 125/149] Fixed name of `ContentTypeDescriptor` content type Since we removed the legacy `ContentType` classes, the database(s) need to be updated to use `ContentType=ContentTypeDescriptor` instead of `ContentType=ContentType`. This wasn't accounted for in the `GetContentTypeDescriptors()` method. --- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 17 ++++++++++------- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 39078fc1..7f0e0daf 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1743.0")] -[assembly: AssemblyFileVersion("3.5.1775.0")] +[assembly: AssemblyVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1777.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index c3e554de..e182c904 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -282,22 +282,25 @@ public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { \---------------------------------------------------------------------------------------------------------------------*/ var configuration = Load("Configuration"); - /*-------------------------------------------------------------------------------------------------------------------- + /*---------------------------------------------------------------------------------------------------------------------- | Add available Content Types to the collection - \-------------------------------------------------------------------------------------------------------------------*/ + \---------------------------------------------------------------------------------------------------------------------*/ _contentTypeDescriptors = new ContentTypeDescriptorCollection(); - /*-------------------------------------------------------------------------------------------------------------------- + /*---------------------------------------------------------------------------------------------------------------------- | Ensure the parent ContentTypes topic is available to iterate over - \-------------------------------------------------------------------------------------------------------------------*/ + \---------------------------------------------------------------------------------------------------------------------*/ if (configuration.Children.GetTopic("ContentTypes") == null) { throw new Exception("Unable to load section Configuration:ContentTypes."); } - /*-------------------------------------------------------------------------------------------------------------------- + /*---------------------------------------------------------------------------------------------------------------------- | Add available Content Types to the collection - \-------------------------------------------------------------------------------------------------------------------*/ - foreach (var topic in configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", "ContentType")) { + \---------------------------------------------------------------------------------------------------------------------*/ + foreach ( + var topic in + configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", nameof(ContentTypeDescriptor)) + ) { // Ensure the Topic is used as the strongly-typed ContentType // Add ContentType Topic to collection if not already added if ( diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 4a0bc393..cc5d44a9 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1763.0")] -[assembly: AssemblyFileVersion("3.5.1811.0")] +[assembly: AssemblyVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1813.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 7898a431..846da99e 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1744.0")] -[assembly: AssemblyFileVersion("3.5.1775.0")] +[assembly: AssemblyVersion("3.5.1746.0")] +[assembly: AssemblyFileVersion("3.5.1777.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 5bf425bc..2e384fa9 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1748.0")] -[assembly: AssemblyFileVersion("3.5.1780.0")] +[assembly: AssemblyVersion("3.5.1750.0")] +[assembly: AssemblyFileVersion("3.5.1782.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 72b4997a..1002e964 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1742.0")] -[assembly: AssemblyFileVersion("3.5.1766.0")] +[assembly: AssemblyVersion("3.5.1744.0")] +[assembly: AssemblyFileVersion("3.5.1768.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 4470c810..05a5502f 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1743.0")] -[assembly: AssemblyFileVersion("3.5.1775.0")] +[assembly: AssemblyVersion("3.5.1745.0")] +[assembly: AssemblyFileVersion("3.5.1777.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From 32f93228690cf54b3e13915ec5f5365a8b0905e6 Mon Sep 17 00:00:00 2001 From: Katherine Trunkey Date: Tue, 22 May 2018 13:31:48 -0700 Subject: [PATCH 126/149] Added null conditional operator to `sourceTopic` Added check for cases where `sourceTopic` may be null (e.g., for legacy pages not associated with a Topic). --- Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs index 8b4a5ed9..bf77f130 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/LayoutControllerBase{T}.cs @@ -178,7 +178,7 @@ protected Topic GetNavigationRoot(Topic currentTopic, int fromRoot = 2, string d /// The to pull the values from. private static int DistanceFromRoot(Topic sourceTopic) { var distance = 1; - while (sourceTopic.Parent != null) { + while (sourceTopic?.Parent != null) { sourceTopic = sourceTopic.Parent; distance++; } From c61d2a5c4bbf9035aeebc474f8672b6be127630c Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 14:41:03 -0700 Subject: [PATCH 127/149] Removed `IsHidden` This wasn't actually adding any value, and was causing a conflict with reflection (since it exists on both `AttributeDescriptor` and `Attribute`). Removed. --- Ignia.Topics/AttributeDescriptor.cs | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/Ignia.Topics/AttributeDescriptor.cs b/Ignia.Topics/AttributeDescriptor.cs index 6fe8b4a9..e99a1f01 100644 --- a/Ignia.Topics/AttributeDescriptor.cs +++ b/Ignia.Topics/AttributeDescriptor.cs @@ -197,33 +197,6 @@ public string GetConfigurationValue(string key, string defaultValue = null) { return defaultValue; } - /*========================================================================================================================== - | PROPERTY: IS HIDDEN? - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Gets or sets whether the attribute should be hidden in the editor. - /// - /// - /// - /// By default, all attributes associated with a are rendered in the editor. - /// Optionally, however, attributes can be set to be hidden. This is particularly advantageous when subtyping a Content - /// Type Descriptor, as some parent attributes may not be necessary for child content types (e.g., they may be - /// implicitly assigned). It c be valuable for attributes that are intended to be managed by the system, and not via the - /// editor (e.g., a timestamp or version). - /// - /// - /// The property does not hide the attribute from the library itself or the views. If the view - /// associated with the property renders the attribute (e.g., via ) then the attribute will be displayed on the page. The - /// property is used exclusively by the editor. - /// - /// - [AttributeSetter] - public new bool IsHidden { - get => Attributes.GetBoolean("IsHidden", false); - set => SetAttributeValue("IsHidden", value? "1" : "0"); - } - /*========================================================================================================================== | PROPERTY: IS REQUIRED? \-------------------------------------------------------------------------------------------------------------------------*/ From fb1625fb6446616434fe74d8a84c7f8766377a6d Mon Sep 17 00:00:00 2001 From: Katherine Trunkey Date: Tue, 22 May 2018 14:54:01 -0700 Subject: [PATCH 128/149] Cleaned up formatting --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index e182c904..687b5084 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -297,10 +297,7 @@ public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { /*---------------------------------------------------------------------------------------------------------------------- | Add available Content Types to the collection \---------------------------------------------------------------------------------------------------------------------*/ - foreach ( - var topic in - configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", nameof(ContentTypeDescriptor)) - ) { + foreach (var topic in configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", "ContentType")) { // Ensure the Topic is used as the strongly-typed ContentType // Add ContentType Topic to collection if not already added if ( From 4e47c591a9ebcdefee80229f499209453091e81e Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 16:39:52 -0700 Subject: [PATCH 129/149] Throw error on duplicate Under expected circumstances, a duplicate `MemberInfo` key should not exist. This will happen, however, if the `new` keyword is used on a member of the _current_ class (as opposed to a _derived_ class). In that case, an error will occur. This should not happen; if it does, the developer is warned about the duplicate key with a specific error message. --- Ignia.Topics/Reflection/MemberInfoCollection{T}.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs index bcc434f3..3dcc228c 100644 --- a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs +++ b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs @@ -40,7 +40,12 @@ in type.GetMembers( BindingFlags.Public ).Where(m => typeof(T).IsAssignableFrom(m.GetType())) ) { - Add((T)member); + if (!Contains(member.Name)) { + Add((T)member); + } + else { + throw new ArgumentException("The Type '" + type.Name + "' already contains the MemberInfo '" + member.Name + "'"); + } } } From 13f83ece7d312add5faf68cc40744a2abc740359 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 17:14:00 -0700 Subject: [PATCH 130/149] Moved duplicate exception to `InsertItem()` By overriding `InsertItem()` we ensure that the same `ArgumentException` error is thrown _anytime_ an item is added using e.g. `Add()`, `Insert()`, &c. without needing to add guard conditions to each instance, or worry about members being added independent of construction. The latter isn't an expected use case, but even without that, it prevents the need to duplicate the guard condition for both constructors. --- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- .../Reflection/MemberInfoCollection{T}.cs | 22 ++++++++++++++----- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 7f0e0daf..08eb95fa 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1745.0")] -[assembly: AssemblyFileVersion("3.5.1777.0")] +[assembly: AssemblyVersion("3.5.1749.0")] +[assembly: AssemblyFileVersion("3.5.1781.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index cc5d44a9..98ff5fa4 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1765.0")] -[assembly: AssemblyFileVersion("3.5.1813.0")] +[assembly: AssemblyVersion("3.5.1769.0")] +[assembly: AssemblyFileVersion("3.5.1817.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 846da99e..9903b4dd 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1746.0")] -[assembly: AssemblyFileVersion("3.5.1777.0")] +[assembly: AssemblyVersion("3.5.1750.0")] +[assembly: AssemblyFileVersion("3.5.1781.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 2e384fa9..01991de9 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1750.0")] -[assembly: AssemblyFileVersion("3.5.1782.0")] +[assembly: AssemblyVersion("3.5.1754.0")] +[assembly: AssemblyFileVersion("3.5.1786.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 1002e964..c047ee68 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1744.0")] -[assembly: AssemblyFileVersion("3.5.1768.0")] +[assembly: AssemblyVersion("3.5.1748.0")] +[assembly: AssemblyFileVersion("3.5.1772.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 05a5502f..f419d957 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1745.0")] -[assembly: AssemblyFileVersion("3.5.1777.0")] +[assembly: AssemblyVersion("3.5.1749.0")] +[assembly: AssemblyFileVersion("3.5.1781.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs index 3dcc228c..d313d01e 100644 --- a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs +++ b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs @@ -40,12 +40,7 @@ in type.GetMembers( BindingFlags.Public ).Where(m => typeof(T).IsAssignableFrom(m.GetType())) ) { - if (!Contains(member.Name)) { - Add((T)member); - } - else { - throw new ArgumentException("The Type '" + type.Name + "' already contains the MemberInfo '" + member.Name + "'"); - } + Add((T)member); } } @@ -66,6 +61,21 @@ internal MemberInfoCollection(Type type, IEnumerable members) : base(StringCo } } + /*========================================================================================================================== + | OVERRIDE: INSERT ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Returns the type associated with this collection. + /// + protected override void InsertItem(int index, T item) { + if (!Contains(item.Name)) { + base.InsertItem(index, item); + } + else { + throw new ArgumentException("The Type '" + Type.Name + "' already contains the MemberInfo '" + item.Name + "'"); + } + } + /*========================================================================================================================== | PROPERTY: TYPE \-------------------------------------------------------------------------------------------------------------------------*/ From 5490fdb5837a54d62654715c39f95192780087de Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 17:27:13 -0700 Subject: [PATCH 131/149] Corrected XmlDoc on `InsertItem` Updated XmlDoc documentation for `InsertItem` to correctly describe the method, parameters, exceptions, and purpose (via remarks). The previous version inadvertently included the summary from a different method. --- .../Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Reflection/MemberInfoCollection{T}.cs | 14 ++++++++++---- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 08eb95fa..e56160c7 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1749.0")] -[assembly: AssemblyFileVersion("3.5.1781.0")] +[assembly: AssemblyVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1783.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 98ff5fa4..8384c60e 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1769.0")] -[assembly: AssemblyFileVersion("3.5.1817.0")] +[assembly: AssemblyVersion("3.5.1771.0")] +[assembly: AssemblyFileVersion("3.5.1819.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 9903b4dd..33dfaa60 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1750.0")] -[assembly: AssemblyFileVersion("3.5.1781.0")] +[assembly: AssemblyVersion("3.5.1752.0")] +[assembly: AssemblyFileVersion("3.5.1783.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 01991de9..8c3bb4f5 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1754.0")] -[assembly: AssemblyFileVersion("3.5.1786.0")] +[assembly: AssemblyVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1788.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index c047ee68..da70724e 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1748.0")] -[assembly: AssemblyFileVersion("3.5.1772.0")] +[assembly: AssemblyVersion("3.5.1750.0")] +[assembly: AssemblyFileVersion("3.5.1774.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index f419d957..00d6489b 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1749.0")] -[assembly: AssemblyFileVersion("3.5.1781.0")] +[assembly: AssemblyVersion("3.5.1751.0")] +[assembly: AssemblyFileVersion("3.5.1783.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs index d313d01e..68a9542d 100644 --- a/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs +++ b/Ignia.Topics/Reflection/MemberInfoCollection{T}.cs @@ -64,15 +64,21 @@ internal MemberInfoCollection(Type type, IEnumerable members) : base(StringCo /*========================================================================================================================== | OVERRIDE: INSERT ITEM \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Returns the type associated with this collection. - /// + /// Fires any time an item is added to the collection. + /// + /// Compared to the base implementation, will throw a specific error if a duplicate key is + /// inserted. This conveniently provides the name of the and the 's , so it's clear what is being duplicated. + /// + /// The zero-based index at which should be inserted. + /// The instance to insert. + /// The Type '{Type.Name}' already contains the MemberInfo '{item.Name}' protected override void InsertItem(int index, T item) { if (!Contains(item.Name)) { base.InsertItem(index, item); } else { - throw new ArgumentException("The Type '" + Type.Name + "' already contains the MemberInfo '" + item.Name + "'"); + throw new ArgumentException($"The Type '{Type.Name}' already contains the MemberInfo '{item.Name}'"); } } From 7b2e7ad6cc5f28a674e63da71d7f4a01b8f75e04 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:03:43 -0700 Subject: [PATCH 132/149] Initialized `_cache_ as part of constructor Whenever a `CachedTopicRepository` is called, it checked to see if the `_cache` was initialized. This should not be necessary. If a `CachedTopicRepository` is required, then it should immediately load its cache so it's ready for the first request. In most scenarios, this won't change the load time (as the `Load()` method will be called shortly after establishing the cache), but it does simplify the internal logic. --- .../CachedTopicRepository.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs index 696fef0d..d922b11f 100644 --- a/Ignia.Topics.Data.Caching/CachedTopicRepository.cs +++ b/Ignia.Topics.Data.Caching/CachedTopicRepository.cs @@ -51,6 +51,11 @@ public CachedTopicRepository(ITopicRepository dataProvider) : base() { \-----------------------------------------------------------------------------------------------------------------------*/ _dataProvider = dataProvider; + /*------------------------------------------------------------------------------------------------------------------------ + | Ensure topics are loaded + \-----------------------------------------------------------------------------------------------------------------------*/ + _cache = _dataProvider.Load(); + } /*========================================================================================================================== @@ -72,13 +77,6 @@ public CachedTopicRepository(ITopicRepository dataProvider) : base() { /// A topic object. public override Topic Load(int topicId, bool isRecursive = true) { - /*------------------------------------------------------------------------------------------------------------------------ - | Ensure topics are loaded - \-----------------------------------------------------------------------------------------------------------------------*/ - if (_cache == null) { - _cache = _dataProvider.Load(); - } - /*------------------------------------------------------------------------------------------------------------------------ | Handle request for entire tree \-----------------------------------------------------------------------------------------------------------------------*/ @@ -101,13 +99,6 @@ public override Topic Load(int topicId, bool isRecursive = true) { /// A topic object. public override Topic Load(string topicKey = null, bool isRecursive = true) { - /*------------------------------------------------------------------------------------------------------------------------ - | Ensure topics are loaded - \-----------------------------------------------------------------------------------------------------------------------*/ - if (_cache == null) { - _cache = _dataProvider.Load(); - } - /*------------------------------------------------------------------------------------------------------------------------ | Lookup by TopicKey \-----------------------------------------------------------------------------------------------------------------------*/ @@ -218,8 +209,8 @@ private Topic GetTopic(Topic sourceTopic, string uniqueKey) { /// Boolean indicator as to the topic's publishing status. /// The integer return value from the execution of the topics_UpdateTopic stored procedure. /// - /// The Content Type topic.Attributes.GetValue(ContentType, Page) referenced by topic.Key could not be found under - /// Configuration:ContentTypes. There are TopicRepository.ContentTypes.Count ContentTypes in the Repository. + /// The Content Type topic.Attributes.GetValue(ContentType, Page) referenced by topic.Key could not be found + /// under Configuration:ContentTypes. There are TopicRepository.ContentTypes.Count ContentTypes in the Repository. /// /// /// Failed to save Topic topic.Key (topic.Id) via From 60bb4f67fa82ecfbbbaa898f99be702de718aa01 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:05:42 -0700 Subject: [PATCH 133/149] Moved `GetContentTypeDescriptors()` to `TopicRepositoryBase` Generally, `GetContentTypeDescriptors()` should be able to operate independent of any underlying implementation; it is a convenience method to consolidate data loaded by `Load()`. As such, its definition can be centralized via the `TopicRepositoryBase`. As part of this, the part of `Save()` that interacts with `GetContentTypeDescriptors()` was also able to be centralized as part of `TopicRepositoryBase`. --- .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 68 +----------------- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 +- Ignia.Topics/Properties/AssemblyInfo.cs | 4 +- .../Repositories/TopicRepositoryBase.cs | 69 ++++++++++++++++++- 8 files changed, 81 insertions(+), 80 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index e56160c7..0e61699e 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1751.0")] -[assembly: AssemblyFileVersion("3.5.1783.0")] +[assembly: AssemblyVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1790.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 687b5084..477954fd 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -34,7 +34,6 @@ public class SqlTopicRepository : TopicRepositoryBase, ITopicRepository { /*========================================================================================================================== | PRIVATE VARIABLES \-------------------------------------------------------------------------------------------------------------------------*/ - private ContentTypeDescriptorCollection _contentTypeDescriptors = null; private readonly string _connectionString = null; /*========================================================================================================================== @@ -264,56 +263,6 @@ private static void SetDerivedTopics(Dictionary topics) { } - /*========================================================================================================================== - | GET CONTENT TYPE DESCRIPTORS - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Retrieves a collection of Content Type Descriptor objects from the configuration section of the data provider. - /// - public override ContentTypeDescriptorCollection GetContentTypeDescriptors() { - - /*------------------------------------------------------------------------------------------------------------------------ - | Initialize content types - \-----------------------------------------------------------------------------------------------------------------------*/ - if (_contentTypeDescriptors == null) { - - /*---------------------------------------------------------------------------------------------------------------------- - | Load configuration data - \---------------------------------------------------------------------------------------------------------------------*/ - var configuration = Load("Configuration"); - - /*---------------------------------------------------------------------------------------------------------------------- - | Add available Content Types to the collection - \---------------------------------------------------------------------------------------------------------------------*/ - _contentTypeDescriptors = new ContentTypeDescriptorCollection(); - - /*---------------------------------------------------------------------------------------------------------------------- - | Ensure the parent ContentTypes topic is available to iterate over - \---------------------------------------------------------------------------------------------------------------------*/ - if (configuration.Children.GetTopic("ContentTypes") == null) { - throw new Exception("Unable to load section Configuration:ContentTypes."); - } - - /*---------------------------------------------------------------------------------------------------------------------- - | Add available Content Types to the collection - \---------------------------------------------------------------------------------------------------------------------*/ - foreach (var topic in configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", "ContentType")) { - // Ensure the Topic is used as the strongly-typed ContentType - // Add ContentType Topic to collection if not already added - if ( - topic is ContentTypeDescriptor contentTypeDescriptor && - !_contentTypeDescriptors.Contains(contentTypeDescriptor.Key) - ) { - _contentTypeDescriptors.Add(contentTypeDescriptor); - } - } - - } - - return _contentTypeDescriptors; - - } - /*========================================================================================================================== | METHOD: SET VERSION HISTORY \-------------------------------------------------------------------------------------------------------------------------*/ @@ -752,22 +701,7 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f /*------------------------------------------------------------------------------------------------------------------------ | Validate content type \-----------------------------------------------------------------------------------------------------------------------*/ - var contentTypes = GetContentTypeDescriptors(); - if (!contentTypes.Contains(topic.Attributes.GetValue("ContentType"))) { - throw new Exception( - "The Content Type \"" + topic.Attributes.GetValue("ContentType", "Page") + "\" referenced by \"" + topic.Key + - "\" could not be found. under \"Configuration:ContentTypes\". There are " + contentTypes.Count + - " ContentTypes in the Repository." - ); - } - var contentType = contentTypes[topic.Attributes.GetValue("ContentType", "Page")]; - - /*------------------------------------------------------------------------------------------------------------------------ - | Update content types collection, if appropriate - \-----------------------------------------------------------------------------------------------------------------------*/ - if (topic is ContentTypeDescriptor && !contentTypes.Contains(topic.Key)) { - _contentTypeDescriptors.Add(topic as ContentTypeDescriptor); - } + var contentType = GetContentTypeDescriptors()[topic.Attributes.GetValue("ContentType", "Page")]; /*------------------------------------------------------------------------------------------------------------------------ | Establish attribute strings diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 8384c60e..1f5c0afe 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1771.0")] -[assembly: AssemblyFileVersion("3.5.1819.0")] +[assembly: AssemblyVersion("3.5.1786.0")] +[assembly: AssemblyFileVersion("3.5.1834.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 33dfaa60..e756d3ea 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1752.0")] -[assembly: AssemblyFileVersion("3.5.1783.0")] +[assembly: AssemblyVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1789.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 8c3bb4f5..6649db1f 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1756.0")] -[assembly: AssemblyFileVersion("3.5.1788.0")] +[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1794.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index da70724e..11297b26 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1750.0")] -[assembly: AssemblyFileVersion("3.5.1774.0")] +[assembly: AssemblyVersion("3.5.1754.0")] +[assembly: AssemblyFileVersion("3.5.1778.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 00d6489b..501fb3f1 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1751.0")] -[assembly: AssemblyFileVersion("3.5.1783.0")] +[assembly: AssemblyVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1789.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Repositories/TopicRepositoryBase.cs b/Ignia.Topics/Repositories/TopicRepositoryBase.cs index 822e39e7..71e77803 100644 --- a/Ignia.Topics/Repositories/TopicRepositoryBase.cs +++ b/Ignia.Topics/Repositories/TopicRepositoryBase.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics.Contracts; using Ignia.Topics.Collections; +using Ignia.Topics.Querying; namespace Ignia.Topics.Repositories { @@ -17,6 +18,11 @@ namespace Ignia.Topics.Repositories { /// public abstract class TopicRepositoryBase : ITopicRepository { + /*========================================================================================================================== + | PRIVATE VARIABLES + \-------------------------------------------------------------------------------------------------------------------------*/ + private ContentTypeDescriptorCollection _contentTypeDescriptors = null; + /*========================================================================================================================== | EVENT HANDLERS \-------------------------------------------------------------------------------------------------------------------------*/ @@ -41,7 +47,50 @@ public abstract class TopicRepositoryBase : ITopicRepository { /// /// Retrieves a collection of Content Type Descriptor objects from the configuration section of the data provider. /// - public abstract ContentTypeDescriptorCollection GetContentTypeDescriptors(); + public virtual ContentTypeDescriptorCollection GetContentTypeDescriptors() { + + /*------------------------------------------------------------------------------------------------------------------------ + | Initialize content types + \-----------------------------------------------------------------------------------------------------------------------*/ + if (_contentTypeDescriptors == null) { + + /*---------------------------------------------------------------------------------------------------------------------- + | Load configuration data + \---------------------------------------------------------------------------------------------------------------------*/ + var configuration = Load("Configuration"); + + /*---------------------------------------------------------------------------------------------------------------------- + | Add available Content Types to the collection + \---------------------------------------------------------------------------------------------------------------------*/ + _contentTypeDescriptors = new ContentTypeDescriptorCollection(); + + /*---------------------------------------------------------------------------------------------------------------------- + | Ensure the parent ContentTypes topic is available to iterate over + \---------------------------------------------------------------------------------------------------------------------*/ + if (configuration.Children.GetTopic("ContentTypes") == null) { + throw new Exception("Unable to load section Configuration:ContentTypes."); + } + + /*---------------------------------------------------------------------------------------------------------------------- + | Add available Content Types to the collection + \---------------------------------------------------------------------------------------------------------------------*/ + foreach (var topic in configuration.Children.GetTopic("ContentTypes").FindAllByAttribute("ContentType", "ContentType")) { + // Ensure the Topic is used as the strongly-typed ContentType + // Add ContentType Topic to collection if not already added + if ( + topic is ContentTypeDescriptor contentTypeDescriptor && + !_contentTypeDescriptors.Contains(contentTypeDescriptor.Key) + ) { + _contentTypeDescriptors.Add(contentTypeDescriptor); + } + } + + } + + return _contentTypeDescriptors; + + } + /*========================================================================================================================== | METHOD: LOAD @@ -167,6 +216,24 @@ public void Rollback(Topic topic, DateTime version) { /// topic public virtual int Save(Topic topic, bool isRecursive = false, bool isDraft = false) { + /*------------------------------------------------------------------------------------------------------------------------ + | Validate content type + \-----------------------------------------------------------------------------------------------------------------------*/ + var contentTypes = GetContentTypeDescriptors(); + if (!contentTypes.Contains(topic.ContentType)) { + throw new ArgumentException( + $"The Content Type \"{topic.ContentType}\" referenced by \"{topic.Key}\" could not be found under " + + $"\"Configuration:ContentTypes\". There are currently {contentTypes.Count} ContentTypes in the Repository." + ); + } + + /*------------------------------------------------------------------------------------------------------------------------ + | Update content types collection, if appropriate + \-----------------------------------------------------------------------------------------------------------------------*/ + if (topic is ContentTypeDescriptor && !contentTypes.Contains(topic.Key)) { + _contentTypeDescriptors.Add(topic as ContentTypeDescriptor); + } + /*------------------------------------------------------------------------------------------------------------------------ | Trigger event \-----------------------------------------------------------------------------------------------------------------------*/ From 65c7b9a173f23d5fe820548c23f59987fc507da7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:08:41 -0700 Subject: [PATCH 134/149] Established `GetContentTypeDescriptors()` test Ensured that `ITopicRepositoryTest` is evaluating the `GetContentTypeDescriptors()` method. This is important since this method has some built-in logic. As part of this, also ensured that `FakeTopicRepository`'s `Load()` and `GetContentTypeDescriptors()` methods were implemented enough to satisfy the test requirements. This included registering all content types in the `FakeTopicRepository` to ensure that internally-created `ContentTypeDescriptor`s had been created (e.g., `Lookup`, `List`, &c.) as well as renaming `ContentType` to `ContentTypeDescriptor`. --- Ignia.Topics.Tests/ITopicRepositoryTest.cs | 19 +++++++++++++++++ .../TestDoubles/FakeTopicRepository.cs | 21 +++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Ignia.Topics.Tests/ITopicRepositoryTest.cs b/Ignia.Topics.Tests/ITopicRepositoryTest.cs index 8a1fa9c6..715ef268 100644 --- a/Ignia.Topics.Tests/ITopicRepositoryTest.cs +++ b/Ignia.Topics.Tests/ITopicRepositoryTest.cs @@ -189,6 +189,25 @@ public void Delete() { } + /*========================================================================================================================== + | TEST: GET CONTENT TYPE DESCRIPTORS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Retrieves a list of s from the and ensures that + /// the expected number (2) are present. + /// + [TestMethod] + public void GetContentTypeDescriptors() { + + var contentTypes = _topicRepository.GetContentTypeDescriptors(); + + Assert.AreEqual(7, contentTypes.Count); + Assert.IsNotNull(contentTypes.GetTopic("ContentTypeDescriptor")); + Assert.IsNotNull(contentTypes.GetTopic("Page")); + Assert.IsNotNull(contentTypes.GetTopic("LookupListItem")); + + } + } //Class diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index 462d9ec4..fd5d07f6 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -8,6 +8,7 @@ using System.Diagnostics.Contracts; using System.Threading.Tasks; using Ignia.Topics.Collections; +using Ignia.Topics.Querying; using Ignia.Topics.Repositories; namespace Ignia.Topics.Tests.TestDoubles { @@ -41,14 +42,6 @@ public FakeTopicRepository() : base() { CreateFakeData(); } - /*========================================================================================================================== - | GET CONTENT TYPE DESCRIPTORS - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Retrieves a collection of Content Type Descriptor objects from the configuration section of the data provider. - /// - public override ContentTypeDescriptorCollection GetContentTypeDescriptors() => throw new NotImplementedException(); - /*========================================================================================================================== | METHOD: LOAD \-------------------------------------------------------------------------------------------------------------------------*/ @@ -58,7 +51,9 @@ public FakeTopicRepository() : base() { /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. /// A topic object. - public override Topic Load(int topicId, bool isRecursive = true) => throw new NotImplementedException(); + public override Topic Load(int topicId, bool isRecursive = true) { + return (topicId < 0)? _cache :_cache.FindFirst(t => t.Id.Equals(topicId)); + } /// /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. @@ -72,7 +67,8 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { | Lookup by TopicKey \-----------------------------------------------------------------------------------------------------------------------*/ if (!String.IsNullOrWhiteSpace(topicKey)) { - throw new NotImplementedException(); + topicKey = topicKey.Contains(":") ? topicKey : "Root:" + topicKey; + return _cache.FindFirst(t => t.GetUniqueKey().Equals(topicKey)); } /*------------------------------------------------------------------------------------------------------------------------ @@ -233,9 +229,12 @@ private void CreateFakeData() { var configuration = TopicFactory.Create("Configuration", "Container", rootTopic); var contentTypes = TopicFactory.Create("ContentTypes", "ContentTypeDescriptor", configuration); - TopicFactory.Create("ContentType", "ContentTypeDescriptor", contentTypes); + TopicFactory.Create("ContentTypeDescriptor", "ContentTypeDescriptor", contentTypes); TopicFactory.Create("Page", "ContentTypeDescriptor", contentTypes); TopicFactory.Create("Container", "ContentTypeDescriptor", contentTypes); + TopicFactory.Create("Lookup", "ContentTypeDescriptor", contentTypes); + TopicFactory.Create("LookupListItem", "ContentTypeDescriptor", contentTypes); + TopicFactory.Create("List", "ContentTypeDescriptor", contentTypes); /*------------------------------------------------------------------------------------------------------------------------ | Establish metadata From c8d601130119c351fb316f65b8820b49b9154e9a Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:09:20 -0700 Subject: [PATCH 135/149] Fixed reference to `IsHidden` `IsHidden` was removed from `AttributeDescriptor`. Needed to fix XmlDoc reference to point to `Topic.IsHidden` instead. --- Ignia.Topics/AttributeDescriptor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Ignia.Topics/AttributeDescriptor.cs b/Ignia.Topics/AttributeDescriptor.cs index e99a1f01..c9579513 100644 --- a/Ignia.Topics/AttributeDescriptor.cs +++ b/Ignia.Topics/AttributeDescriptor.cs @@ -120,7 +120,8 @@ public string Type { /// /// When attributes are displayed in the editor, they are grouped by tabs. The tabs are not predetermined, but rather set /// by individual attributes. If five attributes, for instance, have a display group of "Settings", then a tab will be - /// rendered called "Settings" and will list those five attributes (assuming none are set to ). + /// rendered called "Settings" and will list those five attributes (assuming none are set to ). /// /// /// !String.IsNullOrWhiteSpace(value) From f0667c10ec354f3eec96f4f037f245f906bfd47b Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:10:51 -0700 Subject: [PATCH 136/149] Changed `Rollback` to `Virtual` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All methods in `TopicRepositoryBase` should allow optional overriding in concrete implementations, if necessary—even though `Rollback` may not generally need it, due to its nature. Fixed formatting for `args` while I was at it (since it's not near other variable declarations, the alignment isn't necessary or consistent). --- Ignia.Topics/Repositories/TopicRepositoryBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics/Repositories/TopicRepositoryBase.cs b/Ignia.Topics/Repositories/TopicRepositoryBase.cs index 71e77803..b7984466 100644 --- a/Ignia.Topics/Repositories/TopicRepositoryBase.cs +++ b/Ignia.Topics/Repositories/TopicRepositoryBase.cs @@ -152,7 +152,7 @@ public static Topic Load(XmlNode node, ImportStrategy importStrategy = ImportStr /// exception="T:System.ArgumentNullException"> /// !VersionHistory.Contains(version) /// - public void Rollback(Topic topic, DateTime version) { + public virtual void Rollback(Topic topic, DateTime version) { /*------------------------------------------------------------------------------------------------------------------------ | Retrieve topic from database @@ -238,7 +238,7 @@ public virtual int Save(Topic topic, bool isRecursive = false, bool isDraft = fa | Trigger event \-----------------------------------------------------------------------------------------------------------------------*/ if (topic.OriginalKey != null && topic.OriginalKey != topic.Key) { - var args = new RenameEventArgs(topic); + var args = new RenameEventArgs(topic); RenameEvent?.Invoke(this, args); } From 6342c91813bd1d8a470c3b0f2d4c2ba1dcbc9021 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 19:59:55 -0700 Subject: [PATCH 137/149] Migrated to `GetInteger()` where appropriate There's no need to parse integer values for e.g. `TopicId` or `ParentId`; this can now be done using `GetInteger()`. This has the added benefit of allowing more concrete validation of e.g. `ParentId` to ensure it is actually an integer (instead of simply not null). --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 477954fd..8b737acc 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -190,7 +190,7 @@ private static void SetBlobAttributes(SqlDataReader reader, Dictionary /// - /// Topics can be cross-referenced with each other via a many-to-many relationships.Once the topics are populated in + /// Topics can be cross-referenced with each other via a many-to-many relationships. Once the topics are populated in /// memory, loop through the data to create these associations. /// /// The that representing the current record. @@ -213,7 +213,7 @@ private static void SetRelationships(SqlDataReader reader, Dictionary topics) { | Loop through topics \-----------------------------------------------------------------------------------------------------------------------*/ foreach (var topic in topics.Values) { - var success = Int32.TryParse(topic.Attributes.GetValue("TopicId", "-1", false, false), out var derivedTopicId); - if (!success || derivedTopicId < 0) continue; + var derivedTopicId = topic.Attributes.GetInteger("TopicId", -1, false, false); + if (derivedTopicId < 0) continue; if (topics.Keys.Contains(derivedTopicId)) { topic.DerivedTopic = topics[derivedTopicId]; } @@ -861,7 +861,7 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f \-----------------------------------------------------------------------------------------------------------------------*/ if (isRecursive) { foreach (var childTopic in topic.Children) { - Contract.Assume(childTopic.Attributes.GetValue("ParentID") != null, "Assumes the Parent ID AttributeValue is available."); + Contract.Assume(childTopic.Attributes.GetInteger("ParentID", -1) > 0, "Assumes the Parent ID AttributeValue is available."); childTopic.Attributes.SetValue("ParentID", returnVal.ToString()); Save(childTopic, isRecursive, isDraft); } From a13627f66dddcaffeb8535ccbd6626a294bc2997 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 22:43:29 -0700 Subject: [PATCH 138/149] Included `UniqueKey` in view models This is required by the `_BreadCrumbs.cshtml` page's `GetBreadCrumbs()` method; without this, it throws an exception, causing error pages not to render. --- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs | 1 + Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 1f5c0afe..04f6881a 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1786.0")] -[assembly: AssemblyFileVersion("3.5.1834.0")] +[assembly: AssemblyVersion("3.5.1787.0")] +[assembly: AssemblyFileVersion("3.5.1835.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs b/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs index 580a440f..2140e822 100644 --- a/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs +++ b/Ignia.Topics.Web.Mvc/Controllers/ErrorControllerBase{T}.cs @@ -122,6 +122,7 @@ public virtual T CreateErrorViewModel(string key, string title) { \-----------------------------------------------------------------------------------------------------------------------*/ var viewModel = new T { Key = key, + UniqueKey = "Error:" + key, WebPath = "/Error/" + key, ContentType = "Page", Title = title, diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 6649db1f..779a890a 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1762.0")] -[assembly: AssemblyFileVersion("3.5.1794.0")] +[assembly: AssemblyVersion("3.5.1763.0")] +[assembly: AssemblyFileVersion("3.5.1795.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] From 4238d500db0209a0be23f0387592b7ce378ea128 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 22 May 2018 22:43:44 -0700 Subject: [PATCH 139/149] Indented variables --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 8b737acc..7b223dc4 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -326,8 +326,8 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { /*------------------------------------------------------------------------------------------------------------------------ | Establish database connection \-----------------------------------------------------------------------------------------------------------------------*/ - var connection = new SqlConnection(_connectionString); - var command = new SqlCommand("topics_GetTopicID", connection); + var connection = new SqlConnection(_connectionString); + var command = new SqlCommand("topics_GetTopicID", connection); int topicId; command.CommandType = CommandType.StoredProcedure; From 1a0a4116edb4b1c2afc3acadd74c74b03e5d92a7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 12:36:50 -0700 Subject: [PATCH 140/149] Changed `GetPath()` to static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not only is `GetPath()` stateless—and, thus, will be faster if it is static—but it was actually being _called_ as a static method _already_ by `FilePath.ascx` (the only known consumer of it). It's unclear why this changed, or how it ever worked. Regardless, this fixes the issue. --- Ignia.Topics.Web/Editor/FilePath.cs | 12 ++---------- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Ignia.Topics.Web/Editor/FilePath.cs b/Ignia.Topics.Web/Editor/FilePath.cs index 8c6d700c..c2c06ba9 100644 --- a/Ignia.Topics.Web/Editor/FilePath.cs +++ b/Ignia.Topics.Web/Editor/FilePath.cs @@ -15,15 +15,7 @@ namespace Ignia.Topics.Web.Editor { /// Provides a strongly-typed class associated with the FilePath.ascx Attribute Type control and logic associated with /// building a configured file path from values passed to the constructor. /// - public class FilePath { - - /*========================================================================================================================== - | CONSTRUCTOR - \-------------------------------------------------------------------------------------------------------------------------*/ - /// - /// Initializes a new instance of the class. - /// - public FilePath() { } + public static class FilePath { /*========================================================================================================================== | METHOD: GET PATH @@ -37,7 +29,7 @@ public FilePath() { } /// Boolean indicator as to whether to include the endpoint/leaf topic in the path. /// The assembled topic keys at which to end the path string. /// A constructed file path. - public string GetPath( + public static string GetPath( Topic topic, string attributeKey, bool includeLeafTopic = true, diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 11297b26..4e85afd2 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1754.0")] -[assembly: AssemblyFileVersion("3.5.1778.0")] +[assembly: AssemblyVersion("3.5.1755.0")] +[assembly: AssemblyFileVersion("3.5.1779.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 501fb3f1..b74f98ee 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1757.0")] -[assembly: AssemblyFileVersion("3.5.1789.0")] +[assembly: AssemblyVersion("3.5.1758.0")] +[assembly: AssemblyFileVersion("3.5.1790.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] From adac48b69cda9e4d35069343a84e16cb35852dc7 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 13:59:25 -0700 Subject: [PATCH 141/149] =?UTF-8?q?Moved=20to=20`catch(SqlException?= =?UTF-8?q?=E2=80=A6)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we were catching `Exception`, as per Microsoft's guidance. This was due to the fact that there are a number of other exceptions that can be raised during the processing of a `SqlConnection` and, especially, a `SqlDataReader`. Most of those, however, are not _expected_ exceptions, and thus should not be handled (or even wrapped in a generic exception). In addition, moved `Exception` messages to use string interpolation for cleaner code. --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 7b223dc4..8c8a2712 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Data.SqlClient; using System.Diagnostics; using System.Diagnostics.Contracts; @@ -362,8 +363,8 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Topic(s) failed to load: " + ex.Message); + catch (SqlException exception) { + throw new Exception($"Topic(s) failed to load: '{exception.Message}'"); } /*------------------------------------------------------------------------------------------------------------------------ @@ -501,8 +502,8 @@ public override Topic Load(int topicId, bool isRecursive = true) { /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Topics failed to load: " + ex.Message); + catch (SqlException exception) { + throw new Exception($"Topics failed to load: '{exception.Message}'"); } /*------------------------------------------------------------------------------------------------------------------------ @@ -636,8 +637,8 @@ public override Topic Load(int topicId, DateTime version) { /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Topics failed to load: " + ex.Message); + catch (SqlException exception) { + throw new Exception($"Topics failed to load: '{exception.Message}'"); } /*------------------------------------------------------------------------------------------------------------------------ @@ -844,8 +845,10 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Failed to save Topic " + topic.Key + " (" + topic.Id + ") via " + _connectionString + ": " + ex.Message); + catch (SqlException exception) { + throw new Exception( + $"Failed to save Topic '{topic.Key}' ({topic.Id}) via '{_connectionString}': '{exception.Message}'" + ); } /*------------------------------------------------------------------------------------------------------------------------ @@ -930,8 +933,10 @@ public override void Move(Topic topic, Topic target, Topic sibling) { /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Failed to move Topic " + topic.Key + " (" + topic.Id + ") to " + target.Key + " (" + target.Id + "): " + ex.Message); + catch (SqlException exception) { + throw new Exception( + $"Failed to move Topic '{topic.Key}' ({topic.Id}) to '{target.Key}' ({target.Id}): '{exception.Message}'" + ); } /*------------------------------------------------------------------------------------------------------------------------ @@ -997,8 +1002,8 @@ public override void Delete(Topic topic, bool isRecursive = false) { /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Failed to delete Topic " + topic.Key + " (" + topic.Id + "): " + ex.Message); + catch (SqlException exception) { + throw new Exception($"Failed to delete Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'"); } /*------------------------------------------------------------------------------------------------------------------------ @@ -1092,8 +1097,8 @@ private static string PersistRelations(Topic topic, SqlConnection connection, bo /*------------------------------------------------------------------------------------------------------------------------ | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ - catch (Exception ex) { - throw new Exception("Failed to persist relationships for Topic " + topic.Key + " (" + topic.Id + "): " + ex.Message); + catch (SqlException exception) { + throw new Exception($"Failed to persist relationships for Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'"); } /*------------------------------------------------------------------------------------------------------------------------ From b63dc07d7bdb4f934af9b77bffd901abe8207871 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 14:17:22 -0700 Subject: [PATCH 142/149] Introduced `TopicRepositoryException` Previously, the `SqlTopicRepository` was throwing generic `Exception`s. This is not a best practice. It is appropriate, however, for implementations of `ITopicRepository` to throw a generic, database agnostic exception (as opposed to e.g. `SqlException`) since it should not be tied to any specific persistence technology. The `TopicRepositoryException` allows it to accomplish this. --- .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 26 ++++++--- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 +- Ignia.Topics/Ignia.Topics.csproj | 1 + Ignia.Topics/Properties/AssemblyInfo.cs | 4 +- .../Repositories/TopicRepositoryException.cs | 57 +++++++++++++++++++ 9 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 Ignia.Topics/Repositories/TopicRepositoryException.cs diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 0e61699e..c8c20e87 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1758.0")] -[assembly: AssemblyFileVersion("3.5.1790.0")] +[assembly: AssemblyVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1791.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index 8c8a2712..df09c880 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -364,7 +364,7 @@ public override Topic Load(string topicKey = null, bool isRecursive = true) { | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception($"Topic(s) failed to load: '{exception.Message}'"); + throw new TopicRepositoryException($"Topic(s) failed to load: '{exception.Message}'", exception); } /*------------------------------------------------------------------------------------------------------------------------ @@ -503,7 +503,7 @@ public override Topic Load(int topicId, bool isRecursive = true) { | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception($"Topics failed to load: '{exception.Message}'"); + throw new TopicRepositoryException($"Topics failed to load: '{exception.Message}'", exception); } /*------------------------------------------------------------------------------------------------------------------------ @@ -638,7 +638,7 @@ public override Topic Load(int topicId, DateTime version) { | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception($"Topics failed to load: '{exception.Message}'"); + throw new TopicRepositoryException($"Topics failed to load: '{exception.Message}'", exception); } /*------------------------------------------------------------------------------------------------------------------------ @@ -846,8 +846,9 @@ public override int Save(Topic topic, bool isRecursive = false, bool isDraft = f | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception( - $"Failed to save Topic '{topic.Key}' ({topic.Id}) via '{_connectionString}': '{exception.Message}'" + throw new TopicRepositoryException( + $"Failed to save Topic '{topic.Key}' ({topic.Id}) via '{_connectionString}': '{exception.Message}'", + exception ); } @@ -934,8 +935,9 @@ public override void Move(Topic topic, Topic target, Topic sibling) { | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception( - $"Failed to move Topic '{topic.Key}' ({topic.Id}) to '{target.Key}' ({target.Id}): '{exception.Message}'" + throw new TopicRepositoryException( + $"Failed to move Topic '{topic.Key}' ({topic.Id}) to '{target.Key}' ({target.Id}): '{exception.Message}'", + exception ); } @@ -1003,7 +1005,10 @@ public override void Delete(Topic topic, bool isRecursive = false) { | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception($"Failed to delete Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'"); + throw new TopicRepositoryException( + $"Failed to delete Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'", + exception + ); } /*------------------------------------------------------------------------------------------------------------------------ @@ -1098,7 +1103,10 @@ private static string PersistRelations(Topic topic, SqlConnection connection, bo | Catch exception \-----------------------------------------------------------------------------------------------------------------------*/ catch (SqlException exception) { - throw new Exception($"Failed to persist relationships for Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'"); + throw new TopicRepositoryException( + $"Failed to persist relationships for Topic '{topic.Key}' ({topic.Id}): '{exception.Message}'", + exception + ); } /*------------------------------------------------------------------------------------------------------------------------ diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index 04f6881a..ce9c1e78 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1787.0")] -[assembly: AssemblyFileVersion("3.5.1835.0")] +[assembly: AssemblyVersion("3.5.1788.0")] +[assembly: AssemblyFileVersion("3.5.1836.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index e756d3ea..21c24e2b 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1758.0")] -[assembly: AssemblyFileVersion("3.5.1789.0")] +[assembly: AssemblyVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1790.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 779a890a..920e43d7 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1763.0")] -[assembly: AssemblyFileVersion("3.5.1795.0")] +[assembly: AssemblyVersion("3.5.1764.0")] +[assembly: AssemblyFileVersion("3.5.1796.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index 4e85afd2..ffcb7467 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1755.0")] -[assembly: AssemblyFileVersion("3.5.1779.0")] +[assembly: AssemblyVersion("3.5.1756.0")] +[assembly: AssemblyFileVersion("3.5.1780.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Ignia.Topics.csproj b/Ignia.Topics/Ignia.Topics.csproj index de852b3b..4c1bb8ac 100644 --- a/Ignia.Topics/Ignia.Topics.csproj +++ b/Ignia.Topics/Ignia.Topics.csproj @@ -175,6 +175,7 @@ + diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index b74f98ee..d315dd4f 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1758.0")] -[assembly: AssemblyFileVersion("3.5.1790.0")] +[assembly: AssemblyVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1791.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Repositories/TopicRepositoryException.cs b/Ignia.Topics/Repositories/TopicRepositoryException.cs new file mode 100644 index 00000000..449001ae --- /dev/null +++ b/Ignia.Topics/Repositories/TopicRepositoryException.cs @@ -0,0 +1,57 @@ +/*============================================================================================================================== +| Author Ignia, LLC +| Client Ignia, LLC +| Project Topics Library +\=============================================================================================================================*/ +using System; +using System.Data.Common; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; + +namespace Ignia.Topics.Repositories { + + /*============================================================================================================================ + | CLASS: TOPIC REPOSITORY EXCEPTION + \---------------------------------------------------------------------------------------------------------------------------*/ + /// + /// The provides a general exception that can be thrown for any persistence errors + /// that arise from concrete implementations. + /// + /// + /// Microsoft provides a set of classes, such as , which are specific to + /// their target implementations. Since , however, is intended to be database agnostic, none + /// of these are appropriate to catch when implementing . Instead, the provides a database agnostic version of an exception that can provide a wrapper + /// around any of these more concrete exceptions. + /// + public class TopicRepositoryException : DbException { + + /*========================================================================================================================== + | CONSTRUCTOR: TAXONOMY DELETE EVENT ARGS + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Initializes a new instance with a specific error message. + /// + /// The message to display for this exception. + public TopicRepositoryException(string message) : base(message) {} + + /// + /// Initializes a new instance with a specific error message and nested exception. + /// + /// The message to display for this exception. + /// The reference to the original, underlying exception. + public TopicRepositoryException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Instantiates a new instance for serialization. + /// + /// A instance with details about the serialization requirements. + /// A instance with details about the request context. + /// A new instance. + protected TopicRepositoryException(SerializationInfo info, StreamingContext context) : base(info, context) { + Contract.Requires(info != null); + } + + } // Class + +} // Namespace \ No newline at end of file From 24b2e9ba8c1eec823397ba4773e6ad2190846b26 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 14:56:30 -0700 Subject: [PATCH 143/149] Provide more explicit messaging on key conflict When a `KeyedDictionary` finds a duplicate key, it provides a generic `ArgumentException` announcing this. It doesn't provide any context, however, such as the key that is being duplicated. This makes debugging difficult. To mitigate this, I've added explicit `InsertItem` overrides that detect duplicate keys and, if they exist, provide an `ArgumentException` with an explicit error message containing not only the key value, but also any other relevant context (such as any associated `Topic` or `T` type, as appropriate). --- .../Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 +-- .../Properties/AssemblyInfo.cs | 4 +-- .../Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 +-- .../Collections/AttributeValueCollection.cs | 30 +++++++++++++++---- .../Collections/ReadOnlyTopicCollection{T}.cs | 2 +- .../Collections/RelatedTopicCollection.cs | 29 ++++++++++++++++++ .../Collections/TopicCollection{T}.cs | 27 +++++++++++++++++ Ignia.Topics/Properties/AssemblyInfo.cs | 4 +-- Ignia.Topics/Reflection/TypeCollection.cs | 26 ++++++++++++++++ 11 files changed, 120 insertions(+), 18 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index c8c20e87..1c71a26c 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1759.0")] -[assembly: AssemblyFileVersion("3.5.1791.0")] +[assembly: AssemblyVersion("3.5.1760.0")] +[assembly: AssemblyFileVersion("3.5.1792.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index ce9c1e78..ecdf68c5 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1788.0")] -[assembly: AssemblyFileVersion("3.5.1836.0")] +[assembly: AssemblyVersion("3.5.1789.0")] +[assembly: AssemblyFileVersion("3.5.1837.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 21c24e2b..9b1fa879 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1759.0")] -[assembly: AssemblyFileVersion("3.5.1790.0")] +[assembly: AssemblyVersion("3.5.1760.0")] +[assembly: AssemblyFileVersion("3.5.1791.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 920e43d7..15af7ecb 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1764.0")] -[assembly: AssemblyFileVersion("3.5.1796.0")] +[assembly: AssemblyVersion("3.5.1765.0")] +[assembly: AssemblyFileVersion("3.5.1797.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index ffcb7467..e798cd74 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1756.0")] -[assembly: AssemblyFileVersion("3.5.1780.0")] +[assembly: AssemblyVersion("3.5.1757.0")] +[assembly: AssemblyFileVersion("3.5.1781.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index 5ba39355..48aafc1c 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -475,17 +475,37 @@ internal void SetValue(string key, string value, bool? isDirty, bool enforceBusi /// business logic is enforced. /// /// - /// If a settable property is available corresponding to the , the call should be routed - /// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is - /// set by the 's enforceBusinessLogic parameter. To avoid an - /// infinite loop, internal setters _must_ call this overload. + /// + /// If a settable property is available corresponding to the , the call should be routed + /// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is + /// set by the 's enforceBusinessLogic parameter. To avoid an + /// infinite loop, internal setters _must_ call this overload. + /// + /// + /// Compared to the base implementation, will throw a specific error if a duplicate key + /// is inserted. This conveniently provides the name of the so it's clear what key is + /// being duplicated. + /// /// /// The location that the should be set. /// The object which is being inserted. /// The key for the specified collection item. + /// + /// An AttributeValue with the Key '{item.Key}' already exists. The Value of the existing item is "{this[item.Key].Value}; + /// the new item's Value is '{item.Value}'. These AttributeValues are associated with the Topic '{GetUniqueKey()}'." + /// protected override void InsertItem(int index, AttributeValue item) { if (EnforceBusinessLogic(item, out item)) { - base.InsertItem(index, item); + if (!Contains(item.Key)) { + base.InsertItem(index, item); + } + else { + throw new ArgumentException( + $"An {nameof(AttributeValue)} with the Key '{item.Key}' already exists. The Value of the existing item is " + + $"{this[item.Key].Value}; the new item's Value is '{item.Value}'. These {nameof(AttributeValue)}s are associated " + + $"with the {nameof(Topic)} '{_associatedTopic.GetUniqueKey()}'." + ); + } } } diff --git a/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs b/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs index 2daf9979..29e3d51b 100644 --- a/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs +++ b/Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs @@ -12,7 +12,7 @@ namespace Ignia.Topics.Collections { | CLASS: READ ONLY TOPIC COLLECTION \---------------------------------------------------------------------------------------------------------------------------*/ /// - /// Provides a read-only keyed collection of topics. + /// Provides a read-only collection of topics. /// public class ReadOnlyTopicCollection : ReadOnlyCollection where T : Topic { diff --git a/Ignia.Topics/Collections/RelatedTopicCollection.cs b/Ignia.Topics/Collections/RelatedTopicCollection.cs index b1d800a1..093f275c 100644 --- a/Ignia.Topics/Collections/RelatedTopicCollection.cs +++ b/Ignia.Topics/Collections/RelatedTopicCollection.cs @@ -229,6 +229,35 @@ public void SetTopic(string scope, Topic topic, bool isIncoming) { } + /*========================================================================================================================== + | OVERRIDE: INSERT ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// Fires any time a is added to the collection. + /// + /// Compared to the base implementation, will throw a specific error if a duplicate key is + /// inserted. This conveniently provides the name of the , so it's clear what key is + /// being duplicated. + /// + /// The zero-based index at which should be inserted. + /// The instance to insert. + /// + /// A NamedTopicCollection with the Name '{item.Name}' already exists in this RelatedTopicCollection. The existing key is + /// {this[item.Name].Name}'; the new item's is '{item.Name}'. This collection is associated with the '{GetUniqueKey()}' + /// Topic. + /// + protected override void InsertItem(int index, NamedTopicCollection item) { + if (!Contains(item.Name)) { + base.InsertItem(index, item); + } + else { + throw new ArgumentException( + $"A {nameof(NamedTopicCollection)} with the Name '{item.Name}' already exists in this " + + $"{nameof(RelatedTopicCollection)}. The existing key is '{this[item.Name].Name}'; the new item's is '{item.Name}'. " + + $"This collection is associated with the '{_parent.GetUniqueKey()}' Topic." + ); + } + } + /*========================================================================================================================== | OVERRIDE: GET KEY FOR ITEM \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/Collections/TopicCollection{T}.cs b/Ignia.Topics/Collections/TopicCollection{T}.cs index fb51a2ec..86cfb318 100644 --- a/Ignia.Topics/Collections/TopicCollection{T}.cs +++ b/Ignia.Topics/Collections/TopicCollection{T}.cs @@ -72,6 +72,33 @@ public ReadOnlyTopicCollection AsReadOnly() { return new ReadOnlyTopicCollection(this); } + /*========================================================================================================================== + | OVERRIDE: INSERT ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// Fires any time a is added to the collection. + /// + /// Compared to the base implementation, will throw a specific error if a duplicate key is + /// inserted. This conveniently provides the name of the 's , so it's + /// clear what key is being duplicated. + /// + /// The zero-based index at which should be inserted. + /// The instance to insert. + /// + /// A {typeof(T).Name} with the Key '{item.Key}' already exists. The UniqueKey of the existing {typeof(T).Name} is + /// '{GetUniqueKey()}'; the new item's is '{item.GetUniqueKey()}'. + /// + protected override void InsertItem(int index, T item) { + if (!Contains(item.Key)) { + base.InsertItem(index, item); + } + else { + throw new ArgumentException( + $"A {typeof(T).Name} with the Key '{item.Key}' already exists. The UniqueKey of the existing {typeof(T).Name} is " + + $"'{this[item.Key].GetUniqueKey()}'; the new item's is '{item.GetUniqueKey()}'." + ); + } + } + /*========================================================================================================================== | METHOD: CHANGE KEY \-------------------------------------------------------------------------------------------------------------------------*/ diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index d315dd4f..1bd0f7f1 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1759.0")] -[assembly: AssemblyFileVersion("3.5.1791.0")] +[assembly: AssemblyVersion("3.5.1760.0")] +[assembly: AssemblyFileVersion("3.5.1792.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Reflection/TypeCollection.cs b/Ignia.Topics/Reflection/TypeCollection.cs index 8093de73..9ac7d692 100644 --- a/Ignia.Topics/Reflection/TypeCollection.cs +++ b/Ignia.Topics/Reflection/TypeCollection.cs @@ -380,6 +380,32 @@ private static object GetValueObject(Type type, string value) { /// static internal List SettableTypes { get; } + /*========================================================================================================================== + | OVERRIDE: INSERT ITEM + \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Fires any time an item is added to the collection. + /// + /// + /// Compared to the base implementation, will throw a specific error if a duplicate key + /// is inserted. This conveniently provides the , so it's clear what is being + /// duplicated. + /// + /// The zero-based index at which should be inserted. + /// The instance to insert. + /// + /// The TypeCollection already contains the MemberInfoCollection of the Type '{item.Type}'. + /// + protected override void InsertItem(int index, MemberInfoCollection item) { + if (!Contains(item.Type)) { + base.InsertItem(index, item); + } + else { + throw new ArgumentException( + $"The '{nameof(TypeCollection)}' already contains the {nameof(MemberInfoCollection)} of the Type '{item.Type}'."); + } + } + /*========================================================================================================================== | OVERRIDE: GET KEY FOR ITEM \-------------------------------------------------------------------------------------------------------------------------*/ From 08e3ed582ff436388f717292107272f6b203f82f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 15:05:23 -0700 Subject: [PATCH 144/149] Remove unused `using` statements --- Ignia.Topics.Data.Sql/SqlTopicRepository.cs | 3 --- Ignia.Topics/Collections/AttributeValueCollection.cs | 1 - 2 files changed, 4 deletions(-) diff --git a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs index df09c880..ee2e1366 100644 --- a/Ignia.Topics.Data.Sql/SqlTopicRepository.cs +++ b/Ignia.Topics.Data.Sql/SqlTopicRepository.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Data.SqlClient; using System.Diagnostics; using System.Diagnostics.Contracts; @@ -15,8 +14,6 @@ using System.Text; using System.Web; using System.Xml; -using Ignia.Topics.Collections; -using Ignia.Topics.Querying; using Ignia.Topics.Repositories; namespace Ignia.Topics.Data.Sql { diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index 48aafc1c..17074545 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -7,7 +7,6 @@ using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using Ignia.Topics.Reflection; -using Ignia.Topics.Repositories; namespace Ignia.Topics.Collections { From 9034c8567b80e010e01ba6be710e17b2394d6d56 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 16:13:48 -0700 Subject: [PATCH 145/149] Migrated to string interpolation Replaced string concactenation with string interpolation. --- .../Collections/AttributeValueCollection.cs | 4 ++-- .../Mapping/CachedTopicMappingService.cs | 2 +- Ignia.Topics/Mapping/TopicMappingService.cs | 8 ++++---- Ignia.Topics/Topic.cs | 18 +++++++++--------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Ignia.Topics/Collections/AttributeValueCollection.cs b/Ignia.Topics/Collections/AttributeValueCollection.cs index 17074545..6c612735 100644 --- a/Ignia.Topics/Collections/AttributeValueCollection.cs +++ b/Ignia.Topics/Collections/AttributeValueCollection.cs @@ -558,8 +558,8 @@ private bool EnforceBusinessLogic(AttributeValue originalAttribute, out Attribut _setCounter++; if (_setCounter > 3) { throw new Exception( - "An infinite loop has occurred when setting `" + originalAttribute.Key + - "`; be sure to call `Topic.SetAttributeValue()` when setting attributes from `Topic` properties." + $"An infinite loop has occurred when setting '{originalAttribute.Key}'; be sure that you are referencing " + + $"`Topic.SetAttributeValue()` when setting attributes from `Topic` properties." ); } _typeCache.SetPropertyValue(_associatedTopic, originalAttribute.Key, originalAttribute.Value); diff --git a/Ignia.Topics/Mapping/CachedTopicMappingService.cs b/Ignia.Topics/Mapping/CachedTopicMappingService.cs index fbae6bd7..ac711f55 100644 --- a/Ignia.Topics/Mapping/CachedTopicMappingService.cs +++ b/Ignia.Topics/Mapping/CachedTopicMappingService.cs @@ -190,7 +190,7 @@ private object CacheViewModel(string contentType, object viewModel, (int, Type, if (cacheKey.Item2 != null) { cacheKey = (cacheKey.Item1, null, cacheKey.Item3); } - if (cacheKey.Item1 > 0 && viewModel.GetType().Name.Equals(contentType + "TopicViewModel")) { + if (cacheKey.Item1 > 0 && viewModel.GetType().Name.Equals($"{contentType}TopicViewModel")) { _cache.TryAdd(cacheKey, viewModel); } return viewModel; diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index 683eff51..b24b8f31 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -116,7 +116,7 @@ private async Task MapAsync(Topic topic, Relationships relationships, Co /*---------------------------------------------------------------------------------------------------------------------- | Instantiate object \---------------------------------------------------------------------------------------------------------------------*/ - var viewModelType = _typeLookupService.GetType(topic.ContentType + "TopicViewModel"); + var viewModelType = _typeLookupService.GetType($"{topic.ContentType}TopicViewModel"); var target = Activator.CreateInstance(viewModelType); /*---------------------------------------------------------------------------------------------------------------------- @@ -251,7 +251,7 @@ ConcurrentDictionary cache | Establish per-property variables \-----------------------------------------------------------------------------------------------------------------------*/ var configuration = new PropertyConfiguration(property); - var topicReferenceId = source.Attributes.GetInteger(property.Name + "Id", 0); + var topicReferenceId = source.Attributes.GetInteger($"{property.Name}Id", 0); /*------------------------------------------------------------------------------------------------------------------------ | Assign default value @@ -315,7 +315,7 @@ protected static void SetScalarValue(Topic source, object target, PropertyConfig /*------------------------------------------------------------------------------------------------------------------------ | Attempt to retrieve value from topic.Get{Property}() \-----------------------------------------------------------------------------------------------------------------------*/ - var attributeValue = _typeCache.GetMethodValue(source, "Get" + configuration.AttributeKey)?.ToString(); + var attributeValue = _typeCache.GetMethodValue(source, $"Get{configuration.AttributeKey}")?.ToString(); /*------------------------------------------------------------------------------------------------------------------------ | Attempt to retrieve value from topic.{Property} @@ -473,7 +473,7 @@ protected IList GetSourceCollection(Topic source, Relationships relations | Handle Metadata relationship \-----------------------------------------------------------------------------------------------------------------------*/ if (listSource.Count == 0 && !String.IsNullOrWhiteSpace(configuration.MetadataKey)) { - var metadataKey = "Root:Configuration:Metadata:" + configuration.MetadataKey + ":LookupList"; + var metadataKey = $"Root:Configuration:Metadata:{configuration.MetadataKey}:LookupList"; listSource = _topicRepository.Load(metadataKey)?.Children.ToList(); } diff --git a/Ignia.Topics/Topic.cs b/Ignia.Topics/Topic.cs index f5546be6..e1482029 100644 --- a/Ignia.Topics/Topic.cs +++ b/Ignia.Topics/Topic.cs @@ -97,7 +97,7 @@ public Topic(string key, string contentType, Topic parent, int id = -1) { /// The unique identifier for the . /// /// - /// The value of this topic has already been set to " + _id + "; it cannot be changed. + /// The value of this topic has already been set to {_id}; it cannot be changed. /// /// /// value > 0 @@ -107,7 +107,7 @@ public int Id { set { Contract.Requires(value > 0, "The id is expected to be a positive value."); if (_id > 0 && !_id.Equals(value)) { - throw new ArgumentException("The value of this topic has already been set to " + _id + "; it cannot be changed."); + throw new ArgumentException($"The value of this topic has already been set to {_id}; it cannot be changed."); } _id = value; } @@ -405,8 +405,8 @@ public DateTime LastModified { /// The to move this to the right of. /// parent - A descendant cannot be its own parent. /// - /// Duplicate key when setting Parent property: the topic with the name '" + Key + "' already exists in the '" + - /// parent.Key + "' topic. + /// Duplicate key when setting Parent property: the topic with the name '{Key}' already exists in the '{parent.Key}' + /// topic. /// public void SetParent(Topic parent, Topic sibling = null) { @@ -428,9 +428,9 @@ public void SetParent(Topic parent, Topic sibling = null) { \-----------------------------------------------------------------------------------------------------------------------*/ if (parent != _parent && parent.Children.Contains(Key)) { throw new InvalidKeyException( - "Duplicate key when setting Parent property: the topic with the name '" + Key + - "' already exists in the '" + parent.Key + "' topic." - ); + $"Duplicate key when setting Parent property: the topic with the name '{Key}' already exists in the '{parent.Key}' " + + $"topic." + ); } /*------------------------------------------------------------------------------------------------------------------------ @@ -478,7 +478,7 @@ public string GetUniqueKey() { var topic = this; for (var i = 0; i < 100; i++) { - if (uniqueKey.Length > 0) uniqueKey = ":" + uniqueKey; + if (uniqueKey.Length > 0) uniqueKey = $":{uniqueKey}"; uniqueKey = topic.Key + uniqueKey; topic = topic.Parent; if (topic == null) break; @@ -507,7 +507,7 @@ public string GetWebPath() { Contract.Ensures(Contract.Result() != null); var uniqueKey = GetUniqueKey().Replace("Root:", "/").Replace(":", "/") + "/"; if (!uniqueKey.StartsWith("/")) { - uniqueKey = "/" + uniqueKey; + uniqueKey = $"/{uniqueKey}"; } return uniqueKey; } From ee7de38d756cea81cf119ddb0ff7c3091a1ad2d0 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 16:16:41 -0700 Subject: [PATCH 146/149] Implemented expression-bodied method --- Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index fd5d07f6..d6d17404 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -51,9 +51,8 @@ public FakeTopicRepository() : base() { /// The topic identifier. /// Determines whether or not to recurse through and load a topic's children. /// A topic object. - public override Topic Load(int topicId, bool isRecursive = true) { - return (topicId < 0)? _cache :_cache.FindFirst(t => t.Id.Equals(topicId)); - } + public override Topic Load(int topicId, bool isRecursive = true) => + (topicId < 0)? _cache :_cache.FindFirst(t => t.Id.Equals(topicId)); /// /// Loads a topic (and, optionally, all of its descendants) based on the specified key name. From 113f5050460d51a28be4aece58d77c61f5787041 Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 16:17:35 -0700 Subject: [PATCH 147/149] Added default constructor While not strictly necessary for our needs, this is generally expected for exceptions, and recommended by Visual Studio's Code Analysis. --- Ignia.Topics/Repositories/TopicRepositoryException.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Ignia.Topics/Repositories/TopicRepositoryException.cs b/Ignia.Topics/Repositories/TopicRepositoryException.cs index 449001ae..5184d9fc 100644 --- a/Ignia.Topics/Repositories/TopicRepositoryException.cs +++ b/Ignia.Topics/Repositories/TopicRepositoryException.cs @@ -29,6 +29,12 @@ public class TopicRepositoryException : DbException { /*========================================================================================================================== | CONSTRUCTOR: TAXONOMY DELETE EVENT ARGS \-------------------------------------------------------------------------------------------------------------------------*/ + /// + /// Initializes a new instance. + /// + /// The message to display for this exception. + public TopicRepositoryException() : base() { } + /// /// Initializes a new instance with a specific error message. /// From ba281ba57d58aa77a30630be3b09a0fd97bbec8f Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Fri, 25 May 2018 16:35:11 -0700 Subject: [PATCH 148/149] Removed unused `using` statements --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs | 5 ----- Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs | 3 --- Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs | 5 ----- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs | 1 - Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Collections/ReadOnlyTopicCollection.cs | 1 - Ignia.Topics/Mapping/PropertyConfiguration.cs | 4 ---- Ignia.Topics/Mapping/TopicMappingService.cs | 1 - Ignia.Topics/Properties/AssemblyInfo.cs | 4 ++-- Ignia.Topics/Topic.cs | 1 - Ignia.Topics/TopicFactory.cs | 3 --- 15 files changed, 12 insertions(+), 36 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 1c71a26c..193a60fd 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1760.0")] -[assembly: AssemblyFileVersion("3.5.1792.0")] +[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1794.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index ecdf68c5..a0bbe780 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1789.0")] -[assembly: AssemblyFileVersion("3.5.1837.0")] +[assembly: AssemblyVersion("3.5.1791.0")] +[assembly: AssemblyFileVersion("3.5.1839.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs index b9260ab9..8406464f 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicLookupService.cs @@ -3,11 +3,6 @@ | Client Ignia, LLC | Project Topics Library \=============================================================================================================================*/ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Ignia.Topics.Tests.TestDoubles { diff --git a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs index d6d17404..51488d42 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeTopicRepository.cs @@ -4,10 +4,7 @@ | Project Topics Library \=============================================================================================================================*/ using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Threading.Tasks; -using Ignia.Topics.Collections; using Ignia.Topics.Querying; using Ignia.Topics.Repositories; diff --git a/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs index be60d25f..c5a662b4 100644 --- a/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs +++ b/Ignia.Topics.Tests/TestDoubles/FakeViewModelLookupService.cs @@ -3,11 +3,6 @@ | Client Ignia, LLC | Project Topics Library \=============================================================================================================================*/ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Ignia.Topics.Tests.ViewModels; using Ignia.Topics.ViewModels; diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index 9b1fa879..f071d07b 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1760.0")] -[assembly: AssemblyFileVersion("3.5.1791.0")] +[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1793.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs b/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs index 0d6a35c2..6bc39a59 100644 --- a/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs +++ b/Ignia.Topics.Web.Mvc/Models/NavigationViewModel{T}.cs @@ -3,7 +3,6 @@ | Client Ignia, LLC | Project Topics Library \=============================================================================================================================*/ - using Ignia.Topics.ViewModels; namespace Ignia.Topics.Web.Mvc.Models { diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 15af7ecb..4ac9227b 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1765.0")] -[assembly: AssemblyFileVersion("3.5.1797.0")] +[assembly: AssemblyVersion("3.5.1767.0")] +[assembly: AssemblyFileVersion("3.5.1799.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index e798cd74..f64b9c0c 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1757.0")] -[assembly: AssemblyFileVersion("3.5.1781.0")] +[assembly: AssemblyVersion("3.5.1759.0")] +[assembly: AssemblyFileVersion("3.5.1783.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Collections/ReadOnlyTopicCollection.cs b/Ignia.Topics/Collections/ReadOnlyTopicCollection.cs index 6ced2a8a..30cd785a 100644 --- a/Ignia.Topics/Collections/ReadOnlyTopicCollection.cs +++ b/Ignia.Topics/Collections/ReadOnlyTopicCollection.cs @@ -3,7 +3,6 @@ | Client Ignia, LLC | Project Topics Library \=============================================================================================================================*/ - using System.Collections.Generic; using System.Diagnostics.Contracts; diff --git a/Ignia.Topics/Mapping/PropertyConfiguration.cs b/Ignia.Topics/Mapping/PropertyConfiguration.cs index 2ab29505..0cf37702 100644 --- a/Ignia.Topics/Mapping/PropertyConfiguration.cs +++ b/Ignia.Topics/Mapping/PropertyConfiguration.cs @@ -5,14 +5,10 @@ \=============================================================================================================================*/ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Ignia.Topics.Collections; namespace Ignia.Topics.Mapping { diff --git a/Ignia.Topics/Mapping/TopicMappingService.cs b/Ignia.Topics/Mapping/TopicMappingService.cs index b24b8f31..3c757fc7 100644 --- a/Ignia.Topics/Mapping/TopicMappingService.cs +++ b/Ignia.Topics/Mapping/TopicMappingService.cs @@ -11,7 +11,6 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Ignia.Topics.Collections; using Ignia.Topics.Reflection; using Ignia.Topics.Repositories; using Ignia.Topics.ViewModels; diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index 1bd0f7f1..e8a2cff9 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1760.0")] -[assembly: AssemblyFileVersion("3.5.1792.0")] +[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyFileVersion("3.5.1794.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)] [assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")] diff --git a/Ignia.Topics/Topic.cs b/Ignia.Topics/Topic.cs index e1482029..f4a757aa 100644 --- a/Ignia.Topics/Topic.cs +++ b/Ignia.Topics/Topic.cs @@ -9,7 +9,6 @@ using System.Globalization; using System.Linq; using Ignia.Topics.Collections; -using Ignia.Topics.Repositories; namespace Ignia.Topics { diff --git a/Ignia.Topics/TopicFactory.cs b/Ignia.Topics/TopicFactory.cs index 91016a18..bb782be2 100644 --- a/Ignia.Topics/TopicFactory.cs +++ b/Ignia.Topics/TopicFactory.cs @@ -4,11 +4,8 @@ | Project Topics Library \=============================================================================================================================*/ using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; using System.Text.RegularExpressions; -using Ignia.Topics.Reflection; namespace Ignia.Topics { From 02d003fa3ed8847217d1b01ce72954fef5e76aae Mon Sep 17 00:00:00 2001 From: Jeremy Caney Date: Tue, 5 Jun 2018 15:13:09 -0700 Subject: [PATCH 149/149] Increment minor version In preparation for pull request, increment minor version. --- Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Tests/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics.Web/Properties/AssemblyInfo.cs | 2 +- Ignia.Topics/Properties/AssemblyInfo.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs index 193a60fd..7817e369 100644 --- a/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyVersion("3.6.1762.0")] [assembly: AssemblyFileVersion("3.5.1794.0")] [assembly: CLSCompliant(true)] [assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")] diff --git a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs index 63e51450..7c43f8c1 100644 --- a/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Data.Sql/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1739.0")] +[assembly: AssemblyVersion("3.6.1739.0")] [assembly: AssemblyFileVersion("3.5.1763.0")] [assembly: CLSCompliant(true)] [assembly: Guid("1de1f923-c7c2-435b-b49a-975acbcb5ff0")] diff --git a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs index a0bbe780..47a6b760 100644 --- a/Ignia.Topics.Tests/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Tests/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1791.0")] +[assembly: AssemblyVersion("3.6.1791.0")] [assembly: AssemblyFileVersion("3.5.1839.0")] [assembly: CLSCompliant(true)] [assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")] diff --git a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs index f071d07b..d3c63ed1 100644 --- a/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyVersion("3.6.1762.0")] [assembly: AssemblyFileVersion("3.5.1793.0")] [assembly: CLSCompliant(true)] [assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")] diff --git a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs index 4ac9227b..2a603a7c 100644 --- a/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1767.0")] +[assembly: AssemblyVersion("3.6.1767.0")] [assembly: AssemblyFileVersion("3.5.1799.0")] [assembly: CLSCompliant(true)] [assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")] diff --git a/Ignia.Topics.Web/Properties/AssemblyInfo.cs b/Ignia.Topics.Web/Properties/AssemblyInfo.cs index f64b9c0c..d933bf58 100644 --- a/Ignia.Topics.Web/Properties/AssemblyInfo.cs +++ b/Ignia.Topics.Web/Properties/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1759.0")] +[assembly: AssemblyVersion("3.6.1759.0")] [assembly: AssemblyFileVersion("3.5.1783.0")] [assembly: CLSCompliant(true)] [assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")] diff --git a/Ignia.Topics/Properties/AssemblyInfo.cs b/Ignia.Topics/Properties/AssemblyInfo.cs index e8a2cff9..cbd22887 100644 --- a/Ignia.Topics/Properties/AssemblyInfo.cs +++ b/Ignia.Topics/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("3.5.1762.0")] +[assembly: AssemblyVersion("3.6.1762.0")] [assembly: AssemblyFileVersion("3.5.1794.0")] [assembly: InternalsVisibleTo("Ignia.Topics.Tests")] [assembly: CLSCompliant(true)]