Skip to main content

Migration Guides

v10.0.3 → v10.1.0​

This release is additive: no field, type, value, or constructor signature changed. Four IBAN constants were added to BaseErrorCode and BaseErrorMessage so the new IBAN value object can report failures with the same vocabulary as the other value objects.

What's new​

FieldBaseErrorCodeBaseErrorMessage
IbanRequiredREQUIRED_IBAN"The 'IBAN' field is required."
IbanLengthIBAN_LENGTH"'IBAN' length does not match the expected length for its country."
IbanNotValidNOT_VALID_IBAN"'IBAN' format is invalid."
IbanCheckDigitsIBAN_CHECK_DIGITS"'IBAN' check digits are invalid."

Do I need to change anything?​

No. Upgrade the package; existing code compiles and behaves exactly as before. The new fields are static readonly strings, like every other field since v10.0.4.

Use them when you validate IBANs:

using RA.Utilities.Core.Constants;
using RA.Utilities.Core.Exceptions;

if (string.IsNullOrWhiteSpace(request.Iban))
{
throw new BadRequestException(new ValidationError(BaseErrorMessage.IbanRequired)
{
PropertyName = "IBAN",
ErrorCode = BaseErrorCode.IbanRequired
});
}

IbanLength is not raised by the value object​

RA.Utilities.Core.ValueObjects.IBAN validates the IBAN structure (13–34 characters) and the ISO 7064 mod-97 check digits. It does not consult a per-country length table, so a value that is structurally valid but the wrong length for its country (for example, 20 characters for DE, which expects 22) passes. Use IbanLength in your own country-registry validation; see Validate an IBAN and pick the right error code.


This release contains two breaking changes. The values are unchanged in both cases; what changed is how the compiler and the JSON serializer treat them.

  1. ResponseType was converted from a record back to a plain enum.
  2. Every constant field was migrated from const to static readonly, and the containing classes are no longer static.

Breaking Change 1: ResponseType converted from record back to enum​

In v10.0.4, ResponseType was changed from the type-safe enum pattern record (introduced in v10.0.2) back to a standard C# enum. The set of values is now closed, but everyday usage is simpler.

Why this change?​

  • A plain enum supports constant patterns in switch expressions, Enum.TryParse, and all other standard enum APIs
  • The built-in catalog already covers the standard HTTP outcome categories
  • The underlying values (Success = 0 … GatewayTimeout = 17) are stable and explicit
JSON wire format changed

The old record carried a built-in ResponseTypeJsonConverter that always wrote strings. The new enum has no built-in converter, so System.Text.Json writes numbers ("responseType": 8) by default. If your clients expect "NotFound", register JsonStringEnumConverter — see Serialization.

What changed​

Aspectv10.0.3 (record)v10.0.4 (enum)
Typerecord ResponseTypeenum ResponseType
Valuespublic static readonly fieldsEnum members (Success = 0 … GatewayTimeout = 17)
String valueresponseType.ValueresponseType.ToString()
Implicit conversion to string✅ Yes❌ No — use ToString()
Custom values✅ Via inheritance❌ Not supported — use the closest member
Switch expressions⚠️ Property patterns only ({ Value: "NotFound" } =>)✅ Constant patterns (ResponseType.NotFound =>)
JSON write (default options)Plain strings via ResponseTypeJsonConverterNumbers (NotFound → 8)
JSON write with JsonStringEnumConverterPlain stringsPlain strings ("NotFound")
JSON read (default options)Exact (ordinal) match; empty/whitespace → ErrorNumbers only; a string throws JsonException
JSON read with JsonStringEnumConverterExact (ordinal) matchCase-insensitive names; unknown values throw
defaultnull (reference type)ResponseType.Success (value 0)

Migration Steps​

1. Replace .Value with ToString()​

Before (v10.0.3):

string type = responseType.Value;

After (v10.0.4):

string type = responseType.ToString();

2. Replace construction and parsing​

Before (v10.0.3):

ResponseType type = new("NotFound");

After (v10.0.4):

ResponseType type = Enum.Parse<ResponseType>("NotFound", ignoreCase: true);
// or, for untrusted input:
if (Enum.TryParse(value, ignoreCase: true, out ResponseType type) && Enum.IsDefined(type)) { }

