Skip to main content

Pekspro.DataAnnotationValuesExtractor by Pekspro

NuGet / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: Pekspro.DataAnnotationValuesExtractor

A source generator for creating constants from data annotations.

Author: Pekspro

NuGet: https://www.nuget.org/packages/Pekspro.DataAnnotationValuesExtractor/

You can find more details at https://github.com/pekspro/DataAnnotationValuesExtractor

Source: https://github.com/pekspro/DataAnnotationValuesExtractor

Author

note

Pekspro Alt text

Original Readme

note

DataAnnotationValuesExtractor

Build
status NuGet

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

Why Use This?

When working with data annotations, you often need to reference validation constraints. A good way to solve it is to create constants. But it takes time to do and makes your data models harder to read. And it's harder when your models are auto generated like when you are scaffolding with Entity Framework.

This source generator creates the constants automatically for you. If you have this model:

public partial class Product
{
[Required]
[StringLength(100)]
public string? Name \{ get; set; }

[Required]
[Range(0.01, 999999.99)]
public decimal Price \{ get; set; }
}

Constants will be generated that you can access like this:

// Name length constraints
int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
bool nameRequired = Product.Annotations.Name.IsRequired; // true

// Price constraints
double minPrice = Product.Annotations.Price.Minimum; // 0.01
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
Usage Patterns

There are two ways to configure DataAnnotationValuesExtractor depending on your needs:

######### 1. Direct Approach

Apply [DataAnnotationValues] directly to each class you want to generate constants for:

