Skip to content

Commit 25b99b4

Browse files
committed
Added (optional) version parameter to SetValue()
In order to allow an attribute's `LastModified` property to be stored as metadata, the `SetValue()` method needs to (optionally) accept this. Arguably, this _might_ make more sense being placed after `value`; since mapped properties will be calling `enforceBusinessLogic`, however, but _not_ supplying `lastModified`, we're placing it last. Regardless, as ancillary metdata on an internal overload, the order isn't too critical here. Since this is an advanced infrastructure requirement for tooling (e.g., loading, importing topics) this is _not_ being added to convenience extensions like `SetBooleanValue()`.
1 parent ef6fa83 commit 25b99b4

4 files changed

Lines changed: 39 additions & 27 deletions

File tree

OnTopic/Attributes/AttributeSetterAttribute.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ namespace OnTopic.Attributes {
1313
\---------------------------------------------------------------------------------------------------------------------------*/
1414
/// <summary>
1515
/// Flags that a property should be used when setting an attribute via
16-
/// <see cref="AttributeValueCollection.SetValue(String, String, Boolean?)"/>.
16+
/// <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, DateTime?)"/>.
1717
/// </summary>
1818
/// <remarks>
1919
/// <para>
20-
/// When a call is made to <see cref="AttributeValueCollection.SetValue(String, String, Boolean?)"/>, the code will check
21-
/// to see if a property with the same name as the attribute key exists, and whether that property is decorated with the
22-
/// <see cref="AttributeSetterAttribute"/> (i.e., <code>[AttributeSetter]</code>). If it is, then the update will be
23-
/// routed through that property. This ensures that business logic is enforced by local properties, instead of allowing
20+
/// When a call is made to <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, DateTime?)"/>, the code
21+
/// will check to see if a property with the same name as the attribute key exists, and whether that property is decorated
22+
/// with the <see cref="AttributeSetterAttribute"/> (i.e., <code>[AttributeSetter]</code>). If it is, then the update will
23+
/// be routed through that property. This ensures that business logic is enforced by local properties, instead of allowing
2424
/// business logic to be potentially bypassed by writing directly to the <see cref="Topic.Attributes"/> collection.
2525
/// </para>
2626
/// <para>
@@ -33,10 +33,10 @@ namespace OnTopic.Attributes {
3333
/// </para>
3434
/// <para>
3535
/// To ensure this logic, it is critical that implementers of <see cref="AttributeSetterAttribute"/> ensure that the
36-
/// property setters call <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, Boolean)"/> overload with
37-
/// the final parameter set to false to disable the enforcement of business logic. Otherwise, an infinite loop will occur.
38-
/// Calling that overload tells <see cref="AttributeValueCollection"/> that the business logic has already been enforced
39-
/// by the caller. As this is an internal overload, implementers should use the local proxy at
36+
/// property setters call <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, Boolean, DateTime?)"/>
37+
/// overload with the final parameter set to false to disable the enforcement of business logic. Otherwise, an infinite
38+
/// loop will occur. Calling that overload tells <see cref="AttributeValueCollection"/> that the business logic has
39+
/// already been enforced by the caller. As this is an internal overload, implementers should use the local proxy at
4040
/// <see cref="Topic.SetAttributeValue(String, String, Boolean?)"/>, which ensures that final parameter is set to false.
4141
/// </para>
4242
/// </remarks>

OnTopic/Attributes/AttributeValue.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ namespace OnTopic.Attributes {
3333
/// <para>
3434
/// This class is immutable: once it is constructed, the values cannot be changed. To change a value, callers must either
3535
/// create a new instance of the <see cref="AttributeValue"/> class or, preferably, call the
36-
/// <see cref="Topic.Attributes"/>'s <see cref="AttributeValueCollection.SetValue(String, String, Boolean?)"/> method.
36+
/// <see cref="Topic.Attributes"/>'s <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, DateTime?)"/>
37+
/// method.
3738
/// </para>
3839
/// </remarks>
3940
public class AttributeValue {
@@ -163,12 +164,12 @@ internal AttributeValue(
163164
/// <see cref="AttributeValueCollection"/>.
164165
/// </summary>
165166
/// <remarks>
166-
/// By default, when a user attempts to update an attribute's value by calling
167-
/// <see cref="AttributeValueCollection.SetValue(String, String, Boolean?)"/>, or when an <see cref="AttributeValue"/> is
168-
/// added to the <see cref="AttributeValueCollection"/>, the <see cref="AttributeValueCollection"/> will automatically
169-
/// attempt to call any corresponding setters on <see cref="Topic"/> (or a derived instance) to ensure that the business
170-
/// logic is enforced. To avoid an infinite loop, however, this is disabled when properties on <see cref="Topic"/> call
171-
/// <see cref="Topic.SetAttributeValue(String, String, Boolean?)"/>. When that happens, the
167+
/// By default, when a user attempts to update an attribute's value by calling <see
168+
/// cref="AttributeValueCollection.SetValue(String, String, Boolean?, DateTime?)"/>, or when an <see cref="AttributeValue"
169+
/// /> is added to the <see cref="AttributeValueCollection"/>, the <see cref="AttributeValueCollection"/> will
170+
/// automatically attempt to call any corresponding setters on <see cref="Topic"/> (or a derived instance) to ensure that
171+
/// the business logic is enforced. To avoid an infinite loop, however, this is disabled when properties on <see
172+
/// cref="Topic"/> call <see cref="Topic.SetAttributeValue(String, String, Boolean?)"/>. When that happens, the
172173
/// <see cref="EnforceBusinessLogic"/> value is set to false to communicate to the <see cref="AttributeValueCollection"/>
173174
/// that it should not call the local property. This value is only intended for internal use.
174175
/// </remarks>

OnTopic/Collections/AttributeValueCollection.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ public bool IsDirty(string name) {
204204
/// overwritten to accept whatever value is submitted. This can be used, for instance, to prevent an update from being
205205
/// persisted to the data store on <see cref="Repositories.ITopicRepository.Save(Topic, Boolean, Boolean)"/>.
206206
/// </param>
207+
/// <param name="version">
208+
/// The <see cref="DateTime"/> value that the attribute was last modified. This is intended exclusively for use when
209+
/// populating the topic graph from a persistent data store as a means of indicating the current version for each
210+
/// attribute. This is used when e.g. importing values to determine if the existing value is newer than the source value.
211+
/// </param>
207212
/// <requires
208213
/// description="The key must be specified for the AttributeValue key/value pair."
209214
/// exception="T:System.ArgumentNullException">
@@ -219,7 +224,8 @@ public bool IsDirty(string name) {
219224
/// exception="T:System.ArgumentException">
220225
/// !value.Contains(" ")
221226
/// </requires>
222-
public void SetValue(string key, string? value, bool? isDirty = null) => SetValue(key, value, isDirty, true);
227+
public void SetValue(string key, string? value, bool? isDirty = null, DateTime? version = null)
228+
=> SetValue(key, value, isDirty, true, version);
223229

224230
/// <summary>
225231
/// Protected helper method that either adds a new <see cref="AttributeValue"/> object or updates the value of an existing
@@ -242,6 +248,11 @@ public bool IsDirty(string name) {
242248
/// Instructs the underlying code to call corresponding properties, if available, to ensure business logic is enforced.
243249
/// This should be set to false if setting attributes from internal properties in order to avoid an infinite loop.
244250
/// </param>
251+
/// <param name="version">
252+
/// The <see cref="DateTime"/> value that the attribute was last modified. This is intended exclusively for use when
253+
/// populating the topic graph from a persistent data store as a means of indicating the current version for each
254+
/// attribute. This is used when e.g. importing values to determine if the existing value is newer than the source value.
255+
/// </param>
245256
/// <requires
246257
/// description="The key must be specified for the AttributeValue key/value pair."
247258
/// exception="T:System.ArgumentNullException">
@@ -257,7 +268,7 @@ public bool IsDirty(string name) {
257268
/// exception="T:System.ArgumentException">
258269
/// !value.Contains(" ")
259270
/// </requires>
260-
internal void SetValue(string key, string? value, bool? isDirty, bool enforceBusinessLogic) {
271+
internal void SetValue(string key, string? value, bool? isDirty, bool enforceBusinessLogic, DateTime? version = null) {
261272

262273
/*------------------------------------------------------------------------------------------------------------------------
263274
| Validate input
@@ -292,15 +303,15 @@ internal void SetValue(string key, string? value, bool? isDirty, bool enforceBus
292303
else if (originalAttribute.Value != value) {
293304
markAsDirty = true;
294305
}
295-
var newAttribute = new AttributeValue(key, value, markAsDirty, enforceBusinessLogic);
306+
var newAttribute = new AttributeValue(key, value, markAsDirty, enforceBusinessLogic, version);
296307
this[IndexOf(originalAttribute)] = newAttribute;
297308
}
298309

299310
/*------------------------------------------------------------------------------------------------------------------------
300311
| Create new attribute value
301312
\-----------------------------------------------------------------------------------------------------------------------*/
302313
else {
303-
Add(new AttributeValue(key, value, isDirty ?? true, enforceBusinessLogic));
314+
Add(new AttributeValue(key, value, isDirty ?? true, enforceBusinessLogic, version));
304315
}
305316

306317
}
@@ -316,8 +327,8 @@ internal void SetValue(string key, string? value, bool? isDirty, bool enforceBus
316327
/// <para>
317328
/// If a settable property is available corresponding to the <see cref="AttributeValue.Key"/>, the call should be routed
318329
/// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is
319-
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean)"/>'s enforceBusinessLogic parameter. To avoid an
320-
/// infinite loop, internal setters _must_ call this overload.
330+
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean, DateTime?)"/>'s enforceBusinessLogic parameter. To
331+
/// avoid an infinite loop, internal setters _must_ call this overload.
321332
/// </para>
322333
/// <para>
323334
/// Compared to the base implementation, will throw a specific <see cref="ArgumentException"/> error if a duplicate key
@@ -357,8 +368,8 @@ protected override void InsertItem(int index, AttributeValue item) {
357368
/// <remarks>
358369
/// If a settable property is available corresponding to the <see cref="AttributeValue.Key"/>, the call should be routed
359370
/// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is
360-
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean)"/>'s enforceBusinessLogic parameter. To avoid an
361-
/// infinite loop, internal setters _must_ call this overload.
371+
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean, DateTime?)"/>'s enforceBusinessLogic parameter. To
372+
/// avoid an infinite loop, internal setters _must_ call this overload.
362373
/// </remarks>
363374
/// <param name="index">The location that the <see cref="AttributeValue"/> should be set.</param>
364375
/// <param name="item">The <see cref="AttributeValue"/> object which is being inserted.</param>
@@ -379,8 +390,8 @@ protected override void SetItem(int index, AttributeValue item) {
379390
/// <remarks>
380391
/// If a settable property is available corresponding to the <see cref="AttributeValue.Key"/>, the call should be routed
381392
/// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is
382-
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean)"/>'s enforceBusinessLogic parameter. To avoid an
383-
/// infinite loop, internal setters _must_ call this overload.
393+
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean, DateTime?)"/>'s enforceBusinessLogic parameter. To
394+
/// avoid an infinite loop, internal setters _must_ call this overload.
384395
/// </remarks>
385396
/// <param name="originalAttribute">The <see cref="AttributeValue"/> object which is being inserted.</param>
386397
/// <param name="settableAttribute">

OnTopic/Topic.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ public Topic? DerivedTopic {
635635
/// called by the <see cref="AttributeValueCollection"/>. This is intended to enforce local business logic, and prevent
636636
/// callers from introducing invalid data.To prevent a redirect loop, however, local properties need to inform the
637637
/// <see cref="AttributeValueCollection"/> that the business logic has already been enforced. To do that, they must either
638-
/// call <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, Boolean)"/> with the
638+
/// call <see cref="AttributeValueCollection.SetValue(String, String, Boolean?, Boolean, DateTime?)"/> with the
639639
/// <c>enforceBusinessLogic</c> flag set to <c>false</c>, or, if they're in a separate assembly, call this overload.
640640
/// </remarks>
641641
/// <param name="key">The string identifier for the AttributeValue.</param>

0 commit comments

Comments
 (0)