StronglyTypedId.Attributes
1.0.0-beta03
This library is no longer necessary. Please update to the latest version and see https://github.com/andrewlock/StronglyTypedId#preserving-usages-of-the-stronglytypedid-attribute for details
See the version list below for details.
dotnet add package StronglyTypedId.Attributes --version 1.0.0-beta03
NuGet\Install-Package StronglyTypedId.Attributes -Version 1.0.0-beta03
<PackageReference Include="StronglyTypedId.Attributes" Version="1.0.0-beta03"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add StronglyTypedId.Attributes --version 1.0.0-beta03
#r "nuget: StronglyTypedId.Attributes, 1.0.0-beta03"
// Install StronglyTypedId.Attributes as a Cake Addin #addin nuget:?package=StronglyTypedId.Attributes&version=1.0.0-beta03&prerelease // Install StronglyTypedId.Attributes as a Cake Tool #tool nuget:?package=StronglyTypedId.Attributes&version=1.0.0-beta03&prerelease
StronglyTypedId
StronglyTypedId makes creating strongly-typed IDs as easy as adding an attribute! No more accidentally passing arguments in the wrong order to methods - StronglyTypedId uses .NET 6's compile-time incremental source generators to generate the boilerplate required to use strongly-typed IDs.
Simply, install the required package add the [StronglyTypedId]
attribute to a struct
(in the StronglyTypedIds
namespace):
using StronglyTypedIds;
[StronglyTypedId] // <- Add this attribute to auto-generate the rest of the type
public partial struct FooId { }
and the source generator magically generates the backing code when you save the file! Use Go to Definition to see the generated code:
<picture> <source srcset="https://raw.githubusercontent.com/andrewlock/StronglyTypedId/master/docs/strongly_typed_id.mp4" type="video/mp4"> <img src="https://raw.githubusercontent.com/andrewlock/StronglyTypedId/master/docs/strongly_typed_id.gif" alt="Generating a strongly-typed ID using the StronglyTypedId packages"/> </picture>
StronglyTypedId requires requires the .NET Core SDK v6.0.100 or greater.
Changes in version 1.x
Version 0.x of this library used the helper library CodeGeneration.Roslyn by AArnott, for build-time source generation. In version 1.0.0 this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 6+. As part of this change, there were a number of additional features added and breaking changes made.
Breaking Changes
StronglyTypedIds
namespace is required. In version 0.x of the library, the[StronglyTypedId]
attribute was in the global namespace. In version 1.x, the attribute is in theStronglyTypedIds
namespace, so you must addnamespace StronglyTypedIds;
.- The properties exposed by
StronglyTypedIds
have changed: there is no longer agenerateJsonConverter
property. Instead, this is infered based on theStronglyTypedIdConverters
flags provided. - The
String
backing typed ID will throw if you call the constructor with anull
value
New Features
- The attributes can now auto-generate additional converter types such as EF Core
ValueConverter
and DapperTypeHandler
, as described in my blog posts. These are optional flags on theconverters
property. - Made interface implementations (
IEquatable<T>
andIComparable<T>
currently) optional. This is to potentially support additional interfaces in future versions. - Added a
NullableString
backing type. Due to the behaviour ofstruct
s in c#, theString
backing type ID may still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with theNullableString
backing type. - Added a
[StronglyTypedIdDefaults]
attribute to set default values for all[StronglyTypedId]
attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a[StronglyTypedId]
instance.
Bug Fixes
- Some converters had incorrect implementations, such as in (#26). These have been addressed in version 1.x.
- Better null handling has been added for the
String
backing type, handling issues such as #32. - The code is marked as auto generated, to avoid errors such as #CS1591 as described in #27
- An error deserializing nullable StronglyTypedIds with Newtonsoft.Json #36
Installing
To use the the StronglyTypedId NuGet package, install the StronglyTypedId package into your project. Depending on which converters you implement, you may need one or more of the following additional packages
- Newtonsoft.Json (optional, only required if generating a Newtonsoft
JsonConverter
). Note that in some ASP.NET Core apps, you will likely already reference this project via transitive dependencies. - System.Text.Json (optional, only required if generating a System.Text
JsonConverter
). Note that in .NET Core apps, you will likely already reference this project via transitive dependencies. - Dapper (optional, only required if generating a type mapper)
- EF Core (optional, only required if generating an EF Core ValueConverter)
To install the packages, add the references to your csproj file so that it looks something like the following:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PackageReference Include="StronglyTypedId" Version="1.0.0-beta03" />
</Project>
Usage
To create a strongly-typed ID, create a partial struct
with the desired name, and decorate it with the [StronglyTypedId]
attribute, in the StronglyTypedIds
namespace:
using StronglyTypedIds;
[StronglyTypedId] // Add this attribute to auto-generate the rest of the type
public partial struct FooId { }
This generates the "default" strongly-typed ID using a Guid
backing field, a custom TypeConverter
, and a custom JsonConverter
based on Newtonsoft.Json.
Customising the converters
You can customise which converters to generate by using flags. For example, to generate a TypeConverter
, a System.Text.JsonConverter
, and an EF Core ValueConverter
, use
using StronglyTypedIds;
[StronglyTypedId(converters: StronglyTypedIdConverter.TypeConverter | StronglyTypedIdConverter.SystemTextJson | StronglyTypedIdConverter.EfCoreValueConverter)]
public partial struct SystemTextJsonConverterId { }
Using different types as a backing fields
The default strongly-typed ID uses a Guid
backing field:
using StronglyTypedIds;
[StronglyTypedId]
public partial struct FooId { }
var id = new FooId(Guid.NewGuid());
You can choose a different type backing field, by passing a value of the StronglyTypedIdBackingType
enum in the constructor.
using StronglyTypedIds;
[StronglyTypedId(backingType: StronglyTypedIdBackingType.String)]
public partial struct FooId { }
var id = new FooId("my-id-value");
Currently supported values are Guid
(the default), int
, long
, and string
.
Error CS0436 and [InternalsVisibleTo]
The StronglyTypedId generator automatically adds the [StronglyTypedId]
attributes to your compilation as internal
attributes. If you add the source generator package to multiple projects, and use the [InternalsVisibleTo]
attribute, you may experience errors when you build:
warning CS0436: The type 'StronglyTypedIdImplementations' in 'StronglyTypedIds\StronglyTypedIds.StronglyTypedIdGenerator\StronglyTypedIdImplementations.cs' conflicts with the imported type 'StronglyTypedIdImplementations' in 'MyProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Removing the [InternalsVisibleTo]
attribute will resolve the problem, but if this is not possible you can disable the auto-generation of the [StronglyTypedId]
marker attributes, and rely on the helper StronglyTypedId.Attributes
package instead. This package contains the same attributes, but as they are in an external package, you can avoid the CS0436 error.
Add the package to your solution, ensuring you set "PrivateAssets="All"
in the <PackageReference>
(this will be done automatically when using the .NET CLI or an IDE). To disable the auto-generation of the marker attributes, define the constant STRONGLY_TYPED_ID_EXCLUDE_ATTRIBUTES
in your project file. Your project file should look something like the following:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<DefineConstants>STRONGLY_TYPED_ID_EXCLUDE_ATTRIBUTES</DefineConstants>
</PropertyGroup>
<PackageReference Include="StronglyTypedId" Version="1.0.0-beta03" />
<PackageReference Include="StronglyTypedId.Attributes" Version="1.0.0-beta03">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</Project>
The attribute library is only required at compile time, so it won't appear in your build output.
Why do I need this library?
I have written a blog-post series on strongly-typed IDs that explains the issues and rational behind this library. For a detailed view, I suggest starting there, but I provide a brief introduction here.
This library is designed to tackle a specific instance of primitive obsession, whereby we use primitive objects (Guid
/string
/int
/long
etc) to represent the IDs of domain objects. The problem is that these IDs are all interchangeable - an order ID can be assigned to a product ID, despite the fact that is likely nonsensical from the domain point of view. See here for a more concrete example.
By using strongly-typed IDs, we give each ID its own Type
which wraps the underlying primitive value. This ensures you can only use the ID where it makes sense: ProductId
s can only be assigned to products, or you can only search for products using a ProductId
, not an OrderId
.
Unfortunately, taking this approach requires a lot of boilerplate and ceremony to make working with the IDs manageable. This library abstracts all that away from you, by generating the boilerplate at build-time by using a Roslyn-powered code generator.
What code is generated?
The exact code generated depends on the arguments you provide to the StronglyTypedId
attribute. The code is generated to the obj folder of the project, so you can use Go to Definition on your Id to see the exact code generated in each case.
You can see see example implementations in the test SourceGenerationHelperSnapshotTests
in which all permutations of the attribute are tested, and examples generated in the snapshots folder.
Requirements
The StronglyTypedId NuGet package is a .NET Standard 2.0 package.
You must be using the .NET 6+ SDK (though you can compile for other target frameworks like .NET Core 2.1 and .NET Framework 4.8)
The struct
s you decorate with the StronglyTypedId
attribute must be marked partial
, and cannot be nested inside another class.
Credits
StronglyTypedId
wouldn't work if not for AArnott's CodeGeneration.Roslyn library.
The build process and general design of the library was modelled on the RecordGenerator project, which is similar to this project, but can be used to generate immutable Record types.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 is compatible. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETCoreApp 2.1
- No dependencies.
-
.NETFramework 4.6.1
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
1.0.0-beta04 | 8,347 | 11/26/2021 | |
1.0.0-beta03 | 3,330 | 11/25/2021 | |
0.2.1 | 449,886 | 5/17/2020 | |
0.2.0 | 4,219 | 4/29/2020 | |
0.1.2 | 5,110 | 6/5/2019 |
Version 0.x of this library used the helper library CodeGeneration.Roslyn for build-time source generation. In version 1.x this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 5+. As part of this change, there were a number of additional features added and breaking changes made.
## Changes in 1.0.0-beta03:
Breaking Changes:
* Converted to use .NET 6's incremental source generators. This should provide performance improvements, but it requires using the .NET 6 SDK.
Bug fixes:
* Fixed problem deserializing nullable strongly-typed IDs with Newtonsoft.Json (https://github.com/andrewlock/StronglyTypedId/issues/36)
New Features:
* To support scenarios in which [InternalsVisibleTo] causes duplicate reference issues with the marker attributes, you can set the msbuild constant `STRONGLY_TYPED_ID_EXCLUDE_ATTRIBUTES` to exclude these from build output. You must then reference the StronglyTypedId.Attributes project as well, which contains the marker attributes.
* By default, the marker attributes are decorated with the `[Conditional]` attribute, so they will not appear on your IDs. If you need these to persist, define the msbuild constant `STRONGLY_TYPED_ID_USAGES`.
## Changes in 1.0.0-beta02:
Bug fixes
* Adds auto-generated attributes and enums as `internal` to help avoid referencing issues
## Breaking Changes
* `StronglyTypedIds` namespace is required. In version 0.x of the library, the `[StronglyTypedId]` attribute was in the global namespace. In version 1.x, the attribute is in the `StronglyTypedIds` namespace, so you must add `namespace StronglyTypedIds;`.
* The properties exposed by `StronglyTypedIds` have changed: there is no longer a `generateJsonConverter` property. Instead, this is infered based on the `StronglyTypedIdConverters` flags provided.
* The `String` backing typed ID will throw if you call the constructor with a `null` value
## New Features
* The attributes can now auto-generate additional converter types such as EF Core `ValueConverter` and Dapper `TypeHandler`, as described in [my blog posts](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). These are optional flags on the `converters` property.
* Made interface implementations (`IEquatable<T>` and `IComparable<T>` currently) optional. This is to potentially support additional interfaces in future versions.
* Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type.
* Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance.
## Bug Fixes
* Some converters had incorrect implementations, such as in ([#26](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x.
* Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32).
* The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27)
See https://github.com/andrewlock/StronglyTypedId/blob/master/CHANGELOG.md#v100 for more details.