3. Simplify switch expressions​

Before (v10.0.3):

var label = responseType switch
{
{ Value: "NotFound" } => "Not Found",
{ Value: "Conflict" } => "Conflict",
_ => "Unknown"
};

After (v10.0.4):

var label = responseType switch
{
ResponseType.NotFound => "Not Found",
ResponseType.Conflict => "Conflict",
_ => "Unknown"
};

4. Remove custom derived response types​

Custom types inheriting from ResponseType no longer compile. Replace them with the closest built-in member and carry domain specifics in the error code and message:

Before (v10.0.3):

public record PaymentRequiredResponseType : ResponseType
{
private PaymentRequiredResponseType(string value) : base(value) { }
public static readonly PaymentRequiredResponseType Instance = new("PaymentRequired");
}

throw new RaBaseException(402, PaymentRequiredResponseType.Instance, "Premium subscription required.");

After (v10.0.4 – v10.1.0):

// v10.0.4 – v10.1.0 — no dedicated member yet
throw new RaBaseException(402, ResponseType.Error, "Premium subscription required.");

From v10.1.1, ResponseType has a dedicated PaymentRequired member, so new code can use the typed values:

throw new RaBaseException(
BaseResponseCode.PaymentRequired,
ResponseType.PaymentRequired,
BaseResponseMessages.PaymentRequired
);

5. Decide how ResponseType is serialized​

The numeric wire format is the default. To keep the names your clients saw in v10.0.3, register JsonStringEnumConverter on the serializer used by your API:

using System.Text.Json.Serialization;

// Minimal APIs and Results.Json (used by RA.Utilities.Api)
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

// MVC controllers
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

With the converter, ResponseType.NotFound is written as "NotFound"; without it, it is written as 8. Verify the contract your clients rely on:

{
"responseCode": 404,
"responseType": "NotFound",
"responseMessage": "Product with value '99' not found."
}

Behavior of JsonStringEnumConverter differs from the old converter: names are matched case-insensitively, but unknown names (and null for a non-nullable enum) throw JsonException instead of falling back to Error. Validate input before deserializing if you need tolerant behavior.

6. Check default handling​

default(ResponseType) is now Success (0) rather than null. If a model uses ResponseType as an optional property, decide whether Success is a safe default or whether the property should be ResponseType?.


Breaking Change 2: Constant fields migrated from const to static readonly​

Every field in BaseResponseCode, BaseResponseMessages, BaseErrorCode, BaseErrorMessage, and HeaderParameters is now public static readonly instead of public const. The containing types changed from static class to partial class (with CA1052 suppressed).

Why this change?​

  • const values are embedded into every consumer assembly at compile time, so correcting a value requires recompiling every consumer.
  • static readonly values are resolved from the package assembly at runtime. A corrected value ships in a package update and takes effect without recompiling consumers.
  • The classes can now be split across files inside the package using partial.
Consumers cannot extend the partial types

partial only permits splitting a type's definition inside the assembly that declares it. You cannot add a partial part to BaseResponseCode from your own project; the compiled package type cannot be extended across assemblies.

What changed​

Aspectv10.0.3v10.0.4
Declarationpublic const string EmailRequired = "REQUIRED_EMAIL";public static readonly string EmailRequired = "REQUIRED_EMAIL";
Containing typestatic classpartial class (non-static)
Constant-required contexts✅ Allowed❌ Compiler error (CS0133, CS1736, CS0150, CS0182)
Value inliningInlined into consumer ILRead from the package assembly at runtime
Changing a valueRequires recompiling consumersRequires only updating the package
InstantiationNot possiblePossible (new BaseResponseCode()), but pointless
Binary compatibility—✅ Existing compiled consumers keep working
Ordinary reads (BaseResponseCode.NotFound)✅✅ Unchanged

Migration Steps​

1. Change const declarations that reference these fields​

const requires a compile-time constant (CS0133). Use static readonly instead:

Before:

private const string NotFoundMessage = BaseResponseMessages.NotFound;

After:

private static readonly string NotFoundMessage = BaseResponseMessages.NotFound;

2. Replace default parameter values​

Default parameter values must be compile-time constants (CS1736). Use a nullable sentinel plus a coalesce, or add an overload:

Before:

