Skip to content

Commit 658453a

Browse files
committed
Adds support for Description attribute.
1 parent ece610f commit 658453a

20 files changed

Lines changed: 989 additions & 40 deletions

README-NuGet.md

Lines changed: 18 additions & 5 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`, `Required` and `Display` attribute values as constants
6-
in your classes.
5+
`StringLength`, `Range`, `Required`, `Display` and `Description` attribute
6+
values as constants in your classes.
77

88
## Why Use This?
99

@@ -51,15 +51,17 @@ Apply `[DataAnnotationValues]` directly to each class you want to generate
5151
constants for:
5252

5353
```csharp
54-
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
54+
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true, Description = true)]
5555
public partial class Product
5656
{
5757
[Display(Name = "Product name")]
58+
[Description("Name of the product")]
5859
[Required]
5960
[StringLength(100)]
6061
public string? Name { get; set; }
6162

6263
[Display(Name = "Product price")]
64+
[Description("Price of the product")]
6365
[Required]
6466
[Range(0.01, 999999.99)]
6567
public decimal Price { get; set; }
@@ -77,7 +79,7 @@ each class you want to generate constants for. You can use the
7779
```csharp
7880
using Pekspro.DataAnnotationValuesExtractor;
7981

80-
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true)]
82+
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true, Description = true)]
8183
[DataAnnotationValuesToGenerate(typeof(Customer))]
8284
[DataAnnotationValuesToGenerate(typeof(Order))]
8385
[DataAnnotationValuesToGenerate(typeof(Product))]
@@ -89,7 +91,16 @@ partial class ValidationConstants
8991
Your model classes remain unchanged.
9092

9193
This approach is especially useful when working with auto-generated models, such
92-
as those created by Entity Framework scaffolding.
94+
as those created by Entity Framework scaffolding. If you do, and you have all
95+
your models in a folder, you can use this PowerShell script to generate the
96+
attributes for all models in that folder:
97+
98+
```powershell
99+
Get-ChildItem -Filter '*.cs' |
100+
Where-Object { -not ($_.BaseName -match '(?i)context') } |
101+
ForEach-Object { "[DataAnnotationValuesToGenerate(typeof($($_.BaseName)))]" } |
102+
Set-Clipboard
103+
```
93104

94105
### Use the generated constants
95106

@@ -102,12 +113,14 @@ int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
102113
int minNameLength = Product.Annotations.Name.MinimumLength; // 0
103114
bool nameRequired = Product.Annotations.Name.IsRequired; // true
104115
string? nameDisplayName = Product.Annotations.Name.Display.Name; // Product name
116+
string? nameDescription = Product.Annotations.Name.Description.Text; // Name description
105117
106118
// Price
107119
double minPrice = Product.Annotations.Price.Minimum; // 0.01
108120
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
109121
bool priceRequired = Product.Annotations.Price.IsRequired;
110122
string? priceDisplayName = Product.Annotations.Price.Display.Name; // Price name
123+
string? priceDescription = Product.Annotations.Price.Description.Text; // Price description
111124
112125
// Sku
113126
bool skuRequired = Product.Annotations.Sku.IsRequired; // false

README.md

Lines changed: 23 additions & 6 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`, `Required` and `Display` attribute values as constants
10-
in your classes.
9+
`StringLength`, `Range`, `Required`, `Display` and `Description` attribute
10+
values as constants in your classes.
1111

1212
## Why Use This?
1313

@@ -55,15 +55,17 @@ Apply `[DataAnnotationValues]` directly to each class you want to generate
5555
constants for:
5656

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

6667
[Display(Name = "Product price")]
68+
[Description("Price of the product")]
6769
[Required]
6870
[Range(0.01, 999999.99)]
6971
public decimal Price { get; set; }
@@ -81,7 +83,7 @@ each class you want to generate constants for. You can use the
8183
```csharp
8284
using Pekspro.DataAnnotationValuesExtractor;
8385

84-
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true)]
86+
[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true, Description = true)]
8587
[DataAnnotationValuesToGenerate(typeof(Customer))]
8688
[DataAnnotationValuesToGenerate(typeof(Order))]
8789
[DataAnnotationValuesToGenerate(typeof(Product))]
@@ -115,12 +117,14 @@ int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
115117
int minNameLength = Product.Annotations.Name.MinimumLength; // 0
116118
bool nameRequired = Product.Annotations.Name.IsRequired; // true
117119
string? nameDisplayName = Product.Annotations.Name.Display.Name; // Product name
120+
string? nameDescription = Product.Annotations.Name.Description.Text; // Name description
118121
119122
// Price
120123
double minPrice = Product.Annotations.Price.Minimum; // 0.01
121124
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
122125
bool priceRequired = Product.Annotations.Price.IsRequired;
123126
string? priceDisplayName = Product.Annotations.Price.Display.Name; // Price name
127+
string? priceDescription = Product.Annotations.Price.Description.Text; // Price description
124128
125129
// Sku
126130
bool skuRequired = Product.Annotations.Sku.IsRequired; // false
@@ -161,7 +165,8 @@ the following properties to control which constants are generated:
161165
| `StringLength` | `true` | `MaximumLength`, `MinimumLength` | Extract values from `[StringLength]` attribute. |
162166
| `Range` | `true` | `Minimum`, `Maximum`, `MinimumIsExclusive`, `MaximumIsExclusive` | Extract values from `[Range]` attribute. |
163167
| `Required` | `false` | `IsRequired` | Detect presence of `[Required]` attribute. |
164-
| `Display` | `false` | `Name`, `Description`, `ShortName` | Extract values from `[Display]` attribute. |
168+
| `Display` | `false` | `Name`, `Description`, `ShortName` | Extract values from `[Display]` attribute. |
169+
| `Description` | `false` | `Text` | Extract value from `[Description]` attribute. |
165170

166171
Do you miss some annotations? Create an issue and let me know.
167172

@@ -193,10 +198,11 @@ directory.
193198
Given this input:
194199

195200
```csharp
196-
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
201+
[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true, Description = true)]
197202
public partial class Player
198203
{
199204
[Display(Name = "Player name", ShortName ="Name", Description = "Name of player")]
205+
[Description("The player's name")]
200206
[Required]
201207
[StringLength(50)]
202208
public string? Name { get; set; }
@@ -259,6 +265,17 @@ public partial class Player
259265
/// </summary>
260266
public const string? Description = "Name of player";
261267
}
268+
269+
/// <summary>
270+
/// Description attribute values for Name.
271+
/// </summary>
272+
public static class Description
273+
{
274+
/// <summary>
275+
/// Description text for Name.
276+
/// </summary>
277+
public const string? Text = "The player's name";
278+
}
262279
}
263280

