StronglyTypedId

repository·master·Indexed 23 days ago

https://github.com/andrewlock/stronglytypedid

A .NET source generator that automates the creation of strongly-typed IDs to prevent primitive obsession. It allows developers to replace generic types like Guid, int, long, or string with type-safe structs using the [StronglyTypedId] attribute. The library supports custom templates, global defaults, and provides an optional Templates package for EF Core, Dapper, and Newtonsoft JSON integration.

Tokens
2.6K
Snippets
8
Records
12
Agent score
33%

What's inside StronglyTypedId

  1. Use multiple templates for a single ID

    master
    The [StronglyTypedId] attribute allows you to combine one built-in template with zero or more custom templates. This is useful for adding extra functionality (like EF Core converters or Dapper support) to a standard ID type.
  2. Install the StronglyTypedId NuGet package

    master

    To use StronglyTypedId, install the NuGet package into your project. It is recommended to mark the package as PrivateAssets="all" and ExcludeAssets="runtime" because the source generator handles the heavy lifting at build-time, and the attributes are not required at runtime.

    dotnet add package StronglyTypedId --version 1.0.0-beta08
    <ItemGroup>
      <PackageReference Include="StronglyTypedId" Version="1.0.0-beta08" PrivateAssets="all" ExcludeAssets="runtime" />
    </ItemGroup>
  3. Create a strongly-typed ID

    master

    To create a strongly-typed ID, define a partial struct and decorate it with the [StronglyTypedId] attribute within the StronglyTypedIds namespace. By default, the generator uses a Guid as the backing field. The generated type automatically implements several interfaces including IComparable<T>, IEquatable<T>, IFormattable, and serialization support for System.Text.Json and System.ComponentModel.TypeConverter.

    using StronglyTypedIds;
     
    [StronglyTypedId] // <- Add this attribute to auto-generate the rest of the type
    public partial struct FooId { }
  4. Create a custom template using the Roslyn CodeFix provider

    master

    If you specify a template name in the [StronglyTypedId] attribute that does not exist, the IDE will mark it as an error. You can use the built-in CodeFix provider to automatically generate a new template file.

    1. Trigger CodeFix: Click the error in the IDE and select the action Add [template-name].typedid template to the project.
    2. Fix Build Action: Due to Roslyn limitations, the newly added file might not have the correct build action set. To resolve the error, manually change the file's Build Action:
      • Visual Studio 2022: Set to C# analyzer additional file.
      • JetBrains Rider: Set to AdditionalFiles.

    Template Generation Logic: The provider guesses the backing type based on the template name:

    • If the name includes int, long, or string, it uses that type.
    • Otherwise, it defaults to a Guid backing type.
  5. Create and use custom templates

    master

    You can extend the library by providing custom templates.

    1. Create a file named TEMPLATE.typedid (where TEMPLATE is your template name).
    2. Use PLACEHOLDERID inside the file; this will be replaced by the ID's name during generation.
    3. Set the file's Build Action to AdditionalFiles or C# analyzer additional file.

    To apply a custom template, pass its name as a string to the [StronglyTypedId] attribute.

    // Example template content in guid-efcore.typedid
    partial struct PLACEHOLDERID
    {
        public class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<PLACEHOLDERID, global::System.Guid>
        {
            public EfCoreValueConverter() : this(null) { }
            public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null)
                : base(
                    id => id.Value,
                    value => new PLACEHOLDERID(value),
                    mappingHints
                ) { }
        }
    }
    
    // Applying the custom template
    [StronglyTypedId(Template.Guid, "guid-efcore")] 
    public partial struct GuidId {}
  6. Migrate from 0.x.x to 1.x.x

    master

    When migrating from version 0.x to 1.x, the library has transitioned from using CodeGeneration.Roslyn to native .NET Source Generators. This change requires several adjustments to your code and configuration.

    Required Namespace Change

    In version 1.x, the [StronglyTypedId] attribute is no longer in the global namespace. You must include the StronglyTypedIds namespace in your file.

    Property Changes

    • The generateJsonConverter property has been removed. JSON converter generation is now inferred from the converters property using StronglyTypedIdConverters flags.

    Backing Type Behavior

    • String IDs: The String backing type will now throw an exception if you attempt to call the constructor with a null value.
    • NullableString: A new NullableString backing type has been added. Unlike the standard String type, NullableString allows you to explicitly pass null to the constructor.
  7. Install the StronglyTypedId.Templates package

    master

    To access a collection of pre-defined templates (including full implementations and specialized converters for EF Core, Dapper, and Newtonsoft JSON), install the StronglyTypedId.Templates NuGet package.

    dotnet add package StronglyTypedId.Templates --version 1.0.0-beta08
  8. Preserve [StronglyTypedId] attributes for reflection

    master

    The [StronglyTypedId] and [StronglyTypedIdDefaults] attributes are decorated with [Conditional], meaning they are stripped from the build output and won't be visible via runtime reflection.

    To preserve these attributes in the build output, define the STRONGLY_TYPED_ID_USAGES MSBuild constant. Note: This creates a runtime dependency on StronglyTypedId.Attributes.dll, so you must ensure it is included in your build output (do not exclude compile or runtime assets).

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <!-- Define the MSBuild constant to preserve usages -->
        <DefineConstants>STRONGLY_TYPED_ID_USAGES</DefineConstants>
      </PropertyGroup>
    
      <!-- Add the package -->
      <PackageReference Include="StronglyTypedId" Version="1.0.0-beta08" PrivateAssets="all" />
    
    </Project>
  9. Embed StronglyTypedId attributes in your project

    master

    By default, attributes are contained in an external DLL. To embed them directly into your project's assembly, you must configure your project file to define the STRONGLY_TYPED_ID_EMBED_ATTRIBUTES constant and exclude the compile asset from the package to avoid referencing the external StronglyTypedId.Attributes.dll.

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <!-- Define the MSBuild constant -->
        <DefineConstants>STRONGLY_TYPED_ID_EMBED_ATTRIBUTES</DefineConstants>
      </PropertyGroup>
    
      <!-- Add the package -->
      <PackageReference Include="StronglyTypedId" Version="1.0.0-beta08" 
                        PrivateAssets="all"
                        ExcludeAssets="compile;runtime" />
    
    </Project>
  10. Use different types as backing fields

    master

    While Guid is the default, you can specify a different built-in backing type by passing a value from the Template enum to the [StronglyTypedId] attribute. Supported built-in types are Guid, int, long, and string.

    using StronglyTypedIds;
    
    [StronglyTypedId(Template.Int)]
    public partial struct FooId { }
    
    var id = new FooId(123);
  11. Change default templates globally

    master

    You can set a default template for all [StronglyTypedId] decorated IDs in your assembly using the [StronglyTypedIdDefaults] attribute. This allows you to use the bare [StronglyTypedId] attribute while still controlling the underlying type for the entire project.

    // Set the defaults for the project to 'int'
    [assembly:StronglyTypedIdDefaults(Template.Int)]
    
    [StronglyTypedId] // Uses the default 'int' template
    public partial struct OrderId { }
    
    [StronglyTypedId(Template.Guid)] // Overrides the default to use 'Guid'
    public partial struct HostId { } 
  12. Reference templates in StronglyTypedId.Templates

    master

    Once StronglyTypedId.Templates is installed, you can use various template names.

    Full Implementations (includes multiple converters):

    • guid-full
    • int-full
    • long-full
    • string-full
    • nullablestring-full
    • newid-full

    Standalone Converters (to enhance built-in templates like Template.Guid, Template.Int, Template.Long, or Template.String):

    • Dapper: [template-name]-dapper (e.g., guid-dapper)
    • EF Core: [template-name]-efcore (e.g., int-efcore)
    • Newtonsoft JSON: [template-name]-newtonsoftjson (e.g., string-newtonsoftjson)