### Install AI Agent Skill Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/AI-Coding-Assistants Installs the AI agent skill for Thinktecture.Runtime.Extensions into the current project using the 'skills' CLI. ```bash npx skills@latest add PawelGerr/Thinktecture.Runtime.Extensions ``` -------------------------------- ### Install AI Agent Skill Globally Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/AI-Coding-Assistants Installs the AI agent skill for Thinktecture.Runtime.Extensions globally for the current user using the 'skills' CLI. ```bash npx skills@latest add PawelGerr/Thinktecture.Runtime.Extensions -g ``` -------------------------------- ### Amount Value Object Usage Examples Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Shows how to create Amount instances, perform equality and comparison operations, and use arithmetic operations with other Amounts or decimals. ```csharp // get an instance of amount from database, with Create/TryCreate or an explicit cast var amount = Amount.Create(1m); var otherAmount = (Amount)2m; var zero = Amount.Zero; // equality comparisons amount == zero; // false amount > otherAmount; // false amount > 42m; // false amount.CompareTo(otherAmount); // -1 // arithmetic operations amount + otherAmount; // 3 amount + 42m // 43 ``` -------------------------------- ### FileUrn Usage Examples Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates creating, parsing, validating, serializing, and deserializing FileUrn objects. Includes handling of invalid formats. ```csharp // Creating a FileUrn from its components var documentLocation = FileUrn.Create("blob storage", "containers/documents/contract.pdf"); var imageLocation = FileUrn.Create("local file system", "images/profile/user123.jpg"); // Parsing from string var parsed = FileUrn.Parse("blob storage:containers/documents/contract.pdf", null); // IParsable.Parse Console.WriteLine($"parsed file urn: {parsed}"); // { FileStore = blob storage, Urn = containers/documents/contract.pdf } Console.WriteLine($"documentLocation == parsed: {documentLocation == parsed}"); // true // Validation try { var invalid = FileUrn.Parse("invalid-format", null); } catch (FormatException ex) { Console.WriteLine(ex.Message); // "Invalid FileUrn format. Expected 'fileStore:urn'" } // Serialization var json = JsonSerializer.Serialize(documentLocation); Console.WriteLine($"Serialized JSON: {json}"); // "blob storage:containers/documents/contract.pdf" var deserialized = JsonSerializer.Deserialize(json); Console.WriteLine($"Deserialized FileUrn: {deserialized}"); // blob storage:containers/documents/contract.pdf ``` -------------------------------- ### Custom Configuration for Max Length Strategies Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Framework-Integration Example of creating a custom Configuration object to specify max length strategies for Smart Enums and Keyed Value Objects. ```csharp new Configuration { SmartEnums = new SmartEnumConfiguration { // Choose a max length strategy MaxLengthStrategy = DefaultSmartEnumMaxLengthStrategy.Instance }, KeyedValueObjects = new KeyedValueObjectConfiguration { MaxLengthStrategy = new CustomKeyedValueObjectMaxLengthStrategy((type, keyType) => { if (type == typeof(ProductName)) return 200; return MaxLengthChange.None; }) } } ``` -------------------------------- ### Period Value Object Usage Examples Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates creating Period instances, handling validation exceptions, comparing periods for equality, and checking for intersections. ```csharp // Creating Period instances var startDate = new DateOnly(2023, 1, 1); var endDate = OpenEndDate.Create(2023, 12, 31); var period = Period.Create(startDate, endDate); // Validation examples try { var invalidPeriod = Period.Create( new DateOnly(2023, 12, 31), OpenEndDate.Create(2023, 1, 1) ); // Throws ValidationException } catch (ValidationException ex) { Console.WriteLine(ex.Message); // "From must be earlier than Until" } // Equality comparison var samePeriod = Period.Create(startDate, endDate); var areEqual = period == samePeriod; // True Console.WriteLine($"period == samePeriod: {areEqual}"); // Checking if period intersects with another period var otherPeriod = Period.Create(new DateOnly(2023, 6, 1), OpenEndDate.Create(2024, 6, 1)); var intersects = period.IntersectsWith(otherPeriod); // true Console.WriteLine($"period.IntersectsWith(otherPeriod): {intersects}"); ``` -------------------------------- ### Daily Sales CSV Format Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Example of the CSV structure for daily sales data. ```csv id,datetime,volume 1,20230425 10:45,345.67 ``` -------------------------------- ### Non-interactive Skill Installation for Specific Agent Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/AI-Coding-Assistants Installs the AI agent skill for Thinktecture.Runtime.Extensions non-interactively for a specific agent (e.g., claude-code) using the 'skills' CLI. ```bash npx skills@latest add PawelGerr/Thinktecture.Runtime.Extensions -a claude-code -y ``` -------------------------------- ### Complex Value Object Creation and Validation Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates creating complex value objects using `Create` and `TryCreate`, and validating them with `Validate`. Includes examples of handling validation errors. ```csharp // Creation and Validation var boundary = Boundary.Create(lower: 1.234m, upper: 2.567m); // Rounds to (1.23, 2.57) // Using TryCreate (returns false if invalid) if (Boundary.TryCreate(lower: 5, upper: 3, out var invalidBoundary)) { // Won't reach here - validation fails } // Using TryCreate with validation error details if (!Boundary.TryCreate(lower: 5, upper: 3, out var boundary, out var error)) { Console.WriteLine(error.ToString()); } // Using Validate (returns ValidationError if invalid) var error = Boundary.Validate(lower: 5, upper: 3, out var boundaryWithError); if (error is not null) { Console.WriteLine(error.ToString()); // "Lower boundary '5' must be less than or equal to upper boundary '3'" } ``` -------------------------------- ### String Ordinal Comparer Implementation Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Customization Example implementation of an IComparerAccessor for string members using StringComparer.Ordinal. ```csharp public class StringOrdinal : IComparerAccessor { public static IComparer Comparer => StringComparer.Ordinal; } ``` -------------------------------- ### OpenEndDate Basic Usage Example Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates how to create and manipulate `OpenEndDate` instances, including creating specific dates, representing infinite dates, performing date manipulations like moving to the end of the month, and comparing dates. ```csharp // Creating OpenEndDate var specificDate = OpenEndDate.Create(2023, 12, 31); var infiniteDate = OpenEndDate.Infinite; var defaultDate = default(OpenEndDate); // Same as Infinite // Date manipulation var endOfMonth = OpenEndDate.Create(2023, 3, 15).MoveToEndOfMonth(); // 2023-03-31 // Compare the dates var isLater = infiniteDate > specificDate; // true var isEqual = infiniteDate == defaultDate; // true // String representation Console.WriteLine(specificDate); // "2023-12-31" Console.WriteLine(infiniteDate); // "Infinite" ``` -------------------------------- ### Type Parameter Member Union Example Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions Demonstrates creating union types with type parameters using constructors and factory methods. Implicit conversions are not available for type parameters. ```csharp [Union] public partial struct Result; // Type parameter member — use constructor or factory method (no implicit conversion available) Result success = new Result(42); Result alsoSuccess = Result.CreateT(42); // Concrete type member — implicit conversion still works, factory method also available Result error = "Something failed"; Result alsoError = Result.CreateString("Something failed"); ``` -------------------------------- ### Monthly Sales CSV Formats Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Examples of the two possible CSV structures for monthly sales data. ```csv volume,datetime,id 123.45,20230426 11:50,2 OR volume,quantity,id,datetime 123.45,42,2,2023-04-25 ``` -------------------------------- ### String Ordinal Equality Comparer Implementation Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Customization Example implementation of an IEqualityComparerAccessor for string members using StringComparer.Ordinal. ```csharp public class StringOrdinal : IEqualityComparerAccessor { public static IEqualityComparer EqualityComparer => StringComparer.Ordinal; } ``` -------------------------------- ### Equality Comparison Using All Assignable Properties Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization To include all assignable properties in equality comparison, either omit MemberEqualityComparerAttribute or apply it to every member. This example applies it to both 'Lower' and 'Upper'. ```csharp [ComplexValueObject] public sealed partial class Boundary { [MemberEqualityComparer, decimal>] public decimal Lower { get; } [MemberEqualityComparer, decimal>] public decimal Upper { get; } } ``` -------------------------------- ### Usage of Jurisdiction Value Objects Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates creating, comparing, and using the switch expression with Jurisdiction value objects. Includes examples of validation exceptions. ```csharp // Creating different jurisdictions var district = Jurisdiction.District.Create("District 42"); var country = Jurisdiction.Country.Create("DE"); var unknown = Jurisdiction.Unknown.Instance; // Comparing jurisdictions var district42 = Jurisdiction.District.Create("DISTRICT 42"); Console.WriteLine($"district == district42: {district == district42}"); // true var district43 = Jurisdiction.District.Create("District 43"); Console.WriteLine($"district == district43: {district == district43}"); // false Console.WriteLine($"unknown == Jurisdiction.Unknown.Instance: {unknown == Jurisdiction.Unknown.Instance}"); // true // Validation examples try { var invalidJurisdiction = Jurisdiction.Country.Create("DEU"); // Throws ValidationException } catch (ValidationException ex) { Console.WriteLine(ex.Message); // "ISO code must be exactly 2 characters long." } var description = district.Switch( country: c => $"Country: {c}", federalState: s => $"Federal state: {s}", district: d => $"District: {d}", unknown: _ => "Unknown" ); Console.WriteLine(description); ``` -------------------------------- ### Parameterless Factory Method for Stateless Members Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization Generates a parameterless factory method for stateless members (marked with `TIsStateless = true`). This example shows a union with a string and a stateless record struct. ```csharp [Union( T2IsStateless = true, FactoryMethodGeneration = FactoryMethodGeneration.Always)] public partial class TextOrEmpty; public readonly record struct EmptyState; // Generated: // TextOrEmpty.CreateString(string value) // TextOrEmpty.CreateEmptyState() — parameterless because EmptyState is stateless ``` -------------------------------- ### Quick Start: Simple String Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Define a simple, type-safe string-based value object. The source generator provides factory methods, equality operators, and conversions. ```csharp [ValueObject] public partial class ProductName { } ``` -------------------------------- ### Smart Enum with String Conversion Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Object-Factories Example of a Smart Enum that uses ObjectFactory to accept specific string inputs for conversion. Requires implementing a static Validate method. ```csharp [SmartEnum] [ObjectFactory] public partial class MyEnum { public static readonly MyEnum Item1 = new(1); public static ValidationError? Validate(string? value, IFormatProvider? provider, out MyEnum? item) { switch (value) { case "=1=": item = Item1; return null; default: item = null; return new ValidationError($"Unknown value '{value}'"); } } } ``` -------------------------------- ### Configure EF Core for Message and MessageStates Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Framework-Integration Configures Entity Framework Core to map the Message and MessageState entities using Table-Per-Hierarchy (TPH) inheritance. Includes setup for discriminator, primary keys, and foreign keys. ```csharp public class MessageEntityTypeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Messages"); builder.HasMany(m => m.States) .WithOne() .HasForeignKey("MessageId"); builder.Navigation(m => m.States).AutoInclude(); } } public class MessageStateEntityTypeConfiguration : IEntityTypeConfiguration, IEntityTypeConfiguration, IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("MessageStates"); builder.HasKey(s => s.Order); builder.Property(s => s.Order).ValueGeneratedOnAdd(); // auto-increment builder.Property("MessageId"); // FK to the message table (as a shadow property) builder .HasDiscriminator("Type") .HasValue("Initial") .HasValue("Parsed") .HasValue("Processed") .HasValue("Error"); } public void Configure(EntityTypeBuilder builder) { builder.Property(s => s.CreatedAt).HasColumnName("CreatedAt"); } public void Configure(EntityTypeBuilder builder) { builder.Property(s => s.CreatedAt).HasColumnName("CreatedAt"); } } ``` -------------------------------- ### Period Complex Value Object Definition Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Defines a Period struct with a start date and an open-ended end date. Includes validation for ensuring the start date is before the end date. ```csharp [ComplexValueObject] public partial struct Period { /// /// The definite start date of the period. /// public DateOnly From { get; } /// /// The open-ended end date of the period. /// public OpenEndDate Until { get; } static partial void ValidateFactoryArguments( ref ValidationError? validationError, ref DateOnly from, ref OpenEndDate until ) { if (from >= until) validationError = new ValidationError("From must be earlier than Until"); } public bool IntersectsWith(Period other) { return From <= other.Until && other.From <= Until; } } ``` -------------------------------- ### Define Method Accepting IReadOnlyCollection Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/ToReadOnlyCollection Example signature for a method requiring an IReadOnlyCollection parameter. ```C# // We have to call this method public void SomeMethod(IReadOnlyCollection userNames) { ... } ``` -------------------------------- ### EF Core Performance Tip for FileUrn Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Shows how to configure FileUrn for EF Core to bypass string parsing on database reads by providing a private constructor. ```csharp [ObjectFactory(UseForSerialization = SerializationFrameworks.All, HasCorrespondingConstructor = true)] public partial class FileUrn { // Used by EF Core to skip Validate on read private FileUrn(string value) { var parts = value.Split(':', 2); FileStore = parts[0]; Urn = parts[1]; } // ... } ``` -------------------------------- ### Apply JurisdictionJsonConverter to Model Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Example of applying the custom converter to a class using the JsonConverter attribute. ```csharp // Use JsonConverterAttribute or add the converter to JsonSerializerOptions [Union] [JsonConverter(typeof(JurisdictionJsonConverter))] public abstract partial class Jurisdiction { ... } ----------------- var json = JsonSerializer.Serialize(district); Console.WriteLine(json); // {"$type":"District","value":"District 42"} var deserializedJurisdiction = JsonSerializer.Deserialize(json); // Deserialized jurisdiction: District 42 (District) Console.WriteLine($"Deserialized jurisdiction: {deserializedJurisdiction} ({deserializedJurisdiction?.GetType().Name})"); ``` -------------------------------- ### Execute Switch and Map operations Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Demonstrates basic usage of Switch for side effects or transformations, and Map for direct value mapping. ```csharp ProductType productType = ProductType.Electronics; // Execute different actions based on the enum value (void return) productType.Switch( electronics: () => Console.WriteLine("Processing electronics order"), clothing: () => Console.WriteLine("Processing clothing order") ); // Transform enum values into different types string department = productType.Switch( electronics: () => "Consumer Technology", clothing: () => "Fashion and Apparel" ); // Direct mapping to values - clean and concise decimal discount = productType.Map( electronics: 0.05m, // 5% off electronics clothing: 0.10m // 10% off clothing ); ``` -------------------------------- ### Default OpenAPI Schema Representation Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Framework-Integration Example of the default JSON schema output for a string-based Smart Enum. ```json "schemas": { "ProductType": { "enum": [ "Books", "Electronics", ... ], "type": "string" } } ``` -------------------------------- ### Usage of CurrencyAmount Arithmetic Operators Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates how to use the overloaded arithmetic operators for `CurrencyAmount` to perform addition, subtraction, and multiplication with currency consistency. ```csharp var price = CurrencyAmount.Create(19.99m, CurrencyCode.EUR); var tax = CurrencyAmount.Create(3.80m, CurrencyCode.EUR); CurrencyAmount total = price + tax; // 23.79 EUR CurrencyAmount change = total - price; // 3.80 EUR CurrencyAmount doubled = price * 2; // 39.98 EUR ``` -------------------------------- ### Entity Framework Core Integration with OpenEndDate Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates how to use `OpenEndDate` as a property in an Entity Framework Core entity and how to query entities based on this value object. ```csharp // Entity public class Product { ... public OpenEndDate EndDate { get; set; } } // query var today = OpenEndDate.Create(DateTime.Today); var products = await ctx.Products .Where(p => p.EndDate >= today) .ToListAsync(); ``` -------------------------------- ### Validate Numeric Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Enforces range constraints on a numeric value object. This example ensures that the amount is not negative. ```csharp [ValueObject] public partial struct Amount { static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref decimal value) { if (value < 0) { validationError = new ValidationError("Amount cannot be negative"); return; } } } ``` -------------------------------- ### GET /notification/channels Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Retrieves a list of all available notification channels. The response is an array of strings, where each string is the key of a notification channel. ```APIDOC ## GET /notification/channels ### Description Retrieves a list of all available notification channels. ### Method GET ### Endpoint /notification/channels ### Response #### Success Response (200) - **channels** (string[]) - An array of available notification channel keys (e.g., ["email", "sms"]) ### Response Example ```json [ "email", "sms" ] ``` ``` -------------------------------- ### Basic Regular Union with Classes Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions Illustrates a basic discriminated union using inheritance with classes. Pattern matching with the Switch method is shown for handling different subtypes. ```csharp [Union] public partial class Animal { public sealed class Dog : Animal { public string Name { get; } public Dog(string name) => Name = name; } public sealed class Cat : Animal { public string Name { get; } public Cat(string name) => Name = name; } } // Usage Animal animal = new Animal.Dog("Rover"); // Pattern matching animal.Switch( dog: d => Console.WriteLine($"Dog: {d.Name}"), cat: c => Console.WriteLine($"Cat: {c.Name}") ); ``` -------------------------------- ### Create Single-Item Collections Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/SingleItem Demonstrates the creation of single-item collections using SingleItem.Dictionary, SingleItem.Lookup, and SingleItem.Set. These methods are useful for creating collections with minimal overhead when only one item is needed. ```C# IReadOnlyDictionary dictionary = SingleItem.Dictionary(42, "name"); ILookup lookup = SingleItem.Lookup(42, new[] { "name", "other name" }); IReadOnlySet set = SingleItem.Set("name"); ``` -------------------------------- ### Usage of Money Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates how to create and use the Money Value Object for monetary calculations, including creation with different rounding strategies and arithmetic operations. ```csharp // Creating monetary amounts var price = Money.Create(19.999m, MoneyRoundingStrategy.Down); // 19.99 (rounds down) var roundedUp = Money.Create(19.991m, MoneyRoundingStrategy.Up); // 20.00 (rounds up) var zero = Money.Zero; // 0.00 // Arithmetic operations Money sum = price + roundedUp; // 39.99 Money difference = roundedUp - price; // 0.01 Money doubled = price * 2; // 39.98 (multiplication with int) Money tripled = 3 * price; // 59.97 (multiplication with int) // Division or multiplication with decimal need special handling var multiplicationResult = Money.Create(price * 1.234m); // 24.66766 => 24.67 (uses MoneyRoundingStrategy.Default) // Comparison Console.WriteLine($"roundedUp > price: {roundedUp > price}"); // true ``` -------------------------------- ### Querying with DayMonth in Entity Framework Core Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates querying a database using a DayMonth value object with Entity Framework Core and the generated SQL. ```csharp var deliveryDate = DayMonth.Create(1, 15); var products = await ctx.Products .Where(p => p.ScheduledDeliveryDate == deliveryDate) .ToListAsync(); ``` ```sql SELECT [p].[Id], [p].[ScheduledDeliveryDate], -- other columns FROM [Products] AS [p] WHERE [p].[ScheduledDeliveryDate] = @__deliveryDate_0 ``` -------------------------------- ### Configure ThinktectureModelBinderProvider Options Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Framework-Integration Customize model binding behavior by passing options to the provider constructor. ```csharp // Configure with custom options var provider = new ThinktectureModelBinderProvider( skipBindingFromBody: true // Default: true ); ``` -------------------------------- ### Implement ISpanParsable for Value Objects Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Demonstrates the automatic implementation of ISpanParsable for value objects with numeric, DateTime, and Guid key types, enabling zero-allocation parsing. ```csharp [ValueObject] public readonly partial struct CustomerId { } // ISpanParsable implementation // Zero-allocation parsing for high-performance scenarios ReadOnlySpan span = "12345".AsSpan(); bool success = CustomerId.TryParse(span, null, out CustomerId? id); // Works with numeric types (int, long, decimal, etc.) [ValueObject] public readonly partial struct Amount { } ReadOnlySpan amountSpan = "99.99".AsSpan(); Amount? amount = Amount.Parse(amountSpan, null); // Works with DateTime, Guid, and other ISpanParsable types [ValueObject] public readonly partial struct OrderDate { } [ValueObject] public readonly partial struct ProductId { } ``` -------------------------------- ### Value Object Type Conversion and Equality Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates implicit and explicit type conversions, equality comparisons (==, !=), and comparison operations (>, CompareTo) for value objects. Also shows GetHashCode and ToString implementations. ```csharp // Implicit conversion to key type decimal amountValue = Amount.Create(25.0m); // Returns 25.0m // Explicit conversion from key type (calls Amount.Create(25.0m) internally) var amount = (Amount)25.0m; var amount10 = Amount.Create(10m); var amount20 = Amount.Create(20m); var anotherAmount20 = Amount.Create(20m); var amount25 = Amount.Create(25m); // Equality comparison bool equal = amount20 == anotherAmount20; // true bool notEqual = amount20 != amount25; // true // Comparison (if supported by underlying type) bool isHigher = amount20 > amount10; // true int comparison = amount10.CompareTo(amount20); // GetHashCode - delegates to the key using the configured equality comparer // (default: OrdinalIgnoreCase for strings, default comparer for other types) int hashCode = ProductName.Create("Widget").GetHashCode(); // ToString - delegates to the key's ToString() string key = ProductName.Create("Widget").ToString(); // Returns "Widget" ``` -------------------------------- ### Customizing String Comparison Behavior Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Customize the string comparison behavior for complex value objects by setting the DefaultStringComparison property. This example uses CurrentCulture for string comparisons. ```csharp [ComplexValueObject(DefaultStringComparison = StringComparison.CurrentCulture)] public partial class MyValueObject { public string Property1 { get; } public string Property2 { get; } } ``` -------------------------------- ### Value Object Creation and Validation Methods Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Illustrates various methods for creating and validating value objects, including Create (throws ValidationException), TryCreate (returns boolean), and Validate (returns ValidationError). ```csharp // Using Create (throws ValidationException if invalid) var name = ProductName.Create("Widget"); // Success var amount = Amount.Create(100.50m); // Success // Using TryCreate (returns false if invalid) if (ProductName.TryCreate("ab", out var shortName)) { // Won't reach here - validation fails (too short) } // Using TryCreate with validation error details if (!ProductName.TryCreate("ab", out var name, out var error)) { Console.WriteLine(error.ToString()); // "Product name must be at least 3 characters" } // Using Validate (returns ValidationError if invalid) ValidationError? error = Amount.Validate(-50m, null, out Amount? invalidAmount); if (error is not null) { Console.WriteLine(error.ToString()); // "Amount cannot be negative" } ``` -------------------------------- ### Register EF Core Value Converters Globally Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Framework-Integration Globally registers Thinktecture value converters for Entity Framework Core using DbContextOptionsBuilder. Ensure Thinktecture.Runtime.Extensions.EntityFrameworkCoreX package is installed. ```csharp services.AddDbContext(builder => builder .UseSqlServer(connectionString) .UseThinktectureValueConverters()); ``` -------------------------------- ### Configure All Arithmetic Operators Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Configure all arithmetic operators (+, -, *, /) using `OperatorsGeneration.DefaultWithKeyTypeOverloads` to enable standard operators and key type overloads. ```csharp // Configure all arithmetic operators [ValueObject( // Enable key type overloads (e.g., Amount + decimal) AdditionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, // + SubtractionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, // - MultiplyOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads, // * DivisionOperators = OperatorsGeneration.DefaultWithKeyTypeOverloads)] // / public readonly partial struct Amount { } ``` -------------------------------- ### Defining a Complex Value Object with Behavior Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Defines a complex value object 'TimePeriod' with start and end dates, including validation for the date range and encapsulated behavior methods. ```csharp [ComplexValueObject] public partial class TimePeriod { public DateTime Start { get; } public DateTime End { get; } static partial void ValidateFactoryArguments( ref ValidationError? validationError, ref DateTime start, ref DateTime end) { if (end < start) validationError = new ValidationError("End date cannot be before start date."); } // Domain-specific behavior is encapsulated public TimeSpan Duration => End - Start; public bool Contains(DateTime date) => date >= Start && date <= End; public bool Overlaps(TimePeriod other) => Start < other.End && other.Start < End; } ``` -------------------------------- ### Usage of PartiallyKnownDate Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions Demonstrates creating instances of PartiallyKnownDate with different precision levels and formatting them using the Switch method. Shows implicit conversion from DateOnly. ```csharp // Date with only year known var historicalEvent = new PartiallyKnownDate.YearOnly(2000); // Date with year and month known var approximateBirthdate = new PartiallyKnownDate.YearMonth(1980, 6); // Fully known date var preciseDate = new PartiallyKnownDate.Date(2024, 12, 31); // Implicit conversion from DateOnly to PartiallyKnownDate PartiallyKnownDate fullDate = new DateOnly(2024, 3, 15); // Date(2024, 3, 15) static string FormatDate(PartiallyKnownDate date) { return date.Switch( yearOnly: y => y.Year.ToString(), yearMonth: ym => $"{ym.Year}-{ym.Month:D2}", date: ymd => $"{ymd.Year}-{ymd.Month:D2}-{ymd.Day:D2}" ); } Console.WriteLine($"Historical event: {FormatDate(historicalEvent)}"); // "2000" Console.WriteLine($"Approximate birthdate: {FormatDate(approximateBirthdate)}"); // "1980-06" Console.WriteLine($"Precise date: {FormatDate(preciseDate)}"); // "2024-12-31" ``` -------------------------------- ### Process Order with ShippingMethod Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Demonstrates using the ShippingMethod Smart Enum within an order processing service to calculate costs and delivery dates. ```csharp public class OrderProcessor(TimeProvider timeProvider) { public OrderSummary ProcessOrder(Order order, ShippingMethod shipping) { var shippingCost = shipping.CalculatePrice(order.Weight); var deliveryDate = timeProvider.GetLocalNow().AddDays(shipping.EstimatedDays); return new OrderSummary { OrderTotal = order.SubTotal + shippingCost, EstimatedDelivery = deliveryDate, RequiresSignature = shipping.RequiresSignature, ShippingCost = shippingCost }; } } ``` -------------------------------- ### Register Value Converters with Default Configuration Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Framework-Integration Use this extension method for default configuration, which enables automatic max length calculation for string-based smart enums. This is the recommended approach for global registration. ```csharp services .AddDbContext(builder => builder .UseSqlServer(connectionString) .UseThinktectureValueConverters()) ``` -------------------------------- ### Smart Enums with Generic Derived Types Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Support generic derived types for Smart Enums, enabling type-safe specializations. The example shows different generic types for `Operator`. ```csharp [SmartEnum] public partial class Operator { public static readonly Operator Item1 = new("Operator 1"); public static readonly Operator Item2 = new GenericOperator("Operator 2"); public static readonly Operator Item3 = new GenericOperator("Operator 3"); public static readonly Operator Item4 = new GenericOperator("Operator 4"); private sealed class GenericOperator : Operator { public GenericOperator(string key) : base(key) { } } } ``` -------------------------------- ### Define Notification Service Interface and Implementations Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Create the base interface and concrete implementations for different notification channels. ```csharp public interface INotificationSender { Task SendAsync(string message); } public class EmailNotificationSender(ILogger logger) : INotificationSender { public Task SendAsync(string message) { logger.LogInformation("Sending email: {Message}", message); return Task.CompletedTask; } } public class SmsNotificationSender(ILogger logger) : INotificationSender { public Task SendAsync(string message) { logger.LogInformation("Sending sms: {Message}", message); return Task.CompletedTask; } } ``` -------------------------------- ### Entity Framework Core Integration for DayMonth Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Shows how to use the DayMonth value object as a property in an Entity Framework Core entity and the resulting SQL schema. ```csharp public class Product { public int Id { get; set; } public DayMonth ScheduledDeliveryDate { get; set; } } ``` ```sql CREATE TABLE [Products] ( [Id] int NOT NULL, [ScheduledDeliveryDate] date NOT NULL, -- other columns... ); ``` -------------------------------- ### Equality Comparison Using MemberEqualityComparerAttribute Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Use MemberEqualityComparerAttribute to specify which members are used for equality comparison and hash code computation. The example shows comparison using only the 'Lower' member. ```csharp [ComplexValueObject] public sealed partial class Boundary { // The equality comparison uses `Lower` only! [MemberEqualityComparer, decimal>] public decimal Lower { get; } public decimal Upper { get; } } ``` -------------------------------- ### Usage of Message Processor with Union Result Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions Demonstrates how to use a smart enum message processor and handle its union result exhaustively at compile time using Switch. ```csharp var processorType = MessageProcessorType.Email; ProcessingResult result = processorType.Process("Your order shipped!"); // Handle result — compile-time exhaustive result.Switch( success: success => Handle(success.TransactionId, success.Timestamp), failure: failure => Handle(failure.ErrorMessage) ); ``` -------------------------------- ### Quick Start: Complex Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Define a complex value object with multiple properties representing a single concept. The source generator provides factory methods and equality operators. ```csharp // Multiple properties representing a single concept [ComplexValueObject] public partial class Boundary { public decimal Lower { get; } public decimal Upper { get; } } ``` -------------------------------- ### Usage of DayMonth Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Demonstrates creating, accessing components, converting from DateOnly, and comparing DayMonth instances. ```csharp // Creating DayMonth instances var birthday = DayMonth.Create(5, 15); // May 15th var leapDay = DayMonth.Create(2, 29); // February 29th (works because we use leap year 2000) // Accessing components var day = birthday.Day; // 15 var month = birthday.Month; // 5 var date = new DateOnly(2020, 5, 15); DayMonth dayMonthFromDate = date; // implicit conversion Console.WriteLine($"DayMonth from DateOnly: {dayMonthFromDate}"); // May 15th // Comparing dates var sameDay = DayMonth.Create(5, 15); Console.WriteLine($"birthday == sameDay: {birthday == sameDay}"); // true Console.WriteLine($"birthday < DayMonth.Create(6, 1): {birthday < DayMonth.Create(6, 1)}"); // true ``` -------------------------------- ### Configure Object Factory for Framework Integration Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Configure ObjectFactoryAttribute with flags like UseForSerialization, UseForModelBinding, and UseWithEntityFramework to control its integration with various frameworks. ```csharp [ComplexValueObject] [ObjectFactory( UseForSerialization = SerializationFrameworks.All, // JSON, MessagePack serialization UseForModelBinding = true, // ASP.NET Core model binding UseWithEntityFramework = true)] // EF Core value conversion public partial class Boundary { // ... } ``` -------------------------------- ### Disable Inbound Conversions for Union Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization Disables all conversion operators from member values to the union type by setting `ConversionFromValue` to `ConversionOperatorsGeneration.None`. This example also sets a private constructor access modifier. ```csharp [Union( ConversionFromValue = ConversionOperatorsGeneration.None, ConstructorAccessModifier = UnionConstructorAccessModifier.Private)] public partial class TextOrNumberExtended { public required string AdditionalProperty { get; init; } public TextOrNumberExtended(string text, string additionalProperty) : this(text) { AdditionalProperty = additionalProperty; } public TextOrNumberExtended(int number, string additionalProperty) : this(number) { AdditionalProperty = additionalProperty; } } ``` -------------------------------- ### Value Object with String Conversion Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Object-Factories Example of a Complex Value Object that uses ObjectFactory to parse a formatted string into its components. The Validate method handles string parsing and validation. ```csharp [ComplexValueObject] [ObjectFactory] public partial class Boundary { public decimal Lower { get; } public decimal Upper { get; } public static ValidationError? Validate( string? value, IFormatProvider? provider, out Boundary? item) { item = null; if (value is null) return null; var parts = value.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) return new ValidationError("Invalid format. Expected 'lower:upper', e.g. '1.5:2.5'"); if (!decimal.TryParse(parts[0], provider, out var lower) || !decimal.TryParse(parts[1], provider, out var upper)) return new ValidationError("Invalid numbers. Expected decimal values, e.g. '1.5:2.5'"); return Validate(lower, upper, out item); } } ``` -------------------------------- ### Configure Source Generator Logging Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Source-Generator-Configuration Set the log file path, log level, and uniqueness for log files. Ensure the specified log directory exists. Log levels include Trace, Debug, Information (default), Warning, and Error. ```xml ... C:\temp\samples_logs.txt information false ``` -------------------------------- ### ASP.NET Core Minimal API Integration with OpenEndDate Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Illustrates how to use `OpenEndDate` with ASP.NET Core minimal APIs, allowing it to be directly mapped from route parameters or request bodies. ```csharp // Minimal api var app = builder.Build(); var routeGroup = app.MapGroup("/api"); routeGroup.MapGet("enddate/{date}", (OpenEndDate date) => date); routeGroup.MapPost("enddate", ([FromBody] OpenEndDate date) => date); ``` -------------------------------- ### Custom Key Member Comparer for Value Objects Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects-Customization Use KeyMemberComparerAttribute on simple value objects to specify a custom comparer for ordering. This example uses a StringOrdinal comparer for a ProductName value object. ```csharp [ValueObject] [KeyMemberComparer] public sealed partial class ProductName { } ``` -------------------------------- ### Validate Complex Value Object Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Performs cross-field validation and normalization for complex value objects. This example ensures the lower boundary is less than or equal to the upper boundary and rounds them to two decimal places. ```csharp [ComplexValueObject] public partial class Boundary { public decimal Lower { get; } public decimal Upper { get; } static partial void ValidateFactoryArguments( ref ValidationError? validationError, ref decimal lower, ref decimal upper) { if (lower > upper) { validationError = new ValidationError( $"Lower boundary '{lower}' must be less than or equal to upper boundary '{upper}'"); return; } lower = Math.Round(lower, 2); upper = Math.Round(upper, 2); } } ``` -------------------------------- ### Retrieving an Item by Key using Abstract Static Member Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums Utilize the generic T.Get or T.TryGet methods to fetch a specific Smart Enum instance based on its key. Ensure T implements ISmartEnum. ```csharp // Use T.Get/TryGet/Validate to get the item for provided key. Get("Electronics"); private static void Get(TKey key) where T : ISmartEnum where TKey : notnull { T item = T.Get(key); Console.WriteLine($"Key '{key}' => '{item}'"); } ``` -------------------------------- ### Simple Mode Name Conflict Example Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization Demonstrates a compilation error in 'Simple' mode due to duplicate type names ('NotFound') within nested unions, leading to a conflicting parameter name 'notFound'. ```csharp [Union(NestedUnionParameterNames = NestedUnionParameterNameGeneration.Simple)] public partial class Response { public abstract partial class ErrorA : Response { private ErrorA() { } public sealed class NotFound : ErrorA; // Parameter: notFound } public abstract partial class ErrorB : Response { private ErrorB() { } public sealed class NotFound : ErrorB; // Parameter: notFound (CONFLICT!) } } ``` -------------------------------- ### Usage of Key Type Comparison Operators Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Customization Demonstrates how to use the generated operators for comparing Smart Enums with their key types. The `DefaultWithKeyTypeOverloads` setting enables direct comparisons like `Priority > int` and `int > Priority`. ```csharp // Default: compare two Smart Enum instances bool result = Priority.High > Priority.Low; // true // DefaultWithKeyTypeOverloads: compare directly with key type bool isAbove = Priority.High > 1; // true (compares Key 3 > 1) bool isBelow = 1 < Priority.Medium; // true (compares 1 < Key 2) ``` -------------------------------- ### Discriminated Union with Separate Backing Fields (Default) Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization When a union has only one reference type or no reference types, each member type gets its own strongly-typed backing field. This is the default behavior and requires no casting at runtime. ```csharp [Union] public partial class TextOrNumber; // Generated fields: string _string; int _int32; ``` -------------------------------- ### Generic Key Types for Value Objects Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Value-Objects Illustrates how to define Value Objects with generic key types using placeholder types like TypeParamRef1. This supports both class and struct Value Objects and adapts generated APIs based on type parameter constraints. ```csharp using Thinktecture; // Class-based generic Value Object [ValueObject] public partial class Identifier where T : notnull; // Struct-based generic Value Object [ValueObject] public readonly partial struct Amount where T : notnull; // Usage var id = Identifier.Create(Guid.NewGuid()); var amount = Amount.Create(42.5m); // Validation works as expected if (Identifier.TryCreate(0, out var identifier, out var error)) { Console.WriteLine(identifier); } ``` -------------------------------- ### Implement state-specific behavior with abstract methods Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions Define abstract methods on the base record and implement them in each derived state record to encapsulate inherent state behavior. ```csharp [Union] public abstract partial record OrderState { // Behavior specific to the state public abstract bool CanCancel(); public sealed record Pending(DateTime CreatedAt) : OrderState { public override bool CanCancel() => true; // Can cancel while pending } public sealed record Shipped(DateTime ShippedAt, string TrackingNumber) : OrderState { public override bool CanCancel() => false; // Cannot cancel once shipped } // more states ... } // Usage OrderState currentState = /* get state */; if (currentState.CanCancel()) { // Proceed with cancellation } ``` -------------------------------- ### Factory Method Generation Triggered by Type Parameter Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization Factory methods are generated for all members when a type parameter is used, as conversion operators cannot be generated for type parameters. This example shows generation for a type parameter and a concrete type. ```csharp // T is a type parameter, which triggers factory methods for ALL members [Union] public partial struct Result; // Generated factory methods: // Result.CreateT(T value) — for the type parameter member // Result.CreateString(string value) — for the concrete type member (also has implicit conversion) // Usage Result success = Result.CreateT(42); // factory method (no implicit conversion for type params) Result error = "Something failed"; // implicit conversion still works for string Result alsoError = Result.CreateString("err"); // factory method also available ``` -------------------------------- ### Implement IParsable and ISpanParsable Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Customization Smart Enums automatically implement `IParsable` and `ISpanParsable` based on the key type. `IParsable` is available when the key type implements `IParsable` or is `string`. `ISpanParsable` is available when the key type implements `ISpanParsable` for zero-allocation parsing. ```csharp [SmartEnum] public partial class Priority { public static readonly Priority Low = new(1); public static readonly Priority Medium = new(2); public static readonly Priority High = new(3); } // ISpanParsable implementation // Zero-allocation parsing for high-performance scenarios ReadOnlySpan span = "1".AsSpan(); bool success = Priority.TryParse(span, null, out Priority? priority); // priority == Low // Works with numeric types (int, long, decimal, etc.) [SmartEnum] public partial class PriceCategory { public static readonly PriceCategory Budget = new(9.99m); public static readonly PriceCategory Premium = new(99.99m); } ReadOnlySpan priceSpan = "9.99".AsSpan(); PriceCategory? category = PriceCategory.Parse(priceSpan, null); // Budget // Works with DateTime, Guid, and other ISpanParsable types [SmartEnum] public partial class SpecialDate { public static readonly SpecialDate NewYear = new(new DateTime(2024, 1, 1)); } [SmartEnum] public partial class EntityType { public static readonly EntityType Customer = new(Guid.Parse("11111111-1111-1111-1111-111111111111")); } ``` -------------------------------- ### Real-world API Response with Stateless Errors Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Discriminated-Unions-Customization This example shows a discriminated union for API responses where error types (NotFoundError, UnauthorizedError) are marked as stateless. This reduces memory overhead as these states don't require backing fields. ```csharp [Union( T1Name = "Success", T2Name = "NotFound", T2IsStateless = true, T3Name = "Unauthorized", T3IsStateless = true)] public partial class ApiResponse { public class SuccessResponse { public required string Data { get; init; } } public readonly record struct NotFoundError; // Stateless: presence of this type IS the information public readonly record struct UnauthorizedError; // Stateless: presence of this type IS the information } // Memory efficient: NotFound and Unauthorized don't allocate backing fields ApiResponse response = new ApiResponse.NotFoundError(); response.Switch( success: s => Console.WriteLine($"Data: {s.Data}"), notFound: _ => Console.WriteLine("Resource not found"), unauthorized: _ => Console.WriteLine("Not authorized") ); ``` -------------------------------- ### Register Thinktecture OpenAPI Filters Source: https://github.com/pawelgerr/thinktecture.runtime.extensions/wiki/Smart-Enums-Framework-Integration Enable OpenAPI documentation support for Smart Enums by registering the filters in the service collection. ```csharp services.AddEndpointsApiExplorer() .AddSwaggerGen(options => options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" })) .AddThinktectureOpenApiFilters(); ```