-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathExceptionReflectionHelpers.cs
More file actions
134 lines (123 loc) · 5.33 KB
/
Copy pathExceptionReflectionHelpers.cs
File metadata and controls
134 lines (123 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace Dfe.SignIn.Base.Framework;
/// <summary>
/// Internal helper reflection functionality to discover serializable properties
/// in interaction exceptions.
/// </summary>
internal static class ExceptionReflectionHelpers
{
private static IReadOnlyDictionary<string, Type>? exceptionTypesByFullName;
private static readonly Dictionary<Type, IEnumerable<PropertyInfo>> propertiesByType = [];
private static readonly object @lock = new();
/// <summary>
/// Gets a cached read-only lookup map of exception types by full name.
/// </summary>
/// <remarks>
/// <para>This function is slower the first time that it is invoked.</para>
/// </remarks>
private static IReadOnlyDictionary<string, Type> ExceptionTypesByFullName {
get {
lock (@lock) {
if (exceptionTypesByFullName is not null) {
return exceptionTypesByFullName;
}
exceptionTypesByFullName = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => {
try {
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null)!;
}
})
.Where(type => typeof(Exception).IsAssignableFrom(type))
.GroupBy(type => type.FullName)
.ToDictionary(
keySelector: type => type.First().FullName!,
elementSelector: type => type.First()
);
return exceptionTypesByFullName;
}
}
}
/// <summary>
/// Gets an exception type from a given full name.
/// </summary>
/// <param name="fullName">The full name of the exception type.</param>
/// <returns>
/// <para>The exception type when found; otherwise, a value of null.</para>
/// </returns>
/// <exception cref="ArgumentException">
/// <para>If <paramref name="fullName"/> is null or empty.</para>
/// </exception>
public static Type? GetExceptionTypeByFullName(string fullName)
{
ExceptionHelpers.ThrowIfArgumentNullOrWhiteSpace(fullName, nameof(fullName));
ExceptionTypesByFullName.TryGetValue(fullName, out var result);
return result;
}
/// <summary>
/// Determines whether an exception property can be serialized.
/// </summary>
/// <remarks>
/// <para>A property can only be serialized where:</para>
/// <list type="bullet">
/// <item>The property is defined in a custom <see cref="InteractionException"/> type.</item>
/// <item>The property is type is not in the <see cref="System.Reflection"/> namespace.</item>
/// <item>The property is type is not <see cref="Type"/>.</item>
/// </list>
/// </remarks>
/// <param name="property">The property.</param>
/// <returns>
/// <para>A value of true if the property can be serialized; otherwise, false.</para>
/// </returns>
/// <exception cref="ArgumentException">
/// <para>If <paramref name="property"/> is null.</para>
/// </exception>
[SuppressMessage("csharpsquid", "S3011",
Justification = "Access to private setters on models is required for serialization."
)]
public static bool IsSerializableProperty(PropertyInfo property)
{
ExceptionHelpers.ThrowIfArgumentNull(property, nameof(property));
// Get property from declaring type so that private accessors are found.
property = property.DeclaringType?.GetProperty(
property.Name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
)!;
return property is not null
&& property.PropertyType != typeof(Type)
&& property.PropertyType.Namespace != typeof(MemberInfo).Namespace
&& property.GetCustomAttribute<PersistAttribute>() is not null
&& property.GetGetMethod(nonPublic: true) is not null
&& property.GetSetMethod(nonPublic: true) is not null;
}
/// <summary>
/// Get serializable properties of the given exception type.
/// </summary>
/// <remarks>
/// <para>Type information is cached to avoid poor performance due to excessive
/// use of reflection.</para>
/// <para>This function is thread-safe.</para>
/// </remarks>
/// <param name="exceptionType">The type of exception.</param>
/// <returns>
/// <para>An enumerable collection of zero or more properties.</para>
/// </returns>
/// <exception cref="ArgumentException">
/// <para>If <paramref name="exceptionType"/> is null.</para>
/// </exception>
public static IEnumerable<PropertyInfo> GetSerializableExceptionProperties(Type exceptionType)
{
ExceptionHelpers.ThrowIfArgumentNull(exceptionType, nameof(exceptionType));
IEnumerable<PropertyInfo> result;
lock (@lock) {
if (!propertiesByType.TryGetValue(exceptionType, out result!)) {
result = [.. exceptionType.GetProperties().Where(IsSerializableProperty)];
propertiesByType[exceptionType] = result;
}
}
return result;
}
}