Skip to content

Commit 814af03

Browse files
committed
Moved legacy DefaultConfiguration members to new ConfigurableAttributeDescriptor
In the legacy **ASP.NET WebForms** version of the `Topic-Editor`, a delimited string, `DefaultConfiguration`, contained an untyped assortment of attribute type configuration values, which could be parsed using the `Configuration` dictionary, and accessed with the `GetConfigurationValue()` method. Those aren't needed with the new `AttributeTypeDescriptor` approach, which allow those value to be set and accessed using attributes that are part of the schema. To allow the library to move forward while maintaining backward compatibility, these members are moved off to a new `ConfigurableAttributeDescriptor` class in the `Ignia.Topics.Web` assembly. This is a breaking change, as it will require that all `AttributeTypeDescriptor` objects in the database be updated to a new `ConfigurableAttributeDescriptor` content type. It will also require that the default `TypeLookupService` on `TopicFactory` be replaced with the `WebFormsTopicLookupService` in **WebForms** projects. To help enforce this logic, and force projects to use one of the two type systems, the `AttributeDescriptor` class is now marked as `abstract`.
1 parent c817ded commit 814af03

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using Ignia.Topics.Attributes;
10+
using Ignia.Topics.Internal.Diagnostics;
11+
using Ignia.Topics.Metadata;
12+
13+
namespace Ignia.Topics.Web.Editor {
14+
15+
/*============================================================================================================================
16+
| CLASS: CONFIGURABLE ATTRIBUTE DESCRIPTOR
17+
\---------------------------------------------------------------------------------------------------------------------------*/
18+
/// <summary>
19+
/// Represents metadata for describing an attribute, including information on how it will be presented and validated in the
20+
/// editor.
21+
/// </summary>
22+
/// <remarks>
23+
/// Extends the out-of-the-box <see cref="AttributeDescriptor"/> with a version that can be extended via a <see
24+
/// cref="DefaultConfiguration"/>. This is used by the legacy ASP.NET WebForms version of the OnTopic Editor. Newer releases
25+
/// instead use the <see cref="AttributeTypeDescriptor"/> content types, which provide configurable values via attributes.
26+
/// </remarks>
27+
public class ConfigurableAttributeDescriptor : AttributeDescriptor {
28+
29+
/*==========================================================================================================================
30+
| PRIVATE VARIABLES
31+
\-------------------------------------------------------------------------------------------------------------------------*/
32+
private Dictionary<string, string> _configuration;
33+
private ModelType? _modelType = null;
34+
35+
/*==========================================================================================================================
36+
| CONSTRUCTOR
37+
\-------------------------------------------------------------------------------------------------------------------------*/
38+
/// <summary>
39+
/// Initializes a new instance of a <see cref="ConfigurableAttributeDescriptor"/> class with a <see cref="Topic.Key"/>,
40+
/// <see cref="Topic.ContentType"/>, and, optionally, <see cref="Topic.Parent"/>, <see cref="Topic.Id"/>.
41+
/// </summary>
42+
/// <remarks>
43+
/// By default, when creating new attributes, the <see cref="AttributeValue"/>s for both <see cref="Topic.Key"/> and <see
44+
/// cref="Topic.ContentType"/> will be set to <see cref="AttributeValue.IsDirty"/>, which is required in order to
45+
/// correctly save new topics to the database. When the <paramref name="id"/> parameter is set, however, the <see
46+
/// cref="AttributeValue.IsDirty"/> property is set to <c>false</c> on <see cref="Topic.Key"/> as well as on <see
47+
/// cref="Topic.ContentType"/>, since it is assumed these are being set to the same values currently used in the
48+
/// persistence store.
49+
/// </remarks>
50+
/// <param name="key">A string representing the key for the new topic instance.</param>
51+
/// <param name="contentType">A string representing the key of the target content type.</param>
52+
/// <param name="id">The unique identifier assigned by the data store for an existing topic.</param>
53+
/// <param name="parent">Optional topic to set as the new topic's parent.</param>
54+
/// <exception cref="ArgumentException">
55+
/// Thrown when the class representing the content type is found, but doesn't derive from <see cref="Topic"/>.
56+
/// </exception>
57+
/// <returns>A strongly-typed instance of the <see cref="Topic"/> class based on the target content type.</returns>
58+
public ConfigurableAttributeDescriptor(
59+
string key,
60+
string contentType,
61+
Topic parent,
62+
int id = -1
63+
) : base(
64+
key,
65+
contentType,
66+
parent,
67+
id
68+
) {
69+
_configuration = new Dictionary<string, string>();
70+
}
71+
72+
/*==========================================================================================================================
73+
| PROPERTY: MODEL TYPE
74+
\-------------------------------------------------------------------------------------------------------------------------*/
75+
/// <inheritdoc />
76+
public override ModelType ModelType {
77+
get {
78+
79+
/*----------------------------------------------------------------------------------------------------------------------
80+
| Return cached value
81+
\---------------------------------------------------------------------------------------------------------------------*/
82+
if (_modelType.HasValue) {
83+
return _modelType.Value;
84+
}
85+
86+
/*----------------------------------------------------------------------------------------------------------------------
87+
| Determine value
88+
\---------------------------------------------------------------------------------------------------------------------*/
89+
var editorType = EditorType;
90+
91+
Contract.Assume(
92+
editorType,
93+
$"The 'EditorType' property is not set for the '{Key}' attribute, and thus a 'ModelType' cannot be determined."
94+
);
95+
96+
if (editorType.LastIndexOf(".", StringComparison.InvariantCulture) >= 0) {
97+
editorType = editorType.Substring(0, editorType.LastIndexOf(".", StringComparison.InvariantCulture));
98+
}
99+
100+
if (new[] { "Relationships", "TokenizedTopicList" }.Contains(editorType)) {
101+
_modelType = ModelType.Relationship;
102+
}
103+
else if (
104+
new[] { "TopicPointer" }.Contains(editorType) ||
105+
Key.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase) &&
106+
!Key.Equals("Id", StringComparison.InvariantCultureIgnoreCase)
107+
) {
108+
_modelType = ModelType.Reference;
109+
}
110+
else if (new[] { "TopicList" }.Contains(editorType)) {
111+
_modelType = ModelType.NestedTopic;
112+
}
113+
else {
114+
_modelType = ModelType.ScalarValue;
115+
}
116+
117+
/*----------------------------------------------------------------------------------------------------------------------
118+
| Return value
119+
\---------------------------------------------------------------------------------------------------------------------*/
120+
return _modelType.Value;
121+
122+
}
123+
}
124+
125+
/*==========================================================================================================================
126+
| PROPERTY: EDITOR TYPE
127+
\-------------------------------------------------------------------------------------------------------------------------*/
128+
/// <inheritdoc />
129+
public override string EditorType => Attributes.GetValue("Type", "");
130+
131+
/*==========================================================================================================================
132+
| PROPERTY: DEFAULT CONFIGURATION
133+
\-------------------------------------------------------------------------------------------------------------------------*/
134+
/// <summary>
135+
/// Gets or sets the default configuration.
136+
/// </summary>
137+
/// <remarks>
138+
/// <para>
139+
/// When an attribute is bound to an attribute type control in the editor, the default configuration is injected into
140+
/// the control's configuration. This allows attribute type-specific properties to be set on a per-attribute basis.
141+
/// </para>
142+
/// <para>
143+
/// Properties available for configuration are up to the control associated with the <see cref="EditorType"/>, and the
144+
/// format will be dependent on the framework in which the attribute type control is written. For example, for ASP.NET
145+
/// User Controls as well as AngularJS Directives, the format is Property1="Value" Propert2="Value".
146+
/// </para>
147+
/// </remarks>
148+
public string DefaultConfiguration {
149+
get => Attributes.GetValue("DefaultConfiguration", "")?? "";
150+
set {
151+
SetAttributeValue("DefaultConfiguration", value);
152+
_configuration.Clear();
153+
}
154+
}
155+
156+
/*==========================================================================================================================
157+
| PROPERTY: CONFIGURATION
158+
\-------------------------------------------------------------------------------------------------------------------------*/
159+
/// <summary>
160+
/// Retrieves a dictionary representing a parsed collection of key/value pairs from the
161+
/// <see cref="DefaultConfiguration"/>.
162+
/// </summary>
163+
public IDictionary<string, string> Configuration {
164+
get {
165+
if (_configuration.Count.Equals(0) && DefaultConfiguration.Length > 0) {
166+
_configuration = DefaultConfiguration
167+
.Split(' ')
168+
.Select(value => value.Split('='))
169+
.ToDictionary(
170+
pair => pair[0],
171+
pair => pair.Count().Equals(2)? pair[1]?.Replace("\"", "") : null
172+
);
173+
}
174+
return _configuration;
175+
}
176+
}
177+
178+
/*==========================================================================================================================
179+
| PROPERTY: GET CONFIGURATION VALUE
180+
\-------------------------------------------------------------------------------------------------------------------------*/
181+
/// <summary>
182+
/// Retrieves a configuration value from the <see cref="Configuration"/> dictionary; if the value doesn't exist, then
183+
/// optionally returns a default value.
184+
/// </summary>
185+
public string GetConfigurationValue(string key, string defaultValue = null) {
186+
Contract.Requires(!String.IsNullOrWhiteSpace(key));
187+
if (Configuration.ContainsKey(key) && Configuration[key] != null) {
188+
return Configuration[key];
189+
}
190+
return defaultValue;
191+
}
192+
193+
/*==========================================================================================================================
194+
| PROPERTY: STORE IN BLOB?
195+
\-------------------------------------------------------------------------------------------------------------------------*/
196+
/// <summary>
197+
/// Gets or sets whether or not the attribute is stored as part of the attributes XML.
198+
/// </summary>
199+
/// <remarks>
200+
/// <para>
201+
/// By default, all attributes are stored in the blob, which means they may not be loaded initially, and not accessible
202+
/// to in-memory queries. This is more efficient to store, and is required for larger values.
203+
/// <para>
204+
/// Attributes that are needed to provide indexes, sitemaps, navigation, etc. should be indexed so that they're always
205+
/// available in memory without requiring an additional database query. These increase the memory requirements of the
206+
/// application, but reduce the number of database round-trips required for topics that are accessed outside of a single
207+
/// page. For instance, the title and description of a topic may be cross-referenced on other pages or as part of the
208+
/// navigation, and should thus be indexed. Indexed attributes are those not stored as a blob.
209+
/// </para>
210+
/// <para>
211+
/// This property and its corresponding attribute was named <c>StoreInBlob</c> in versions of OnTopic prior to 4.0.
212+
/// </para>
213+
/// </remarks>
214+
[AttributeSetter]
215+
public virtual bool StoreInBlob {
216+
get => Attributes.GetBoolean("StoreInBlob", true);
217+
set => SetAttributeValue("StoreInBlob", value ? "1" : "0");
218+
}
219+
220+
} //Class
221+
} //Namespace
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
using System;
7+
using System.Collections.Generic;
8+
using Ignia.Topics.Metadata;
9+
using Ignia.Topics.Web.Editor;
10+
11+
namespace Ignia.Topics {
12+
13+
/*============================================================================================================================
14+
| CLASS: TOPIC LOOKUP SERVICE (WEB FORMS)
15+
\---------------------------------------------------------------------------------------------------------------------------*/
16+
/// <summary>
17+
/// The <see cref="WebFormsTopicLookupService"/> provides access to all of the standard <see cref="Topic"/> types available
18+
/// in the <see cref="DefaultTopicLookupService"/>, as well as custom <see cref="Topic"/>s available for WebForms.
19+
/// </summary>
20+
public class WebFormsTopicLookupService: DefaultTopicLookupService {
21+
22+
/*==========================================================================================================================
23+
| CONSTRUCTOR
24+
\-------------------------------------------------------------------------------------------------------------------------*/
25+
/// <summary>
26+
/// Establishes a new instance of a <see cref="WebFormsTopicLookupService"/>. Optionally accepts a list of <see
27+
/// cref="Type"/> instances and a default <see cref="Type"/> value.
28+
/// </summary>
29+
/// <remarks><inheritdoc /></remarks>
30+
/// <param name="types"><inheritdoc /></param>
31+
/// <param name="defaultType"><inheritdoc /></param>
32+
public WebFormsTopicLookupService(IEnumerable<Type> types = null, Type defaultType = null) :
33+
base(types, defaultType?? typeof(Topic)) {
34+
35+
/*------------------------------------------------------------------------------------------------------------------------
36+
| Ensure editor types are accounted for
37+
\-----------------------------------------------------------------------------------------------------------------------*/
38+
if (!Contains(nameof(ContentTypeDescriptor))) Add(typeof(ConfigurableAttributeDescriptor));
39+
40+
}
41+
42+
} //Class
43+
} //Namespace

0 commit comments

Comments
 (0)