Skip to content

Commit 3cfc474

Browse files
committed
Adds support for DisplayAttribute.
1 parent 7688736 commit 3cfc474

56 files changed

Lines changed: 1638 additions & 76 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README-NuGet.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
A C# source generator that automatically extracts values from data annotation
44
attributes and exposes them as strongly-typed constants. Access your
5-
`StringLength`, `Range`, and `Required` attribute values at constants in your
6-
classes.
5+
`StringLength`, `Range`, `Required` and `Display` attribute values as constants
6+
in your classes.
77

88
## Why Use This?
99

@@ -42,22 +42,24 @@ double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
4242

4343
## Usage Patterns
4444

45-
There are two ways to use DataAnnotationValuesExtractor depending on your needs:
45+
There are two ways to configure DataAnnotationValuesExtractor depending on your
46+
needs:
4647

4748
### 1. Direct Approach
4849

4950
Apply `[DataAnnotationValues]` directly to each class you want to generate
5051
constants for:
5152

52-
5353
```csharp
54-
[DataAnnotationValues(StringLength = true, Range = true, Required = true)]
54+
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
5555
public partial class Product
5656
{
57+
[Display(Name = "Product name")]
5758
[Required]
5859
[StringLength(100)]
5960
public string? Name { get; set; }
6061

62+
[Display(Name = "Product price")]
6163
[Required]
6264
[Range(0.01, 999999.99)]
6365
public decimal Price { get; set; }
@@ -75,7 +77,7 @@ each class you want to generate constants for. You can use the
7577
```csharp
7678
using Pekspro.DataAnnotationValuesExtractor;
7779

78-
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true)]
80+
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true)]
7981
[DataAnnotationValuesToGenerate(typeof(Customer))]
8082
[DataAnnotationValuesToGenerate(typeof(Order))]
8183
[DataAnnotationValuesToGenerate(typeof(Product))]
@@ -95,17 +97,19 @@ No matter which approach your are using, you can access generated constants like
9597
this:
9698

9799
```csharp
98-
// Name length constraints
100+
// Name
99101
int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
100102
int minNameLength = Product.Annotations.Name.MinimumLength; // 0
101103
bool nameRequired = Product.Annotations.Name.IsRequired; // true
104+
string? nameDisplayName = Product.Annotations.Name.Display.Name; // Product name
102105
103-
// Price constraints
106+
// Price
104107
double minPrice = Product.Annotations.Price.Minimum; // 0.01
105108
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
106109
bool priceRequired = Product.Annotations.Price.IsRequired;
110+
string? priceDisplayName = Product.Annotations.Price.Display.Name; // Price name
107111
108-
// Sky constraints
112+
// Sku
109113
bool skuRequired = Product.Annotations.Sku.IsRequired; // false
110114
```
111115

README.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ status](https://github.com/pekspro/DataAnnotationValuesExtractor/actions/workflo
66

77
A C# source generator that automatically extracts values from data annotation
88
attributes and exposes them as strongly-typed constants. Access your
9-
`StringLength`, `Range`, and `Required` attribute values at constants in your
10-
classes.
9+
`StringLength`, `Range`, `Required` and `Display` attribute values as constants
10+
in your classes.
1111

1212
## Why Use This?
1313

@@ -46,7 +46,7 @@ double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
4646

4747
## Usage Patterns
4848

49-
There are two ways configure use DataAnnotationValuesExtractor depending on your
49+
There are two ways to configure DataAnnotationValuesExtractor depending on your
5050
needs:
5151

5252
### 1. Direct Approach
@@ -55,13 +55,15 @@ Apply `[DataAnnotationValues]` directly to each class you want to generate
5555
constants for:
5656

5757
```csharp
58-
[DataAnnotationValues(StringLength = true, Range = true, Required = true)]
58+
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
5959
public partial class Product
6060
{
61+
[Display(Name = "Product name")]
6162
[Required]
6263
[StringLength(100)]
6364
public string? Name { get; set; }
6465

66+
[Display(Name = "Product price")]
6567
[Required]
6668
[Range(0.01, 999999.99)]
6769
public decimal Price { get; set; }
@@ -79,7 +81,7 @@ each class you want to generate constants for. You can use the
7981
```csharp
8082
using Pekspro.DataAnnotationValuesExtractor;
8183

82-
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true)]
84+
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true)]
8385
[DataAnnotationValuesToGenerate(typeof(Customer))]
8486
[DataAnnotationValuesToGenerate(typeof(Order))]
8587
[DataAnnotationValuesToGenerate(typeof(Product))]
@@ -108,17 +110,19 @@ No matter which approach your are using, you can access generated constants like
108110
this:
109111

