Skip to content

Commit d5530ed

Browse files
committed
Merge branch 'improvement/MemberAccess-integration' into develop
When we first introduced `MemberAccessor` (a82f7e6), we provided the bare-minimum integration in order to incorporate it with `TopicMappingService`, which was the primary target. In doing so, other internal classes, such as `BindingModelValidator`, continued to operate off of `MemberInfo` instances (namely `PropertyInfo`). In addition to being inconsistent, this also means those classes continued to operate off of direct calls to reflection. Instead, the preference is to go through `MemberAccessor` not only for consistency, but also performance, as the `MemberAccessor` instances are cached via the `TypeAccessorCache`. By passing a `MemberAccessor` around directly, we get access to more metadata, a more consistent interface, and potential performance improvements by ensuring use of our optimizations of reflection calls and avoidance of additional `TypeAccessorCache` lookups (e.g., when we already have a `MemberAccessor` in a calling class). As part of this, I updated `ReverseTopicMappingService` (3d4c9c4), `BindingModelValidator` (825bb8b), `PropertyConfiguration`'s `Validate()` method (39d1703), and `TopicMappingService`'s `SetCollectionValueAsync()` (385bfec) method to use `MemberAccessor` instead of e.g. `MemberInfo`. I also updated `ItemConfiguration` to operate off of `List<Attribute>`, thus preventing the need to make multiple calls to `GetCustomAttributes()` (9705824). Though, in a future release, we should consider updating `ItemConfiguration` to operate off of `MemberAccessor`; this isn't done today because `MemberAccessor` doesn't support parameters.
2 parents a82a06b + 7604d15 commit d5530ed

7 files changed

Lines changed: 132 additions & 113 deletions

File tree

