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

+90
-71
lines changed

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

OnTopic.ViewModels/ListTopicViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace OnTopic.ViewModels {
2525
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
2626
/// </para>
2727
/// </remarks>
28-
public class ListTopicViewModel: ContentItemTopicViewModel {
28+
public record ListTopicViewModel: ContentItemTopicViewModel {
2929

3030
} //Class
3131
} //Namespace

OnTopic.ViewModels/LookupListItemTopicViewModel.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 LookupListItemTopicViewModel: ItemTopicViewModel {
20+
public record LookupListItemTopicViewModel: ItemTopicViewModel {
2121

2222

2323
} //Class

OnTopic.ViewModels/NavigationTopicViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ namespace OnTopic.ViewModels {
2828
/// cref="NavigationTopicViewModel"/> class is marked as <c>sealed</c>.
2929
/// </para>
3030
/// </remarks>
31-
public sealed class NavigationTopicViewModel : TopicViewModel, INavigationTopicViewModel<NavigationTopicViewModel> {
31+
public sealed record NavigationTopicViewModel : TopicViewModel, INavigationTopicViewModel<NavigationTopicViewModel> {
3232

3333
/*==========================================================================================================================
3434
| SHORT TITLE
3535
\-------------------------------------------------------------------------------------------------------------------------*/
3636
/// <summary>
3737
/// Provides a short title to be used in the navigation, for cases where the normal title is too long.
3838
/// </summary>
39-
public string? ShortTitle { get; set; }
39+
public string? ShortTitle { get; init; }
4040

4141
/*==========================================================================================================================
4242
| CHILDREN

OnTopic.ViewModels/PageGroupTopicViewModel.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 PageGroupTopicViewModel : SectionTopicViewModel {
20+
public record PageGroupTopicViewModel : SectionTopicViewModel {
2121

2222

2323
} //Class

OnTopic.ViewModels/PageTopicViewModel.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,59 @@ 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 PageTopicViewModel: TopicViewModel, IPageTopicViewModel {
21+
public record PageTopicViewModel: TopicViewModel, IPageTopicViewModel {
2222

2323
/*==========================================================================================================================
2424
| SUBTITLE
2525
\-------------------------------------------------------------------------------------------------------------------------*/
2626
/// <summary>
2727
/// Provides an optional subtitle which will typically be displayed under the title.
2828
/// </summary>
29-
public string? Subtitle { get; set; }
29+
public string? Subtitle { get; init; }
3030

3131
/*==========================================================================================================================
3232
| META TITLE
3333
\-------------------------------------------------------------------------------------------------------------------------*/
3434
/// <summary>
3535
/// Provides an optional title to be used in page's metadata, if it differs from the <see cref="TopicViewModel.Title"/>.
3636
/// </summary>
37-
public string? MetaTitle { get; set; }
37+
public string? MetaTitle { get; init; }
3838

3939
/*==========================================================================================================================
4040
| META DESCRIPTION
4141
\-------------------------------------------------------------------------------------------------------------------------*/
4242
/// <inheritdoc />
43-
public string? MetaDescription { get; set; }
43+
public string? MetaDescription { get; init; }
4444

4545
/*==========================================================================================================================
4646
| META KEYWORDS
4747
\-------------------------------------------------------------------------------------------------------------------------*/
4848
/// <inheritdoc />
49-
public string? MetaKeywords { get; set; }
49+
public string? MetaKeywords { get; init; }
5050

5151
/*==========================================================================================================================
5252
| META KEYWORDS
5353
\-------------------------------------------------------------------------------------------------------------------------*/
5454
/// <summary>
5555
/// Determines whether or not search engines are expected to index the page.
5656
/// </summary>
57-
public bool? NoIndex { get; set; }
57+
public bool? NoIndex { get; init; }
5858

5959
/*==========================================================================================================================
6060
| SHORT TITLE
6161
\-------------------------------------------------------------------------------------------------------------------------*/
6262
/// <summary>
6363
/// Provides a short title to be used in the navigation, for cases where the normal title is too long.
6464
/// </summary>
65-
public string? ShortTitle { get; set; }
65+
public string? ShortTitle { get; init; }
6666

6767
/*==========================================================================================================================
6868
| BODY
6969
\-------------------------------------------------------------------------------------------------------------------------*/
7070
/// <summary>
7171
/// Provides the primary content for the page, which is typically in HTML format.
7272
/// </summary>
73-
public string? Body { get; set; }
73+
public string? Body { get; init; }
7474

7575
} //Class
7676
} //Namespace

OnTopic.ViewModels/SectionTopicViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ namespace OnTopic.ViewModels {
1919
/// default implementations that can be used directly, used as base classes, or overwritten at the presentation level. They
2020
/// are supplied for convenience to model factory default settings for out-of-the-box content types.
2121
/// </remarks>
22-
public class SectionTopicViewModel : TopicViewModel {
22+
public record SectionTopicViewModel : TopicViewModel {
2323

2424
/*==========================================================================================================================
2525
| HEADER IMAGE
2626
\-------------------------------------------------------------------------------------------------------------------------*/
2727
/// <summary>
2828
/// Provides a header image which may be displayed at the top of a section.
2929
/// </summary>
30-
public Uri? HeaderImageUrl { get; set; }
30+
public Uri? HeaderImageUrl { get; init; }
3131

3232
} //Class
3333
} //Namespace

OnTopic.ViewModels/SlideTopicViewModel.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 SlideTopicViewModel: ContentItemTopicViewModel {
20+
public record SlideTopicViewModel: ContentItemTopicViewModel {
2121

2222

2323
} //Class

0 commit comments

Comments
 (0)