-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSetValueAttribute.cs
More file actions
187 lines (173 loc) · 7.04 KB
/
SetValueAttribute.cs
File metadata and controls
187 lines (173 loc) · 7.04 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Configuration;
namespace NetBricks;
/// <summary>
/// Attribute that specifies one or more configuration keys to try when setting a property value.
/// This attribute enables automatic population of properties from configuration sources
/// like environment variables, command line arguments, or configuration files.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SetValueAttribute : ValidationAttribute
{
private static readonly Dictionary<string, string> ErrorMessages = [];
/// <summary>
/// Gets the array of configuration keys to check for a value.
/// Keys are tried in order until a non-empty value is found.
/// </summary>
public string[] Keys { get; }
/// <summary>
/// Gets or sets the default value to use if none of the keys have a value.
/// </summary>
public object? Default { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="SetValueAttribute"/> class with the specified keys.
/// </summary>
/// <param name="keys">The configuration keys to check for a value.</param>
/// <exception cref="ArgumentException">Thrown when no keys are provided.</exception>
public SetValueAttribute(params string[] keys)
{
if (keys == null || keys.Length == 0)
{
throw new ArgumentException("At least one key must be provided", nameof(keys));
}
Keys = keys;
}
internal static void SetError(Type type, string? propertyName, string errorMessage)
{
string key = $"{type.FullName}.{propertyName}";
ErrorMessages[key] = errorMessage;
}
internal static string? GetError(Type type, string? propertyName)
{
string key = $"{type.FullName}.{propertyName}";
return ErrorMessages.TryGetValue(key, out var message) ? message : null;
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
string? error = GetError(validationContext.ObjectType, validationContext.MemberName);
if (!string.IsNullOrEmpty(error))
{
return new ValidationResult(error);
}
return ValidationResult.Success;
}
}
internal static class SetValue
{
internal static void Apply<T>(IConfiguration configuration, T instance) where T : class
{
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
if (instance == null)
throw new ArgumentNullException(nameof(instance));
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
// Check if the property has the SetValue attribute
var attribute = Attribute.GetCustomAttribute(property, typeof(SetValueAttribute)) as SetValueAttribute;
if (attribute is null)
continue;
// Try each key in order until we find a value
string? value = null;
foreach (var key in attribute.Keys)
{
value = configuration[key];
if (!string.IsNullOrEmpty(value))
break;
}
if (!string.IsNullOrEmpty(value))
{
// Get the target type
var targetType = property.PropertyType;
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
var effectiveType = nullableUnderlyingType ?? targetType;
// Supported data types
if (effectiveType == typeof(string))
{
var convertedValue = value.AsString(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(bool))
{
var convertedValue = value.AsBool(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(int))
{
var convertedValue = value.AsInt(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(long))
{
var convertedValue = value.AsLong(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(float))
{
var convertedValue = value.AsFloat(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(double))
{
var convertedValue = value.AsDouble(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(Guid))
{
if (Guid.TryParse(value, out var convertedValue))
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType.IsEnum)
{
if (Enum.TryParse(effectiveType, value, ignoreCase: true, out var convertedValue))
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(string[]) || effectiveType == typeof(IEnumerable<string>))
{
var convertedValue = value.AsArray(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue);
}
}
else if (effectiveType == typeof(IList<string>) || effectiveType == typeof(List<string>))
{
var convertedValue = value.AsArray(() => null);
if (convertedValue is not null)
{
property.SetValue(instance, convertedValue.ToList());
}
}
else
{
SetValueAttribute.SetError(typeof(T), property.Name, $"Unsupported type {effectiveType.Name} for property {property.Name}.");
}
}
}
}
}