Skip to main content
net10.0net11.0.NETStandard2.0

Source Generator to generate strongly-typed id with all needed helpers such as converters for System.Text.Json, Newtonsoft.Json, or MongoDB.

Published
Sep 27, 2026
23d ago
Total Downloads
196.0K
196,034

Install

dotnet add package Meziantou.Framework.StronglyTypedId --version 3.0.7
ProductVersions
.NET
net10.0net11.0
.NET Standard
netstandard2.0

README

Meziantou.Framework.StronglyTypedId

The source generator generates constructors, properties, equality members, common interfaces, converters for multiple serializers

[StronglyTypedId<int>]
public partial struct ProjectId { }

or using typeof if you're not using a version of C# that supports generic attributes

[StronglyTypedId(typeof(int))]
public partial struct ProjectId { }

StronglyTypedId generates the following code:

[System.ComponentModel.TypeConverterAttribute(typeof(ProjectIdTypeConverter))]
[System.Text.Json.Serialization.JsonConverterAttribute(typeof(ProjectIdJsonConverter))]
[Newtonsoft.Json.JsonConverterAttribute(typeof(ProjectIdNewtonsoftJsonConverter))]
[MongoDB.Bson.Serialization.Attributes.BsonSerializerAttribute(typeof(ProjectIdBsonConverter))]
public partial struct ProjectId :
    System.IEquatable<ProjectId>,
    System.IParsable<ProjectId>,         // .NET 7+
    System.ISpanParsable<ProjectId>,     // .NET 7+
    System.IUtf8SpanParsable<ProjectId>, // .NET 8+
    System.IFormattable,
    System.ISpanFormattable,             // .NET 6+
    System.IUtf8SpanFormattable,         // .NET 8+
    IStronglyTypedId,                    // When Meziantou.Framework.StronglyTypedId.Interfaces is referenced
    IStronglyTypedId<int>,               // When Meziantou.Framework.StronglyTypedId.Interfaces is referenced
    IComparable, IComparable<ProjectId>  // When at least one of the interface is explicitly defined by the user
{
    public int Value { get; }
    public string ValueAsString { get; } // Value formatted using InvariantCulture

    private ProjectId(int value);

    public static ProjectId FromInt32(int value);
    public static ProjectId Parse(string value);
    public static ProjectId Parse(ReadOnlySpan<char> value);
    public static bool TryParse(string value, out ProjectId result);
    public static bool TryParse(ReadOnlySpan<char> value, out ProjectId result);
    public override int GetHashCode();
    public override bool Equals(object? other);
    public bool Equals(ProjectId other);
    public static bool operator ==(ProjectId a, ProjectId b);
    public static bool operator !=(ProjectId a, ProjectId b);
    public override string ToString();

    private partial class ProjectIdTypeConverter : System.ComponentModel.TypeConverter
    {
        public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType);
        public override object? ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value);
        public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Type destinationType);
        public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType);
    }

    // Generated only when System.Text.Json is accessible
    private partial class ProjectIdJsonConverter : System.Text.Json.Serialization.JsonConverter<ProjectId>
    {
        public override void Write(System.Text.Json.Utf8JsonWriter writer, ProjectId value, System.Text.Json.JsonSerializerOptions options);
        public override ProjectId Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options);
    }

    // Generated only when Newtonsoft.Json is accessible
    private partial class ProjectIdNewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter
    {
        public override bool CanRead { get; }
        public override bool CanWrite { get; }
        public override bool CanConvert(System.Type type);
        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer);
        public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer);
    }

    // Generated only when MongoDB.Bson.Serialization.Serializers.SerializerBase is accessible
    private partial class ProjectIdBsonConverter : MongoDB.Bson.Serialization.Serializers.SerializerBase<ProjectId>
    {
        public override ProjectId Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args);
        public override void Serialize(MongoDB.Bson.Serialization.BsonSerializationContext context, MongoDB.Bson.Serialization.BsonSerializationArgs args, ProjectId value);
    }
}

Parsing and formatting are culture-invariant: the IFormatProvider arguments of the interface members are ignored and CultureInfo.InvariantCulture is always used. IUtf8SpanParsable, IFormattable, ISpanFormattable and IUtf8SpanFormattable are implemented explicitly, so the type doesn't expose overloads with an IFormatProvider parameter.

IUtf8SpanParsable decodes the UTF-8 text and uses TryParse(ReadOnlySpan) (or TryParse(string)), so both representations accept the same values.