OnTopic.ViewModels/TopicViewModelLookupService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public TopicViewModelLookupService(IEnumerable<Type>? types = null) : base(types
4848
\-----------------------------------------------------------------------------------------------------------------------*/
4949
TryAdd(typeof(ItemTopicViewModel));
5050
TryAdd(typeof(ContentItemTopicViewModel));
51+
TryAdd(typeof(CacheProfileTopicViewModel));
5152
TryAdd(typeof(LookupListItemTopicViewModel));
5253
TryAdd(typeof(SlideTopicViewModel));
5354

OnTopic/Internal/Reflection/MemberAccessor.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal class MemberAccessor {
2626
\-------------------------------------------------------------------------------------------------------------------------*/
2727
private Action<object, object?>? _setter;
2828
private Func<object, object?>? _getter;
29+
private List<Attribute> _customAttributes = default!;
2930

3031
/*==========================================================================================================================
3132
| CONSTRUCTOR
@@ -122,6 +123,19 @@ internal MemberAccessor(MemberInfo memberInfo) {
122123
/// </remarks>
123124
internal bool CanWrite { get; private set; }
124125

126+
/*==========================================================================================================================
127+
| CUSTOM ATTRIBUTES
128+
\-------------------------------------------------------------------------------------------------------------------------*/
129+
/// <summary>
130+
/// Provides a cached list of custom attributes associated with member.
131+
/// </summary>
132+
internal List<Attribute> CustomAttributes {
133+
get {
134+
_customAttributes ??= MemberInfo.GetCustomAttributes(true).OfType<Attribute>().ToList();
135+
return _customAttributes;
136+
}
137+
}
138+
125139
/*==========================================================================================================================
126140
| METHOD: IS SETTABLE?
127141
\-------------------------------------------------------------------------------------------------------------------------*/

OnTopic/Mapping/Internal/ItemConfiguration.cs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace OnTopic.Mapping.Internal {
1515
| CLASS: ITEM CONFIGURATION
1616
\---------------------------------------------------------------------------------------------------------------------------*/
1717
/// <summary>
18-
/// Evaluates the <see cref="ICustomAttributeProvider"/> for a given instance for a <see cref="ParameterInfo"/> or <see cref
18+
/// Evaluates the <see cref="IEnumerable{Attribute}"/> of a given instance for a <see cref="ParameterInfo"/> or <see cref
1919
/// ="PropertyInfo"/>, and exposes known <see cref="Attribute"/>s through a set of property values.
2020
/// </summary>
2121
/// <remarks>
@@ -41,20 +41,21 @@ internal class ItemConfiguration {
4141
| CONSTRUCTOR
4242
\-------------------------------------------------------------------------------------------------------------------------*/
4343
/// <summary>
44-
/// Given an <see cref="ICustomAttributeProvider"/> instance, exposes a set of properties associated with known <see cref=
45-
/// "Attribute"/> instances.
44+
/// Given an <see cref="IEnumerable{Attribute}"/> instance, exposes a set of properties associated with known
45+
/// <see cref="Attribute"/> instances.
4646
/// </summary>
47-
/// <param name="source">
48-
/// The <see cref="ICustomAttributeProvider"/> instance to check for <see cref="Attribute"/> values.
47+
/// <param name="customAttributes">
48+
/// The <see cref="IEnumerable{Attribute}"/> instance to check for <see cref="Attribute"/> values.
4949
/// </param>
5050
/// <param name="name">The name of the <see cref="ParameterInfo"/> or <see cref="PropertyInfo"/>.</param>
5151
/// <param name="attributePrefix">The prefix to apply to the attributes.</param>
52-
internal ItemConfiguration(ICustomAttributeProvider source, string name, string? attributePrefix = "") {
52+
internal ItemConfiguration(IEnumerable<Attribute> customAttributes, string name, string? attributePrefix = "") {
5353

5454
/*------------------------------------------------------------------------------------------------------------------------
5555
| Set backing property
5656
\-----------------------------------------------------------------------------------------------------------------------*/
57-
Source = source;
57+
CustomAttributes = customAttributes;
58+
var source = customAttributes;
5859

5960
/*------------------------------------------------------------------------------------------------------------------------
6061
| Set default values
@@ -74,23 +75,22 @@ internal ItemConfiguration(ICustomAttributeProvider source, string name, string?
7475
/*------------------------------------------------------------------------------------------------------------------------
7576
| Attributes: Retrieve basic attributes
7677
\-----------------------------------------------------------------------------------------------------------------------*/
77-
GetAttributeValue<MapAsAttribute>(source, a => MapAs = a.Type);
78-
GetAttributeValue<DefaultValueAttribute>(source, a => DefaultValue = a.Value);
79-
GetAttributeValue<InheritAttribute>(source, a => InheritValue = true);
80-
GetAttributeValue<AttributeKeyAttribute>(source, a => AttributeKey = attributePrefix + a.Key);
81-
GetAttributeValue<MapToParentAttribute>(source, a => MapToParent = true);
82-
GetAttributeValue<MapToParentAttribute>(source, a => AttributePrefix += (a.AttributePrefix?? name));
83-
GetAttributeValue<IncludeAttribute>(source, a => IncludeAssociations = a.Associations);
84-
GetAttributeValue<FlattenAttribute>(source, a => FlattenChildren = true);
85-
GetAttributeValue<MetadataAttribute>(source, a => MetadataKey = a.Key);
86-
GetAttributeValue<DisableMappingAttribute>(source, a => DisableMapping = true);
87-
GetAttributeValue<FilterByContentTypeAttribute>(source, a => ContentTypeFilter = a.ContentType);
78+
GetAttributeValue<MapAsAttribute>( a => MapAs = a.Type);
79+
GetAttributeValue<DefaultValueAttribute>( a => DefaultValue = a.Value);
80+
GetAttributeValue<InheritAttribute>( a => InheritValue = true);
81+
GetAttributeValue<AttributeKeyAttribute>( a => AttributeKey = attributePrefix + a.Key);
82+
GetAttributeValue<MapToParentAttribute>( a => MapToParent = true);
83+
GetAttributeValue<MapToParentAttribute>( a => AttributePrefix += (a.AttributePrefix?? name));
84+
GetAttributeValue<IncludeAttribute>( a => IncludeAssociations = a.Associations);
85+
GetAttributeValue<FlattenAttribute>( a => FlattenChildren = true);
86+
GetAttributeValue<MetadataAttribute>( a => MetadataKey = a.Key);
87+
GetAttributeValue<DisableMappingAttribute>( a => DisableMapping = true);
88+
GetAttributeValue<FilterByContentTypeAttribute>( a => ContentTypeFilter = a.ContentType);
8889

8990
/*------------------------------------------------------------------------------------------------------------------------
9091
| Attributes: Determine collection key and type
9192
\-----------------------------------------------------------------------------------------------------------------------*/
9293
GetAttributeValue<CollectionAttribute>(
93-
source,
9494
a => {
9595
CollectionKey = a.Key ?? CollectionKey;
9696
CollectionType = a.Type;
@@ -104,7 +104,7 @@ internal ItemConfiguration(ICustomAttributeProvider source, string name, string?
104104
/*------------------------------------------------------------------------------------------------------------------------
105105
| Attributes: Set attribute filters
106106
\-----------------------------------------------------------------------------------------------------------------------*/
107-
var filterByAttributes = (FilterByAttributeAttribute[])source.GetCustomAttributes(typeof(FilterByAttributeAttribute), true);
107+
var filterByAttributes = source.OfType<FilterByAttributeAttribute>();
108108
if (filterByAttributes is not null && filterByAttributes.Any()) {
109109
foreach (var filter in filterByAttributes) {
110110
AttributeFilters.Add(filter.Key, filter.Value);
@@ -114,12 +114,12 @@ internal ItemConfiguration(ICustomAttributeProvider source, string name, string?
114114
}
115115

116116
/*==========================================================================================================================
117-
| PROPERTY: SOURCE
117+
| PROPERTY: CUSTOM ATTRIBUTES
118118
\-------------------------------------------------------------------------------------------------------------------------*/
119119
/// <summary>
120-
/// The <see cref="ICustomAttributeProvider"/> that the current <see cref="ItemConfiguration"/> is associated with.
120+
/// The <see cref="IEnumerable{Attribute}"/> that the current <see cref="ItemConfiguration"/> is associated with.
121121
/// </summary>
122-
internal ICustomAttributeProvider Source { get; }
122+
protected IEnumerable<Attribute> CustomAttributes { get; }
123123

124124
/*==========================================================================================================================
125125
| PROPERTY: ATTRIBUTE KEY
@@ -438,18 +438,28 @@ internal bool SatisfiesAttributeFilters(Topic source) =>
438438
source?.Attributes?.GetValue(f.Key, "")?.Equals(f.Value, StringComparison.OrdinalIgnoreCase)?? false
439439
);
440440

441+
/*==========================================================================================================================
442+
| PRIVATE: GET ATTRIBUTE
443+
\-------------------------------------------------------------------------------------------------------------------------*/
444+
/// <summary>
445+
/// Helper function identifies whether a <typeparamref name="T"/> exists in the <see cref="CustomAttributes"/> and, if so,
446+
/// returns the value.
447+
/// </summary>
448+
/// <typeparam name="T">An <see cref="Attribute"/> type to evaluate.</typeparam>
449+
private T? GetAttribute<T>() where T : Attribute =>
450+
CustomAttributes.OfType<T>().FirstOrDefault();
451+
441452
/*==========================================================================================================================
442453
| PRIVATE: GET ATTRIBUTE VALUE
443454
\-------------------------------------------------------------------------------------------------------------------------*/
444455
/// <summary>
445-
/// Helper function evaluates an attribute and then, if it exists, executes an <see cref="Action{T1}"/> to process the
456+
/// Helper function evaluates an attribute and then, if it exists, executes an <see cref="Action{T}"/> to process the
446457
/// results.
447458
/// </summary>
448459
/// <typeparam name="T">An <see cref="Attribute"/> type to evaluate.</typeparam>
449-
/// <param name="source">The <see cref="ICustomAttributeProvider"/> instance to pull the attribute from.</param>
450460
/// <param name="action">The <see cref="Action{T}"/> to execute on the attribute.</param>
451-
private static void GetAttributeValue<T>(ICustomAttributeProvider source, Action<T> action) where T : Attribute {
452-
var attribute = (T)source.GetCustomAttributes(typeof(T), true).FirstOrDefault();
461+
private void GetAttributeValue<T>(Action<T> action) where T : Attribute {
462+
var attribute = GetAttribute<T>();
453463
if (attribute is not null) {
454464
action(attribute);
455465
}

OnTopic/Mapping/Internal/PropertyConfiguration.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
\=============================================================================================================================*/
66
using System.ComponentModel.DataAnnotations;
77
using System.Reflection;
8+
using OnTopic.Internal.Reflection;
89
using OnTopic.Mapping.Annotations;
910

1011
namespace OnTopic.Mapping.Internal {
@@ -35,24 +36,26 @@ internal class PropertyConfiguration: ItemConfiguration {
3536
| CONSTRUCTOR
3637
\-------------------------------------------------------------------------------------------------------------------------*/
3738
/// <summary>
38-
/// Given a <see cref="PropertyInfo"/> instance, exposes a set of properties associated with known <see cref="Attribute"/>
39-
/// instances.
39+
/// Given a <see cref="MemberAccessor"/> instance, exposes a set of properties associated with known <see cref="Attribute"
40+
/// /> instances.
4041
/// </summary>
41-
/// <param name="property">The <see cref="PropertyInfo"/> instance to check for <see cref="Attribute"/> values.</param>
42+
/// <param name="memberAccessor">
43+
/// The <see cref="MemberAccessor"/> instance to check for <see cref="Attribute"/> values.
44+
/// </param>
4245
/// <param name="attributePrefix">The prefix to apply to the attributes.</param>
43-
internal PropertyConfiguration(PropertyInfo property, string? attributePrefix = ""):
44-
base(property, property.Name, attributePrefix)
46+
internal PropertyConfiguration(MemberAccessor memberAccessor, string? attributePrefix = ""):
47+
base(memberAccessor.CustomAttributes, memberAccessor.Name, attributePrefix)
4548
{
46-
Property = property;
49+
MemberAccessor = memberAccessor;
4750
}
4851

4952
/*==========================================================================================================================
50-
| PROPERTY: PROPERTY
53+
| PROPERTY: MEMBER ACCESSOR
5154
\-------------------------------------------------------------------------------------------------------------------------*/
5255
/// <summary>
53-
/// The <see cref="PropertyInfo"/> that the current <see cref="PropertyConfiguration"/> is associated with.
56+
/// The <see cref="MemberAccessor"/> that the current <see cref="PropertyConfiguration"/> is associated with.
5457
/// </summary>
55-
internal PropertyInfo Property { get; }
58+
internal MemberAccessor MemberAccessor { get; }
5659

5760
/*==========================================================================================================================
5861
| METHOD: VALIDATE
@@ -63,8 +66,8 @@ internal PropertyConfiguration(PropertyInfo property, string? attributePrefix =
6366
/// </summary>
6467
/// <param name="target">The target DTO to validate the current property on.</param>
6568
internal void Validate(object target) {
66-
foreach (ValidationAttribute validator in Property.GetCustomAttributes(typeof(ValidationAttribute))) {
67-
validator.Validate(Property.GetValue(target), Property.Name);
69+
foreach (ValidationAttribute validator in CustomAttributes.OfType<ValidationAttribute>()) {
70+
validator.Validate(MemberAccessor.GetValue(target), MemberAccessor.Name);
6871
}
6972
}
7073

0 commit comments

Comments
 (0)