[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
public partial class Product
{
[Display(Name = "Product name")]
[Required]
[StringLength(100)]
public string? Name \{ get; set; }

[Display(Name = "Product price")]
[Required]
[Range(0.01, 999999.99)]
public decimal Price \{ get; set; }

public string? Sku \{ get; set; }
}

######### 2. Centralized Approach

Create a dummy class and use the DataAnnotationValuesToGenerate attribute for each class you want to generate constants for. You can use the DataAnnotationValuesConfiguration attribute to configure what to be generated.

using Pekspro.DataAnnotationValuesExtractor;

[DataAnnotationValuesConfiguration(StringLength = true, Range = true, Required = true, Display = true)]
[DataAnnotationValuesToGenerate(typeof(Customer))]
[DataAnnotationValuesToGenerate(typeof(Order))]
[DataAnnotationValuesToGenerate(typeof(Product))]
partial class ValidationConstants
{
}

Your model classes remain unchanged.

This approach is especially useful when working with auto-generated models, such as those created by Entity Framework scaffolding. If you do, and you have all your models in a folder, you can use this PowerShell script to generate the attributes for all models in that folder:

Get-ChildItem -Filter '*.cs' |
Where-Object \{ -not ($_.BaseName -match '(?i)context') \} |
ForEach-Object \{ "[DataAnnotationValuesToGenerate(typeof($($_.BaseName)))]" \} |
Set-Clipboard

######### Use the generated constants

No matter which approach your are using, you can access generated constants like this:

// Name
int maxNameLength = Product.Annotations.Name.MaximumLength; // 100
int minNameLength = Product.Annotations.Name.MinimumLength; // 0
bool nameRequired = Product.Annotations.Name.IsRequired; // true
string? nameDisplayName = Product.Annotations.Name.Display.Name; // Product name

// Price
double minPrice = Product.Annotations.Price.Minimum; // 0.01
double maxPrice = Product.Annotations.Price.Maximum; // 999999.99
bool priceRequired = Product.Annotations.Price.IsRequired;
string? priceDisplayName = Product.Annotations.Price.Display.Name; // Price name

// Sku
bool skuRequired = Product.Annotations.Sku.IsRequired; // false
Installation

Add the package to your project:

dotnet add package Pekspro.DataAnnotationValuesExtractor

For optimal setup, configure the package reference in your .csproj to exclude it from your output assembly:

<ItemGroup>
<PackageReference Include="Pekspro.DataAnnotationValuesExtractor" Version="0.0.1"
PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>

Why these attributes?

  • PrivateAssets="all" - Projects referencing yours won't inherit this package, they don't need it.
  • ExcludeAssets="runtime" - The attributes DLL won't be copied to your build output, this is also not needed.
Configuration Options

Both [DataAnnotationValues] and [DataAnnotationValuesConfiguration] support the following properties to control which constants are generated:

PropertyDefaultGenerated ConstantsDescription
StringLengthtrueMaximumLength, MinimumLengthExtract values from [StringLength] attribute.
RangetrueMinimum, Maximum, MinimumIsExclusive, MaximumIsExclusiveExtract values from [Range] attribute.
RequiredfalseIsRequiredDetect presence of [Required] attribute.
DisplayfalseName, Description, ShortNameExtract values from [Display] attribute.

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

Viewing Generated Code

There are two ways to inspect the generated source code:

######### Method 1: Go to Definition

Right-click on your class name and select Go to Definition (F12). Visual Studio will show you the generated partial class.

######### Method 2: Output to File

Add this to your .csproj file to save generated files to disk:

<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\$(Configuration)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

Generated files will be saved to obj\[Configuration]\GeneratedFiles\ directory.

Generated Code Example

Given this input:

[DataAnnotationValues(StringLength = true, Range = true, Required = true, Display = true)]
public partial class Player
{
[Display(Name = "Player name", ShortName ="Name", Description = "Name of player")]
[Required]
[StringLength(50)]
public string? Name \{ get; set; }

[StringLength(100, MinimumLength = 6)]
public string? Email \{ get; set; }

[Range(1, 100)]
public int Score \{ get; set; }
}

The generator produces:

public partial class Player
{
/// <summary>
/// Data annotation values.
/// </summary>
public static class Annotations
{
/// <summary>
/// Data annotation values for Name.
/// </summary>
public static class Name
{
/// <summary>
/// Maximum length for Name.
/// </summary>
public const int MaximumLength = 50;

/// <summary>
/// Minimum length for Name.
/// </summary>
public const int MinimumLength = 0;

/// <summary>
/// Indicates whether Name is required.
/// </summary>
public const bool IsRequired = true;

/// <summary>
/// Display attribute values for Name.
/// </summary>
public static class Display
{
/// <summary>
/// Display name for Name.
/// </summary>
public const string? Name = "Player name";

/// <summary>
/// Short display name for Name.
/// </summary>
public const string? ShortName = "Name";

/// <summary>
/// Description for Name.
/// </summary>
public const string? Description = "Name of player";
}
}

/// <summary>
/// Data annotation values for Email.
/// </summary>
public static class Email
{
/// <summary>
/// Maximum length for Email.
/// </summary>
public const int MaximumLength = 100;

/// <summary>
/// Minimum length for Email.
/// </summary>
public const int MinimumLength = 6;

/// <summary>
/// Indicates whether Email is required.
/// </summary>
public const bool IsRequired = false;
}

/// <summary>
/// Data annotation values for Score.
/// </summary>
public static class Score
{
/// <summary>
/// Minimum value for Score.
/// </summary>
public const int Minimum = 1;

/// <summary>
/// Maximum value for Score.
/// </summary>
public const int Maximum = 100;

/// <summary>
/// Indicates whether the minimum value for Score is exclusive.
/// </summary>
public const bool MinimumIsExclusive = false;

/// <summary>
/// Indicates whether the maximum value for Score is exclusive.
/// </summary>
public const bool MaximumIsExclusive = false;

/// <summary>
/// Indicates whether Score is required.
/// </summary>
public const bool IsRequired = false;
}
}
}
Requirements
  • .NET SDK 7.0 or later - Required for the source generator to run
  • Target Framework - Can target .NET Core 3.1, .NET Standard 2.0, or any later framework
  • Partial Classes - Generated code uses partial classes, so it must be in the same assembly as your models

Note: You need .NET 7 SDK installed, but your project can target earlier frameworks.

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests on GitHub.

Credits

This project is heavily inspired by the NetEscapades.EnumExtractors project.

License

This project is licensed under the MIT License. See the LICENSE file for details.

About

note

Generating code to extract values from data annotations in C#.

How to use

Example (source csproj, source files)

This is the CSharp Project that references Pekspro.DataAnnotationValuesExtractor

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Pekspro.DataAnnotationValuesExtractor" Version="1.0.0" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

//---------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by the Pekspro.DataAnnotationValuesExtractor source generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//---------------------------------------------------------------------------------------

#nullable enable

namespace Attr
{
/// <summary>
/// Data annotation values for Person.
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Pekspro.DataAnnotationValuesExtractor", "1.0.0")]
public partial class Person
{
/// <summary>
/// Data annotation values.
/// </summary>
public static class Annotations
{
/// <summary>
/// Data annotation values for FirstName.
/// </summary>
public static class FirstName
{
/// <summary>
/// Maximum length for FirstName.
/// </summary>
public const int MaximumLength = 100;

/// <summary>
/// Minimum length for FirstName.
/// </summary>
public const int MinimumLength = 3;

/// <summary>
/// Indicates whether FirstName is required.
/// </summary>
public const bool IsRequired = true;

/// <summary>
/// Display attribute values for FirstName.
/// </summary>
public static class Display
{
/// <summary>
/// Display name for FirstName.
/// </summary>
public const string? Name = "First name";

/// <summary>
/// Short display name for FirstName.
/// </summary>
public const string? ShortName = null;

/// <summary>
/// Description for FirstName.
/// </summary>
public const string? Description = null;
}
}

/// <summary>
/// Data annotation values for Age.
/// </summary>
public static class Age
{
/// <summary>
/// Minimum value for Age.
/// </summary>
public const int Minimum = 18;

/// <summary>
/// Maximum value for Age.
/// </summary>
public const int Maximum = 200;

/// <summary>
/// Indicates whether the minimum value for Age is exclusive.
/// </summary>
public const bool MinimumIsExclusive = false;

/// <summary>
/// Indicates whether the maximum value for Age is exclusive.
/// </summary>
public const bool MaximumIsExclusive = false;

/// <summary>
/// Indicates whether Age is required.
/// </summary>
public const bool IsRequired = true;
}
}
}
}

Useful

Download Example (.NET C#)

Share Pekspro.DataAnnotationValuesExtractor

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Pekspro.DataAnnotationValuesExtractor

Category "EnhancementClass" has the following generators:

1 ApparatusAOT Nuget GitHub Repo stars 2023-04-16

2 AspectGenerator Nuget GitHub Repo stars 2024-01-07

3 CommonCodeGenerator Nuget GitHub Repo stars 2024-04-03

4 Comparison Nuget GitHub Repo stars 2025-05-25

5 DudNet Nuget GitHub Repo stars 2023-10-27

6 Enhanced.GetTypes Nuget GitHub Repo stars 2024-09-17

7 FastGenericNew Nuget GitHub Repo stars 2023-08-10

8 Immutype Nuget GitHub Repo stars 2023-08-12

9 Ling.Audit Nuget GitHub Repo stars 2023-12-12

10 Lombok.NET Nuget GitHub Repo stars 2023-04-16

11 M31.FluentAPI Nuget GitHub Repo stars 2023-08-25

12 MemberAccessor Nuget GitHub Repo stars 2025-03-24

13 MemoryPack Nuget GitHub Repo stars 2023-08-04

14 Meziantou.Polyfill Nuget GitHub Repo stars 2023-10-10

15 Microsoft.Extensions.Logging Nuget GitHub Repo stars 2023-04-16

16 Microsoft.Extensions.Options.Generators.OptionsValidatorGenerator Nuget GitHub Repo stars 2023-11-17

17 Microsoft.Interop.JavaScript.JSImportGenerator 2023-04-16

18 OptionToStringGenerator Nuget GitHub Repo stars 2024-02-15

19 Pekspro.DataAnnotationValuesExtractor Nuget GitHub Repo stars 2026-02-15

20 Program Nuget GitHub Repo stars 2025-11-06

21 QueryStringGenerator Nuget GitHub Repo stars 2024-11-07

22 RSCG_Decorator Nuget GitHub Repo stars 2023-09-30

23 RSCG_UtilityTypes NugetNuget GitHub Repo stars 2023-12-22

24 StaticReflection NugetNuget GitHub Repo stars 2023-10-13

25 SyncMethodGenerator Nuget GitHub Repo stars 2023-08-14

26 System.Runtime.InteropServices Nuget GitHub Repo stars 2023-04-16

27 System.Text.RegularExpressions Nuget GitHub Repo stars 2023-04-16

28 TelemetryLogging Nuget GitHub Repo stars 2023-11-30

29 ThisClass Nuget GitHub Repo stars 2024-04-19

See category

EnhancementClass