Troubleshooting & FAQ
Common Issues
How do I serialize ResponseType as a string instead of a number?
Cause: ResponseType is a plain enum with no built-in converter. System.Text.Json therefore writes its numeric value (ResponseType.NotFound → 8) by default.
Fix: Register JsonStringEnumConverter on the serializer your endpoints use:
using System.Text.Json.Serialization;
// Minimal APIs and Results.Json (what RA.Utilities.Api uses)
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". Note that the converter is stricter than the old ResponseTypeJsonConverter: unknown names and null for a non-nullable enum throw JsonException instead of falling back to ResponseType.Error.
See ResponseType for the full behavior matrix.
Can I use ResponseType in a switch expression?
Yes — fully. ResponseType is a standard enum, so constant patterns work and the compiler can verify exhaustiveness:
// ✅ Constant patterns (preferred)
var label = responseType switch
{
ResponseType.Success => "OK",
ResponseType.NotFound => "Not Found",
ResponseType.Conflict => "Conflict",
ResponseType.BadRequest => "Bad Request",
ResponseType.Unauthorized => "Unauthorized",
_ => "Other"
};
How do I compare two ResponseType values?
Use == or .Equals() — enum members are value types, so comparison is simple and allocation-free:
ResponseType a = ResponseType.NotFound;
ResponseType b = ResponseType.NotFound;
bool same = a == b; // true
bool sameEq = a.Equals(b); // true
Can I create a ResponseType from a string at runtime?
Yes, with the standard System.Enum APIs:
// ✅ Parse a member name
ResponseType type = Enum.Parse<ResponseType>("NotFound", ignoreCase: true);
// ✅ Safer for untrusted input — numeric strings parse to undefined values
if (Enum.TryParse(value, ignoreCase: true, out ResponseType parsed) && Enum.IsDefined(parsed))
{
// safe to use parsed
}
Enum.TryParse succeeds for any name, including one that is not a ResponseType member, and for numeric strings. Always combine it with Enum.IsDefined when the input is untrusted.
Why can't I use BaseResponseCode values as default parameter values?
Cause: Since v10.0.4 the fields are static readonly, not const. C# requires a compile-time constant for a default parameter value, so void Fail(int code = BaseResponseCode.BadRequest) fails with CS1736.
Fix: Use a nullable sentinel plus a coalesce, or add an overload:
// ✅ Nullable sentinel
public IActionResult Fail(string? message = null)
{
return BadRequest(message ?? BaseResponseMessages.BadRequest);
}
// ✅ Overload
public IActionResult Fail() => Fail(BaseResponseMessages.BadRequest);
public IActionResult Fail(string message) => BadRequest(message);
Why did private const string X = BaseResponseMessages.NotFound; stop compiling?
Cause: A const initializer must itself be a compile-time constant. The package fields are now static readonly, so the compiler reports CS0133.
Fix: Make your declaration static readonly:
// ❌ CS0133
private const string NotFoundMessage = BaseResponseMessages.NotFound;
// ✅
private static readonly string NotFoundMessage = BaseResponseMessages.NotFound;
Why can't I use these values in attributes or case labels?
Cause: Attribute arguments and case labels also require compile-time constants. Referencing a static readonly field produces CS0182 (attribute argument) or CS0150 (case label).
Fix: Use a local const or a literal in those positions, or replace the case with an if:
// Attribute — define the literal in your own project
private const string NotFoundMessage = "The requested resource was not found.";
[DefaultValue(NotFoundMessage)]
public string? Message { get; set; }
// case label -> if
if (error.ErrorCode == BaseErrorCode.EmailRequired)
{
ShowEmailError();
}
These are the only source-breaking contexts. Ordinary reads such as BaseResponseCode.NotFound compile unchanged.
Which IBAN error code should I use?
Use the code that matches the check that failed:
| Check | Error code | Message |
|---|---|---|
| Missing or whitespace input | BaseErrorCode.IbanRequired | BaseErrorMessage.IbanRequired |
| Structure: country code, check digits, or 13–34 characters | BaseErrorCode.IbanNotValid | BaseErrorMessage.IbanNotValid |
| ISO 7064 mod-97 check digits | BaseErrorCode.IbanCheckDigits | BaseErrorMessage.IbanCheckDigits |
| Country-specific total length | BaseErrorCode.IbanLength | BaseErrorMessage.IbanLength |
using RA.Utilities.Core.Constants;
using RA.Utilities.Core.Exceptions;
throw new BadRequestException(new ValidationError(BaseErrorMessage.IbanNotValid)
{
PropertyName = "IBAN",
ErrorCode = BaseErrorCode.IbanNotValid,
AttemptedValue = normalizedIban,
ExpectedValue = "2a2n[9-30c] in 2 to 7 groups of 3 to 5 characters"
});
The IBAN value object picks the first three automatically. IbanLength is for your own country-registry checks; see below.
When should I use IBAN_LENGTH instead of NOT_VALID_IBAN?
Cause: RA.Utilities.Core.ValueObjects.IBAN performs structural validation only — a two-letter country code, two check digits, and a 9–30 character BBAN (13–34 characters in total) — followed by the ISO 7064 mod-97 test. It does not consult a per-country length table, so an IBAN that is structurally valid but the wrong length for its country (for example, 20 characters for DE, which expects 22) passes.
Fix: If your domain requires country-specific lengths, validate them separately and report BaseErrorCode.IbanLength:
// DE expects 22 characters in total
if (compactIban.StartsWith("DE", StringComparison.Ordinal) && compactIban.Length != 22)
{
throw new BadRequestException(new ValidationError(BaseErrorMessage.IbanLength)
{
PropertyName = "IBAN",
ErrorCode = BaseErrorCode.IbanLength,
AttemptedValue = compactIban.Length,
ExpectedValue = 22
});
}
See Validate an IBAN and pick the right error code for a registry-based helper.
Does BaseResponseCode include Created?
Yes. BaseResponseCode.Created is 201, alongside Success, Accepted, NoContent, and the error codes. Use it when you construct a response manually; ASP.NET Core's Created and CreatedAtAction helpers already set 201, so you usually do not need the field there.
Why can I write new BaseResponseCode()?
Cause: Since v10.0.4 the constant holders are partial class declarations rather than static class declarations, so they have an implicit public parameterless constructor. They hold only static state.
Answer: Don't instantiate them. Access the fields through the type name (BaseResponseCode.NotFound). Instantiating the class compiles but has no purpose.
Can I add my own values to these classes with partial?
No. The partial keyword only permits splitting a type's definition inside the assembly that declares it. BaseResponseCode and the other types are compiled into RA.Utilities.Core.Constants; you cannot add a partial part from your own project. Define your own constants class for domain-specific values.
Known Limitations
- Closed set of values: Consuming projects cannot add new
ResponseTypemembers. For an outcome without a dedicated member (e.g.,BadGateway), use the closest built-in member (typicallyErrororProblem) and communicate the specifics through the error code and message. - Numeric JSON by default: Without
JsonStringEnumConverter,responseTypeis serialized as a number. Register the converter if your clients depend on names such as"NotFound". - Strict string deserialization with the converter:
JsonStringEnumConverterthrowsJsonExceptionfor unknown names and fornullwhen the target enum is non-nullable. Validate first if you need tolerant input. defaultisSuccess:default(ResponseType)and uninitialized fields areResponseType.Success(value0). Use explicit members in models whereSuccessis not the right default.- No implicit conversion from
string: UseEnum.TryParse/Enum.Parseor the built-in members directly. - No compile-time constants: Since v10.0.4 the fields are
static readonly. They cannot be used in attributes, default parameter values,caselabels, orconstdeclarations. - No localization support:
BaseResponseMessagesprovides English-only strings. For multi-language support, use these as fallback defaults and implement your own localization layer. - IBAN validation is structural:
IBANchecks the 13–34 character structure and the ISO 7064 mod-97 check digits, not a per-country length table. ReportBaseErrorCode.IbanLengthfrom your own registry check if your domain needs it.
Performance Tips
- Static field reads are cheap:
BaseResponseCode.NotFoundis a single static field load — no allocation, no computation. - Values are not inlined: Unlike the previous
constfields, the JIT reads the value from the package assembly. The overhead is negligible in request-handling code, but extremely hot loops that compare thousands of values may benefit from a local variable. - Enum members are values:
ResponseType.NotFoundinvolves no allocation, andToString()returns the member name. - Prefer
TryParseoverParse: When the input may be invalid,Enum.TryParseavoids exception overhead on the failure path. - Configure the converter once: Register
JsonStringEnumConverterin startup rather than creatingJsonSerializerOptionsper request.