110112
```csharp
111-
// Name length constraints
113+
// Name
112114
int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
113115
int minNameLength = Product.Annotations.Name.MinimumLength; // 0
114116
bool nameRequired = Product.Annotations.Name.IsRequired; // true
117+
string? nameDisplayName = Product.Annotations.Name.Display.Name; // Product name
115118
116-
// Price constraints
119+
// Price
117120
double minPrice = Product.Annotations.Price.Minimum; // 0.01
118121
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
119122
bool priceRequired = Product.Annotations.Price.IsRequired;
123+
string? priceDisplayName = Product.Annotations.Price.Display.Name; // Price name
120124
121-
// Sky constraints
125+
// Sku
122126
bool skuRequired = Product.Annotations.Sku.IsRequired; // false
123127
```
124128

@@ -157,6 +161,7 @@ the following properties to control which constants are generated:
157161
| `StringLength` | `true` | `MaximumLength`, `MinimumLength` | Extract values from `[StringLength]` attribute. |
158162
| `Range` | `true` | `Minimum`, `Maximum`, `MinimumIsExclusive`, `MaximumIsExclusive` | Extract values from `[Range]` attribute. |
159163
| `Required` | `false` | `IsRequired` | Detect presence of `[Required]` attribute. |
164+
| `Display` | `false` | `Name`, `Description`, `ShortName` | Extract values from `[Display]` attribute. |
160165

161166
Do you miss some annotations? Create an issue and let me know.
162167

@@ -188,9 +193,10 @@ directory.
188193
Given this input:
189194