IFormattable, ISpanFormattable and IUtf8SpanFormattable use the following rules:

  • Without a format, the result is the same as ToString(). So, $"{id}" and id.ToString() return the same value.
  • With a format, the format is applied to Value using the invariant culture. For instance, $"{id:N}" returns the Guid without the hyphens. The format is ignored when the underlying type isn't formattable (string, bool, ObjectId).
var id = ProjectId.FromInt32(42);
$"{id}";     // ProjectId { Value = 42 }
$"{id:D5}";  // 00042

If the Meziantou.Framework.StronglyTypedId.Interfaces NuGet package is present, the generator will implements IStronglyTypedId and IStronglyTypedId.

Supported types

  • System.Boolean
  • System.Byte
  • System.DateTime
  • System.DateTimeOffset
  • System.Decimal
  • System.Double
  • System.Guid
  • System.Half
  • System.Int16
  • System.Int32
  • System.Int64
  • System.Int128
  • System.Numerics.BigInteger
  • System.SByte
  • System.Single
  • System.String
  • System.Uint16
  • System.Uint32
  • System.Uint64
  • System.Uint128
  • MongoDB.Bson.ObjectId

Configuration

You can configure the code generation using the [StronglyTypedIdAttribute] attribute:

[StronglyTypedId(idType: typeof(long),
                 generateSystemTextJsonConverter: true,
                 generateNewtonsoftJsonConverter: true,
                 generateSystemComponentModelTypeConverter: true,
                 generateMongoDBBsonSerialization: true,
                 addCodeGeneratedAttribute: true
                 )]
public partial struct ProjectId { }

When the underlying type is System.Guid, you can choose how the generated New() method creates the value. The default is Version4 (Guid.NewGuid()). Version7 (Guid.CreateVersion7()) requires .NET 9 or later, otherwise the MFSTID0002 diagnostic is reported. None doesn't generate the New() method. The option only applies to System.Guid; using it with another underlying type reports the MFSTID0003 diagnostic:

[StronglyTypedId<Guid>(GuidGenerationStrategy = GuidGenerationStrategy.Version7)]
public partial struct ProjectId { }

// Generated by the source generator
public partial struct ProjectId
{
    public static ProjectId New() => new ProjectId(Guid.CreateVersion7());
}
[StronglyTypedId<Guid>(GuidGenerationStrategy = GuidGenerationStrategy.None)]
public partial struct ProjectId { } // No New() method is generated

You can configure the default value of the options for all the types of an assembly using the [assembly: StronglyTypedIdDefaults] attribute. Options set on [StronglyTypedId] take precedence over the assembly-level defaults:

[assembly: StronglyTypedIdDefaults(GuidGenerationStrategy = GuidGenerationStrategy.Version7,
                                   GenerateNewtonsoftJsonConverter = false,
                                   GenerateMongoDBBsonSerialization = false,
                                   GenerateSystemComponentModelTypeConverter = false,
                                   GenerateSystemTextJsonConverter = true,
                                   AddCodeGeneratedAttribute = true,
                                   StringComparison = StringComparison.Ordinal,
                                   GenerateToStringAsRecord = true)]

[StronglyTypedId<Guid>]
public partial struct ProjectId { } // New() uses Guid.CreateVersion7()

[StronglyTypedId<Guid>(GuidGenerationStrategy = GuidGenerationStrategy.Version4)]
public partial struct CustomerId { } // New() uses Guid.NewGuid()

You can generate IComparable, IComparable and comparison operators by adding one interface:

[StronglyTypedId<int>]
public partial struct ProjectId : IComparable { }

// Generated by the source generator
public partial struct ProjectId : IComparable<ProjectId>
{
	public int CompareTo(object? other);
	public int CompareTo(ProjectId other);
	public static bool operator <(ProjectId left, ProjectId right);
	public static bool operator <=(ProjectId left, ProjectId right);
	public static bool operator >(ProjectId left, ProjectId right);
	public static bool operator >=(ProjectId left, ProjectId right);
}

Additional resources

Analyzer rules

Id Category Description Severity Enabled
MFSTID0001 StronglyTypedId Not supported type Error ✔️
MFSTID0002 StronglyTypedId Guid generation strategy is not supported by the target framework Error ✔️
MFSTID0003 StronglyTypedId Guid generation strategy is only applicable to Guid Warning ✔️
MFSTID0004 StronglyTypedId Generic types are not supported Error ✔️

Changes from Previous Version

No structural changes between v3.0.6 and v3.0.7.

Adoption Assessment

A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.

Compatibility metadata lists 3 target frameworks; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.