Skip to content

Commit 10d0652

Browse files
committed
Merge branch 'feature/TopicViewModel-records' into develop
The new C# 9.0 record feature is optimal for view models and binding models, which we don't expect to change after they've been generated. Records are also potentially a bit faster, since they're immutable. Finally, in the rare case that we _do_ need to make modifications, the `record` syntax makes it easy to generate new versions via shallow copies via the `with` syntax. The nature of records mandates that all downstream implementations of view and binding models are also converted to records, so this is (very much!) a breaking change. Since view models _should_ be read-only, however, and will generally be mapped using e.g. the `ITopicMappingService`, we don't expect this to be a difficult migration for adopters.
2 parents bbc035d + 1872dda commit 10d0652

26 files changed

Lines changed: 90 additions & 71 deletions

OnTopic.Tests/BindingModels/BasicTopicBindingModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ public BasicTopicBindingModel(string? key, string? contentType) {
2626
ContentType = contentType;
2727
}
2828

29-
public string? Key { get; set; }
29+
public string? Key { get; init; }
3030

3131
[Required]
32-
public string? ContentType { get; set; }
32+
public string? ContentType { get; init; }
3333

3434
} //Class
3535
} //Namespace

OnTopic.Tests/ReverseTopicMappingServiceTest.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ public async Task Map_ExceedsMinimumValue_ThrowsValidationException() {
369369
/// cref="InvalidOperationException"/>.
370370
/// </summary>
371371
[TestMethod]
372-
[ExpectedException(typeof(TopicMappingException))]
372+
[ExpectedException(typeof(MappingModelValidationException))]
373373
public async Task Map_InvalidChildrenProperty_ThrowsInvalidOperationException() {
374374

375375
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -387,7 +387,7 @@ public async Task Map_InvalidChildrenProperty_ThrowsInvalidOperationException()
387387
/// cref="InvalidOperationException"/>.
388388
/// </summary>
389389
[TestMethod]
390-
[ExpectedException(typeof(TopicMappingException))]
390+
[ExpectedException(typeof(MappingModelValidationException))]
391391
public async Task Map_InvalidParentProperty_ThrowsInvalidOperationException() {
392392

393393
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -408,7 +408,7 @@ public async Task Map_InvalidParentProperty_ThrowsInvalidOperationException() {
408408
/// <see cref="InvalidOperationException"/>.
409409
/// </summary>
410410
[TestMethod]
411-
[ExpectedException(typeof(TopicMappingException))]
411+
[ExpectedException(typeof(MappingModelValidationException))]
412412
public async Task Map_InvalidAttribute_ThrowsInvalidOperationException() {
413413

414414
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -426,7 +426,7 @@ public async Task Map_InvalidAttribute_ThrowsInvalidOperationException() {
426426
/// is invalid, and expected to throw an <see cref="InvalidOperationException"/>.
427427
/// </summary>
428428
[TestMethod]
429-
[ExpectedException(typeof(TopicMappingException))]
429+
[ExpectedException(typeof(MappingModelValidationException))]
430430
public async Task Map_InvalidRelationshipBaseType_ThrowsInvalidOperationException() {
431431

432432
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -446,7 +446,7 @@ public async Task Map_InvalidRelationshipBaseType_ThrowsInvalidOperationExceptio
446446
/// cref="InvalidOperationException"/>.
447447
/// </summary>
448448
[TestMethod]
449-
[ExpectedException(typeof(TopicMappingException))]
449+
[ExpectedException(typeof(MappingModelValidationException))]
450450
public async Task Map_InvalidRelationshipType_ThrowsInvalidOperationException() {
451451

452452
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -465,7 +465,7 @@ public async Task Map_InvalidRelationshipType_ThrowsInvalidOperationException()
465465
/// cref="IList"/>. This is invalid, and expected to throw an <see cref="InvalidOperationException"/>.
466466
/// </summary>
467467
[TestMethod]
468-
[ExpectedException(typeof(TopicMappingException))]
468+
[ExpectedException(typeof(MappingModelValidationException))]
469469
public async Task Map_InvalidRelationshipListType_ThrowsInvalidOperationException() {
470470

471471
var mappingService = new ReverseTopicMappingService(_topicRepository);
@@ -484,7 +484,7 @@ public async Task Map_InvalidRelationshipListType_ThrowsInvalidOperationExceptio
484484
/// cref="IRelatedTopicBindingModel"/>. This is invalid, and expected to throw an <see cref="InvalidOperationException"/>.
485485
/// </summary>
486486
[TestMethod]
487-
[ExpectedException(typeof(TopicMappingException))]
487+
[ExpectedException(typeof(MappingModelValidationException))]
488488
public async Task Map_InvalidTopicReferenceType_ThrowsInvalidOperationException() {
489489

490490
var mappingService = new ReverseTopicMappingService(_topicRepository);

OnTopic.Tests/ViewModels/DescendentSpecializedTopicViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace OnTopic.Tests.ViewModels {
2424
/// This is a sample class intended for test purposes only; it is not designed for use in a production environment.
2525
/// </para>
2626
/// </remarks>
27-
public class DescendentSpecializedTopicViewModel: DescendentTopicViewModel {
27+
public record DescendentSpecializedTopicViewModel: DescendentTopicViewModel {
2828

2929
public bool IsLeaf { get; set; }
3030

OnTopic.Tests/ViewModels/DescendentTopicViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace OnTopic.Tests.ViewModels {
2323
/// This is a sample class intended for test purposes only; it is not designed for use in a production environment.
2424
/// </para>
2525
/// </remarks>
26-
public class DescendentTopicViewModel: TopicViewModel {
26+
public record DescendentTopicViewModel: TopicViewModel {
2727

2828
[Follow(Relationships.Children)]
2929
public TopicViewModelCollection<DescendentTopicViewModel> Children { get; } = new();

OnTopic.ViewModels/BindingModels/RelatedTopicBindingModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace OnTopic.ViewModels.BindingModels {
2121
/// cref="ReverseTopicMappingService"/>. The only reason to implement a custom definition is if the caller needs additional
2222
/// metadata for separate validation or processing.
2323
/// </remarks>
24-
public class RelatedTopicBindingModel : IRelatedTopicBindingModel {
24+
public record RelatedTopicBindingModel : IRelatedTopicBindingModel {
2525

2626
/*==========================================================================================================================
2727
| PROPERTY: UNIQUE KEY
@@ -33,7 +33,7 @@ public class RelatedTopicBindingModel : IRelatedTopicBindingModel {
3333
/// value is not null
3434
/// </requires>
3535
[Required]
36-
public string? UniqueKey { get; set; }
36+
public string? UniqueKey { get; init; }
3737

3838
} //Class
3939
} //Namespaces

OnTopic.ViewModels/ContentItemTopicViewModel.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,39 @@ namespace OnTopic.ViewModels {
1818
/// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They
1919
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
2020
/// </remarks>
21-
public class ContentItemTopicViewModel: ItemTopicViewModel {
21+
public record ContentItemTopicViewModel: ItemTopicViewModel {
2222

2323
/*==========================================================================================================================
2424
| DESCRIPTION
2525
\-------------------------------------------------------------------------------------------------------------------------*/
2626
/// <summary>
2727
/// Gets the description; for Content Items, this is effectively the body.
2828
/// </summary>
29-
public string Description { get; set; } = default!;
29+
public string Description { get; init; } = default!;
3030

3131
/*==========================================================================================================================
3232
| LEARN MORE URL
3333
\-------------------------------------------------------------------------------------------------------------------------*/
3434
/// <summary>
3535
/// Gets an optional URL for additional information that should be linked to.
3636
/// </summary>
37-
public Uri? LearnMoreUrl { get; set; }
37+
public Uri? LearnMoreUrl { get; init; }
3838

3939
/*==========================================================================================================================
4040
| THUMBNAIL IMAGE
4141
\-------------------------------------------------------------------------------------------------------------------------*/
4242
/// <summary>
4343
/// Gets an optional path to a thumbnail image that should accompany the content item.
4444
/// </summary>
45-
public Uri? ThumbnailImage { get; set; }
45+
public Uri? ThumbnailImage { get; init; }
4646

4747
/*==========================================================================================================================
4848
| CATEGORY
4949
\-------------------------------------------------------------------------------------------------------------------------*/
5050
/// <summary>
5151
/// Gets the category that the content item should be grouped under.
5252
/// </summary>
53-
public string? Category { get; set; }
53+
public string? Category { get; init; }
5454

5555
} //Class
5656
} //Namespace

OnTopic.ViewModels/ContentListTopicViewModel.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace OnTopic.ViewModels {
1717
/// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They
1818
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
1919
/// </remarks>
20-
public class ContentListTopicViewModel: PageTopicViewModel {
20+
public record ContentListTopicViewModel: PageTopicViewModel {
2121

2222
/*==========================================================================================================================
2323
| CONTENT ITEMS
@@ -51,7 +51,7 @@ public class ContentListTopicViewModel: PageTopicViewModel {
5151
/// corresponding attribute, and so this can easily be hidden or disabled globally via the editor.
5252
/// </remarks>
5353
/// <returns>True if the content list should be indexed; false otherwise.</returns>
54-
public bool IsIndexed { get; set; }
54+
public bool IsIndexed { get; init; }
5555

5656
/*==========================================================================================================================
5757
| INDEX LABEL
@@ -64,8 +64,8 @@ public class ContentListTopicViewModel: PageTopicViewModel {
6464
/// "IndexLabel"/> allows that to be optionally set on a per topic basis. The default value is "Contents", though it is up
6565
/// to view implementors and editor configurations as to whether this option is exposed or honored.
6666
/// </remarks>
67-
/// <returns>Returns the value set; defaults to "Contents".</returns>
68-
public string IndexLabel { get; set; } = "Contents";
67+
/// <returns>Returns the value init; defaults to "Contents".</returns>
68+
public string IndexLabel { get; init; } = "Contents";
6969

7070
} //Class
7171
} //Namespace

OnTopic.ViewModels/IndexTopicViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace OnTopic.ViewModels {
1717
/// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They
1818
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
1919
/// </remarks>
20-
public class IndexTopicViewModel: PageTopicViewModel {
20+
public record IndexTopicViewModel: PageTopicViewModel {
2121

2222

2323
} //Class
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
7+
namespace System.Runtime.CompilerServices {
8+
9+
/*============================================================================================================================
10+
| CLASS: IS EXTERNAL INIT
11+
\---------------------------------------------------------------------------------------------------------------------------*/
12+
/// <summary>
13+
/// The <see cref="IsExternalInit"/> class is made available as part of the .NET 5.0 CLR in order to enable init accessors.
14+
/// As this is not available in .NET Standard, however, we must maintain this separate copy until we migrate to .NET 5.0.
15+
/// </summary>
16+
internal static class IsExternalInit {
17+
18+
} //Class
19+
} //Namespace

OnTopic.ViewModels/ItemTopicViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace OnTopic.ViewModels {
1717
/// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They
1818
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
1919
/// </remarks>
20-
public class ItemTopicViewModel : TopicViewModel {
20+
public record ItemTopicViewModel : TopicViewModel {
2121

2222

2323
} //Class

0 commit comments

Comments
 (0)