190195
```csharp
191-
[DataAnnotationValues(StringLength = true, Range = true, Required = true)]
196+
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
192197
public partial class Player
193198
{
199+
[Display(Name = "Player name", ShortName ="Name", Description = "Name of player")]
194200
[Required]
195201
[StringLength(50)]
196202
public string? Name { get; set; }
@@ -232,6 +238,27 @@ public partial class Player
232238
/// Indicates whether Name is required.
233239
/// </summary>
234240
public const bool IsRequired = true;
241+
242+
/// <summary>
243+
/// Display attribute values for Name.
244+
/// </summary>
245+
public static class Display
246+
{
247+
/// <summary>
248+
/// Display name for Name.
249+
/// </summary>
250+
public const string? Name = "Player name";
251+
252+
/// <summary>
253+
/// Short display name for Name.
254+
/// </summary>
255+
public const string? ShortName = "Name";
256+
257+
/// <summary>
258+
/// Description for Name.
259+
/// </summary>
260+
public const string? Description = "Name of player";
261+
}
235262
}
236263

237264
/// <summary>

Source/Library/Pekspro.DataAnnotationValuesExtractor.Attributes/DataAnnotationValuesAttribute.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public class DataAnnotationValuesAttribute : System.Attribute
2424
/// Is set to false by default.
2525
/// </summary>
2626
public bool Required { get; set; }
27+
28+
/// <summary>
29+
/// Whether Display attribute values should be included.
30+
/// Is set to false by default.
31+
/// </summary>
32+
public bool Display { get; set; }
2733
}
2834
}
2935

Source/Library/Pekspro.DataAnnotationValuesExtractor.Attributes/DataAnnotationValuesOptionsAttribute.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public class DataAnnotationValuesOptionsAttribute : System.Attribute
2626
/// Is set to false by default.
2727
/// </summary>
2828
public bool Required { get; set; }
29+
30+
/// <summary>
31+
/// Whether Display attribute values should be included.
32+
/// Is set to false by default.
33+
/// </summary>
34+
public bool Display { get; set; }
2935
}
3036
}
3137

Source/Library/Pekspro.DataAnnotationValuesExtractor/DataAnnotationValuesDetailedOptions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Pekspro.DataAnnotationValuesExtractor;
1414
public readonly bool AddStringLength;
1515
public readonly bool AddRange;
1616
public readonly bool AddRequired;
17+
public readonly bool AddDisplay;
1718

1819
public DataAnnotationValuesDetailedOptions(
1920
string name,
@@ -22,7 +23,8 @@ public DataAnnotationValuesDetailedOptions(
2223
ImmutableArray<TypeInformation> types,
2324
bool addStringLength,
2425
bool addRange,
25-
bool addRequired
26+
bool addRequired,
27+
bool addDisplay = false
2628
)
2729
{
2830
Name = name;
@@ -42,6 +44,7 @@ bool addRequired
4244
AddStringLength = addStringLength;
4345
AddRange = addRange;
4446
AddRequired = addRequired;
47+
AddDisplay = addDisplay;
4548
}
4649

4750
public bool Equals(DataAnnotationValuesDetailedOptions other)
@@ -53,6 +56,7 @@ public bool Equals(DataAnnotationValuesDetailedOptions other)
5356
&& AddStringLength == other.AddStringLength
5457
&& AddRange == other.AddRange
5558
&& AddRequired == other.AddRequired
59+
&& AddDisplay == other.AddDisplay
5660
&& TypesEqual(Types, other.Types);
5761
}
5862

@@ -101,6 +105,7 @@ public override int GetHashCode()
101105
hash = hash * 23 + AddStringLength.GetHashCode();
102106
hash = hash * 23 + AddRange.GetHashCode();
103107
hash = hash * 23 + AddRequired.GetHashCode();
108+
hash = hash * 23 + AddDisplay.GetHashCode();
104109

105110
if (!Types.IsDefault)
106111
{

Source/Library/Pekspro.DataAnnotationValuesExtractor/DataAnnotationValuesExtractor.cs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
7777
bool addStringLength = true;
7878
bool addRange = true;
7979
bool addRequired = false;
80+
bool addDisplay = false;
8081
var typesBuilder = ImmutableArray.CreateBuilder<ITypeSymbol>();
8182

8283
foreach (AttributeData attributeData in symbol.GetAttributes())
@@ -103,6 +104,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
103104
{
104105
addRequired = required;
105106
}
107+
108+
if (namedArgument.Key == nameof(DataAnnotationValuesOptionsAttribute.Display)
109+
&& namedArgument.Value.Value is bool display)
110+
{
111+
addDisplay = display;
112+
}
106113
}
107114
}
108115
else if (attributeData.AttributeClass?.Name == "DataAnnotationValuesToGenerateAttribute" &&
@@ -127,10 +134,11 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
127134
name,
128135
nameSpace,
129136
filePath,
130-
ExtractTypeInformation(typesBuilder.ToImmutable(), addStringLength, addRange, addRequired),
137+
ExtractTypeInformation(typesBuilder.ToImmutable(), addStringLength, addRange, addRequired, addDisplay),
131138
addStringLength,
132139
addRange,
133-
addRequired
140+
addRequired,
141+
addDisplay
134142
);
135143
}
136144

@@ -158,6 +166,7 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
158166
bool addStringLength = true;
159167
bool addRange = true;
160168
bool addRequired = false;
169+
bool addDisplay = false;
161170

162171
// Get configuration from the DataAnnotationValuesAttribute
163172
foreach (AttributeData attributeData in symbol.GetAttributes())
@@ -184,6 +193,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
184193
{
185194
addRequired = required;
186195
}
196+
197+
if (namedArgument.Key == nameof(DataAnnotationValuesAttribute.Display)
198+
&& namedArgument.Value.Value is bool display)
199+
{
200+
addDisplay = display;
201+
}
187202
}
188203
}
189204
}
@@ -196,18 +211,20 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
196211
name,
197212
nameSpace,
198213
filePath,
199-
ExtractTypeInformation(types, addStringLength, addRange, addRequired),
214+
ExtractTypeInformation(types, addStringLength, addRange, addRequired, addDisplay),
200215
addStringLength,
201216
addRange,
202-
addRequired
217+
addRequired,
218+
addDisplay
203219
);
204220
}
205221

206222
static ImmutableArray<TypeInformation> ExtractTypeInformation(
207223
ImmutableArray<ITypeSymbol> typeSymbols,
208224
bool includeStringLength,
209225
bool includeRange,
210-
bool includeRequired)
226+
bool includeRequired,
227+
bool includeDisplay)
211228
{
212229
var builder = ImmutableArray.CreateBuilder<TypeInformation>();
213230

@@ -221,6 +238,7 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
221238
StringLengthInfo? stringLength = null;
222239
RangeInfo? range = null;
223240
bool isRequired = false;
241+
DisplayInfo? display = null;
224242

225243
// Check for StringLength attribute
226244
if (includeStringLength)
@@ -289,14 +307,53 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
289307
isRequired = requiredAttr != null;
290308
}
291309

310+
// Check for Display attribute
311+
if (includeDisplay)
312+
{
313+
var displayAttr = property.GetAttributes()
314+
.FirstOrDefault(a => a.AttributeClass?.Name == "DisplayAttribute" &&
315+
a.AttributeClass?.ContainingNamespace?.ToDisplayString() == "System.ComponentModel.DataAnnotations");
316+
317+
if (displayAttr != null)
318+
{
319+
string? name = null;
320+
string? shortName = null;
321+
string? description = null;
322+
323+
// Extract named arguments
324+
foreach (var namedArg in displayAttr.NamedArguments)
325+
{
326+
if (namedArg.Key == "Name" && namedArg.Value.Value is string nameValue)
327+
{
328+
name = nameValue;
329+
}
330+
else if (namedArg.Key == "ShortName" && namedArg.Value.Value is string shortNameValue)
331+
{
332+
shortName = shortNameValue;
333+
}
334+
else if (namedArg.Key == "Description" && namedArg.Value.Value is string descriptionValue)
335+
{
336+
description = descriptionValue;
337+
}
338+
}
339+
340+
// Only create DisplayInfo if at least one value is present
341+
if (name != null || shortName != null || description != null)
342+
{
343+
display = new DisplayInfo(name, shortName, description);
344+
}
345+
}
346+
}
347+
292348
// Only add property if it has any attributes we're tracking
293-
if (stringLength.HasValue || range.HasValue || includeRequired)
349+
if (stringLength.HasValue || range.HasValue || includeRequired || display.HasValue)
294350
{
295351
propertyInfos.Add(new PropertyInformation(
296352
property.Name,
297353
stringLength,
298354
range,
299-
isRequired));
355+
isRequired,
356+
display));
300357
}
301358
}
302359

0 commit comments

Comments
 (0)