264281
/// <summary>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public class DataAnnotationValuesAttribute : System.Attribute
3030
/// Is set to false by default.
3131
/// </summary>
3232
public bool Display { get; set; }
33+
34+
/// <summary>
35+
/// Whether Description attribute values should be included.
36+
/// Is set to false by default.
37+
/// </summary>
38+
public bool Description { get; set; }
3339
}
3440
}
3541

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ public class DataAnnotationValuesOptionsAttribute : System.Attribute
3232
/// Is set to false by default.
3333
/// </summary>
3434
public bool Display { get; set; }
35+
36+
/// <summary>
37+
/// Whether Description attribute values should be included.
38+
/// Is set to false by default.
39+
/// </summary>
40+
public bool Description { get; set; }
3541
}
3642
}
3743

Source/Library/Pekspro.DataAnnotationValuesExtractor/DataAnnotationValuesDetailedOptions.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace Pekspro.DataAnnotationValuesExtractor;
1515
public readonly bool AddRange;
1616
public readonly bool AddRequired;
1717
public readonly bool AddDisplay;
18-
18+
public readonly bool AddDescription;
19+
1920
public DataAnnotationValuesDetailedOptions(
2021
string name,
2122
string? ns,
@@ -24,14 +25,15 @@ public DataAnnotationValuesDetailedOptions(
2425
bool addStringLength,
2526
bool addRange,
2627
bool addRequired,
27-
bool addDisplay = false
28+
bool addDisplay = false,
29+
bool addDescription = false
2830
)
2931
{
3032
Name = name;
3133
Namespace = ns;
3234
FullFileName = fullFileName;
3335
Types = types;
34-
36+
3537
if (fullFileName != string.Empty)
3638
{
3739
FilePath = Path.GetDirectoryName(fullFileName);
@@ -45,6 +47,7 @@ public DataAnnotationValuesDetailedOptions(
4547
AddRange = addRange;
4648
AddRequired = addRequired;
4749
AddDisplay = addDisplay;
50+
AddDescription = addDescription;
4851
}
4952

5053
public bool Equals(DataAnnotationValuesDetailedOptions other)
@@ -57,6 +60,7 @@ public bool Equals(DataAnnotationValuesDetailedOptions other)
5760
&& AddRange == other.AddRange
5861
&& AddRequired == other.AddRequired
5962
&& AddDisplay == other.AddDisplay
63+
&& AddDescription == other.AddDescription
6064
&& TypesEqual(Types, other.Types);
6165
}
6266

@@ -106,6 +110,7 @@ public override int GetHashCode()
106110
hash = hash * 23 + AddRange.GetHashCode();
107111
hash = hash * 23 + AddRequired.GetHashCode();
108112
hash = hash * 23 + AddDisplay.GetHashCode();
113+
hash = hash * 23 + AddDescription.GetHashCode();
109114

110115
if (!Types.IsDefault)
111116
{

Source/Library/Pekspro.DataAnnotationValuesExtractor/DataAnnotationValuesExtractor.cs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
7878
bool addRange = true;
7979
bool addRequired = false;
8080
bool addDisplay = false;
81+
bool addDescription = false;
8182
var typesBuilder = ImmutableArray.CreateBuilder<ITypeSymbol>();
8283

8384
foreach (AttributeData attributeData in symbol.GetAttributes())
@@ -110,6 +111,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
110111
{
111112
addDisplay = display;
112113
}
114+
115+
if (namedArgument.Key == nameof(DataAnnotationValuesOptionsAttribute.Description)
116+
&& namedArgument.Value.Value is bool description)
117+
{
118+
addDescription = description;
119+
}
113120
}
114121
}
115122
else if (attributeData.AttributeClass?.Name == "DataAnnotationValuesToGenerateAttribute" &&
@@ -134,11 +141,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
134141
name,
135142
nameSpace,
136143
filePath,
137-
ExtractTypeInformation(typesBuilder.ToImmutable(), addStringLength, addRange, addRequired, addDisplay),
144+
ExtractTypeInformation(typesBuilder.ToImmutable(), addStringLength, addRange, addRequired, addDisplay, addDescription),
138145
addStringLength,
139146
addRange,
140147
addRequired,
141-
addDisplay
148+
addDisplay,
149+
addDescription
142150
);
143151
}
144152

