Skip to content

Commit 145c2a4

Browse files
committed
Merge branch 'bugfix/ReverseTopicMappingService-records' into develop
The `TopicMappingService` takes a very permissive approach to mapping; if the target property doesn't exist on the source `Topic`, or cannot be mapped, it fails silently. By contrast, the `ReverseTopicMappingService` is much more conservative, since the results are (potentially) invasive, resulting in updating the underlying persistence store. As a result, if the source binding model has properties that cannot be mapped, or don't correspond to attributes on the `ContentTypeDescriptor`, an exception is thrown. This helps warn implementers about design flaws that might result in data loss. This introduces problems with C# 9.0 record types, however, as the compiler dynamically generates a `EqualityContract` property for each record. But as there isn't an `EqualityContract` attribute on the `ContentTypeDescriptor`, the `BindingModelValidator` will throw an exception. To mitigate that, a hard-coded exception is added to both the `ReverseTopicMappingService` and the `BindingModelValidator` to ignore properties named `EqualityContract`, treating them as though they are decorated with the `[DisableMapping]` attribute. This effectively allows binding models to be implemented as record types, just as we prefer for view models. And since we implemented our stock `OnTopic.ViewModels` as records, it also allows binding models to derive from those models.
2 parents 5950ec1 + bea5a20 commit 145c2a4

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
using System.ComponentModel.DataAnnotations;
7+
using OnTopic.Models;
8+
9+
namespace OnTopic.Tests.BindingModels {
10+
11+
/*============================================================================================================================
12+
| BINDING MODEL: RECORD
13+
\---------------------------------------------------------------------------------------------------------------------------*/
14+
/// <summary>
15+
/// Provides a strongly-typed binding model based on a C# 9.0 <c>record</c> data type to ensure that it can be properly
16+
/// mapped from.
17+
/// </summary>
18+
/// <remarks>
19+
/// This is a sample class intended for test purposes only; it is not designed for use in a production environment.
20+
/// </remarks>
21+
public class RecordTopicBindingModel : ITopicBindingModel {
22+
23+
public RecordTopicBindingModel() { }
24+
25+
public string? Key { get; init; }
26+
27+
[Required]
28+
public string? ContentType { get; init; }
29+
30+
} //Class
31+
} //Namespace

OnTopic.Tests/ReverseTopicMappingServiceTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,31 @@ public async Task Map_Existing_ReturnsUpdatedTopic() {
155155

156156
}
157157

158+
/*==========================================================================================================================
159+
| TEST: MAP: RECORD: RETURNS NEW TOPIC
160+
\-------------------------------------------------------------------------------------------------------------------------*/
161+
/// <summary>
162+
/// Establishes a <see cref="ReverseTopicMappingService"/> and tests mapping a binding model that's based on a C# 9.0
163+
/// record type.
164+
/// </summary>
165+
[TestMethod]
166+
public async Task Map_Record_ReturnsNewTopic() {
167+
168+
var mappingService = new ReverseTopicMappingService(_topicRepository);
169+
170+
var bindingModel = new RecordTopicBindingModel() {
171+
Key = "Test",
172+
ContentType = "TextAttributeDescriptor"
173+
};
174+
175+
var target = await mappingService.MapAsync<TextAttributeDescriptor>(bindingModel).ConfigureAwait(false);
176+
177+
Assert.IsNotNull(target);
178+
Assert.AreEqual<string>("Test", target.Key);
179+
Assert.AreEqual<string>("TextAttributeDescriptor", target.ContentType);
180+
181+
}
182+
158183
/*==========================================================================================================================
159184
| TEST: MAP: COMPLEX OBJECT: RETURNS FLATTENED TOPIC
160185
\-------------------------------------------------------------------------------------------------------------------------*/

OnTopic/Mapping/Reverse/BindingModelValidator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ static internal void ValidateProperty(
173173
return;
174174
}
175175

176+
/*------------------------------------------------------------------------------------------------------------------------
177+
| Skip properties injected by the compiler for record types
178+
\-----------------------------------------------------------------------------------------------------------------------*/
179+
if (configuration.Property.Name is "EqualityContract") {
180+
return;
181+
}
182+
176183
/*------------------------------------------------------------------------------------------------------------------------
177184
| Handle mapping properties from referenced objects
178185
\-----------------------------------------------------------------------------------------------------------------------*/

OnTopic/Mapping/Reverse/ReverseTopicMappingService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ private async Task SetPropertyAsync(
275275
return;
276276
}
277277

278+
/*------------------------------------------------------------------------------------------------------------------------
279+
| Skip properties injected by the compiler for record types
280+
\-----------------------------------------------------------------------------------------------------------------------*/
281+
if (configuration.Property.Name is "EqualityContract") {
282+
return;
283+
}
284+
278285
/*------------------------------------------------------------------------------------------------------------------------
279286
| Handle mapping properties from referenced objects
280287
\-----------------------------------------------------------------------------------------------------------------------*/

0 commit comments

Comments
 (0)