public IActionResult Fail(string message = BaseResponseMessages.BadRequest)
{
return BadRequest(message);
}

After:

public IActionResult Fail(string? message = null)
{
return BadRequest(message ?? BaseResponseMessages.BadRequest);
}

3. Replace case labels​

case labels must be compile-time constants (CS0150). Use if/else, or switch on the literal value when that is clearer:

Before:

switch (error.ErrorCode)
{
case BaseErrorCode.EmailRequired:
ShowEmailError();
break;
}

After:

if (error.ErrorCode == BaseErrorCode.EmailRequired)
{
ShowEmailError();
}

4. Replace attribute arguments​

Attribute arguments must be compile-time constants (CS0182). Define a local const in your own project (duplicating the literal) or inline the literal in the attribute:

Before:

[DefaultValue(BaseResponseMessages.NotFound)]
public string? Message { get; set; }

After:

private const string NotFoundMessage = "The requested resource was not found.";

[DefaultValue(NotFoundMessage)]
public string? Message { get; set; }

5. Rebuild and re-test​

Call sites that only read the fields — by far the most common usage — require no source changes. A rebuild is still required to compile against the new package, and any JSON tests that asserted string output should be reviewed against Breaking Change 1.


v10.0.1 → v10.0.2​

Breaking Change: ResponseType converted from enum to record​

In v10.0.2, ResponseType was changed from a traditional C# enum to a type-safe enum pattern using a record. This enables consuming projects to define custom response types via inheritance, but it requires code changes.

Why this change?​

  • Traditional enum types cannot be extended by consuming projects
  • The record pattern allows custom response types (e.g., PaymentRequired, RateLimited) while retaining strong typing
  • JSON serialization produced plain strings (e.g., "NotFound") via the built-in ResponseTypeJsonConverter

What changed​

Aspectv10.0.1 (enum)v10.0.2 (record)
Typeenum ResponseTyperecord ResponseType
MembersSuccess = 0, NotFound = 3, etc.public static readonly ResponseType Success = new("Success")
Removed members-Database, Unknown
New members-GatewayTimeout
JSONJsonStringEnumConverterResponseTypeJsonConverter
Extensible❌ No✅ Yes, via inheritance
Switch expressions✅ Yes (enum constant patterns)⚠️ Partial (use property patterns: { Value: "NotFound" } =>)
ToString()Returns enum nameReturns Value string

Migration Steps​

1. Replace enum-specific patterns​

Before (v10.0.1):

// Switch expression
var label = responseType switch
{
ResponseType.NotFound => "Not Found",
ResponseType.Conflict => "Conflict",
_ => "Unknown"
};

// Enum parsing
var type = Enum.Parse<ResponseType>("NotFound");

// Default value
ResponseType defaultType = default; // Was ResponseType.Success (0)

After (v10.0.2):

// Use property pattern matching (switch still works on the record itself)
var label = responseType switch
{
{ Value: "NotFound" } => "Not Found",
{ Value: "Conflict" } => "Conflict",
_ => "Unknown"
};

// Or switch on the Value string directly
var label = responseType.Value switch
{
"NotFound" => "Not Found",
"Conflict" => "Conflict",
_ => "Unknown"
};

// Use the static fields directly; parse via the JSON converter or constructor
ResponseType type = new("NotFound");

// No default value — always use an explicit static field
ResponseType defaultType = ResponseType.Error;

2. Remove references to removed members​

Database and Unknown were removed. Replace them:

// Before
ResponseType.Database // Removed

// After
ResponseType.Error // Use Error for general failures
// Before
ResponseType.Unknown // Removed

// After
ResponseType.Error // Use Error instead

3. Update RaBaseException usage​

If you use RA.Utilities.Core.Exceptions v10.0.4+ (which depends on this version), the RaBaseException constructor now accepts ResponseType directly:

// Before (v10.0.3)
throw new RaBaseException("NotFound", "Product not found.", 404);

// After (v10.0.4)
throw new RaBaseException(404, ResponseType.NotFound, "Product not found.");

4. Serialization is unchanged​

JSON output is identical. Custom response types also serialize as plain strings:

{
"responseCode": 404,
"responseType": "NotFound",
"responseMessage": "Product with value '99' not found."
}

No changes needed to API clients.


Full Changelog​

See the package release notes for a complete version history.