@@ -167,6 +175,7 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
167175
bool addRange = true;
168176
bool addRequired = false;
169177
bool addDisplay = false;
178+
bool addDescription = false;
170179

171180
// Get configuration from the DataAnnotationValuesAttribute
172181
foreach (AttributeData attributeData in symbol.GetAttributes())
@@ -199,6 +208,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
199208
{
200209
addDisplay = display;
201210
}
211+
212+
if (namedArgument.Key == nameof(DataAnnotationValuesAttribute.Description)
213+
&& namedArgument.Value.Value is bool description)
214+
{
215+
addDescription = description;
216+
}
202217
}
203218
}
204219
}
@@ -211,11 +226,12 @@ static void Execute(in DataAnnotationValuesDetailedOptions toGenerate, SourcePro
211226
name,
212227
nameSpace,
213228
filePath,
214-
ExtractTypeInformation(types, addStringLength, addRange, addRequired, addDisplay),
229+
ExtractTypeInformation(types, addStringLength, addRange, addRequired, addDisplay, addDescription),
215230
addStringLength,
216231
addRange,
217232
addRequired,
218-
addDisplay
233+
addDisplay,
234+
addDescription
219235
);
220236
}
221237

@@ -224,7 +240,8 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
224240
bool includeStringLength,
225241
bool includeRange,
226242
bool includeRequired,
227-
bool includeDisplay)
243+
bool includeDisplay,
244+
bool includeDescription)
228245
{
229246
var builder = ImmutableArray.CreateBuilder<TypeInformation>();
230247

@@ -239,6 +256,7 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
239256
RangeInfo? range = null;
240257
bool isRequired = false;
241258
DisplayInfo? display = null;
259+
DescriptionInfo? description = null;
242260

243261
// Check for StringLength attribute
244262
if (includeStringLength)
@@ -318,7 +336,7 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
318336
{
319337
string? name = null;
320338
string? shortName = null;
321-
string? description = null;
339+
string? displayDescription = null;
322340

323341
// Extract named arguments
324342
foreach (var namedArg in displayAttr.NamedArguments)
@@ -333,27 +351,46 @@ static ImmutableArray<TypeInformation> ExtractTypeInformation(
333351
}
334352
else if (namedArg.Key == "Description" && namedArg.Value.Value is string descriptionValue)
335353
{
336-
description = descriptionValue;
354+
displayDescription = descriptionValue;
337355
}
338356
}
339357

340358
// Only create DisplayInfo if at least one value is present
341-
if (name != null || shortName != null || description != null)
359+
if (name != null || shortName != null || displayDescription != null)
360+
{
361+
display = new DisplayInfo(name, shortName, displayDescription);
362+
}
363+
}
364+
}
365+
366+
// Check for Description attribute
367+
if (includeDescription)
368+
{
369+
var descriptionAttr = property.GetAttributes()
370+
.FirstOrDefault(a => a.AttributeClass?.Name == "DescriptionAttribute" &&
371+
a.AttributeClass?.ContainingNamespace?.ToDisplayString() == "System.ComponentModel");
372+
373+
if (descriptionAttr != null && descriptionAttr.ConstructorArguments.Length > 0)
374+
{
375+
string? text = descriptionAttr.ConstructorArguments[0].Value as string;
376+
377+
if (text != null)
342378
{
343-
display = new DisplayInfo(name, shortName, description);
379+
description = new DescriptionInfo(text);
344380
}
345381
}
346382
}
347383

348384
// Only add property if it has any attributes we're tracking
349-
if (stringLength.HasValue || range.HasValue || includeRequired || display.HasValue)
385+
if (stringLength.HasValue || range.HasValue || includeRequired || display.HasValue || description.HasValue)
350386
{
351387
propertyInfos.Add(new PropertyInformation(
352388
property.Name,
353389
stringLength,
354390
range,
355391
isRequired,
356-
display));
392+
display,
393+
description));
357394
}
358395
}
359396

0 commit comments

Comments
 (0)