This page captures business rules enforced by this library. SAIF.NetCore.Common is a foundation-layer utility library, so every rule below is a generic validation, normalization, or guard-clause behavior enforced directly by code in this library — none involve a database, SQL, or an inherited base class from another library.
A required argument passed to Enforce.ArgumentNotNull<T>(value, name) must not be null, and the supplied parameter name must not itself be blank.
Trigger
A caller invokes Enforce.ArgumentNotNull<T>(value, name).
Enforcement
Enforce.ArgumentNotNull<T> throws ArgumentNullException("parameterName") when name is null/empty/whitespace, and ArgumentNullException(name) when value is null.
Outcome
Construction/execution fails immediately with a typed exception; no further guard-clause logic runs.
BR-002 Required string must not be null empty or white space¶
Field
Value
Statement
A required string passed to Enforce.ArgumentNotNullOrEmpty must not be null or empty, and a string passed to Enforce.ArgumentNotNullOrWhiteSpace must not be null, empty, or all white space.
Trigger
A caller invokes either guard method.
Enforcement
Both delegate to Enforce.ArgumentNotNull first: a blank parameterName throws ArgumentNullException("parameterName"), and a null parameter throws ArgumentNullException(parameterName). Only a non-null but empty/whitespace parameter reaches the custom ArgumentException with message "Parameter ({0}) cannot be null or empty." or "Parameter ({0}) cannot be null or white space." respectively, substituting the parameter name.
Outcome
A blank name or null value fails with ArgumentNullException; a non-null empty/whitespace value fails with the custom-message ArgumentException.
BR-005 ValidatedNotNullAttribute is a marker only¶
Field
Value
Statement
ValidatedNotNullAttribute applied to the value parameter of Enforce.ArgumentNotNull is a static-analysis hint only and has no runtime behavior of its own.
Trigger
The compiler or a static analyzer inspects Enforce.ArgumentNotNull's parameter list.
Enforcement
The attribute class is internal, sealed, and empty; it carries no fields, properties, or logic.
Outcome
Analyzers that recognize the ValidatedNotNullAttribute convention suppress null-dereference warnings for the guarded parameter; at runtime the attribute has no effect.*
BR-006 IfNullThrow raises a caller specified exception type¶
Field
Value
Statement
GenericExtensions.IfNullThrow<T,TException>(value, message) throws an instance of the caller-specified exception type, constructed with the supplied message, when value is null.
Trigger
A caller invokes value.IfNullThrow<T,TException>(message) on a null value.
BR-007 ToBoolean recognizes a fixed set of true tokens¶
Field
Value
Statement
StringExtensions.ToBoolean returns true only for the case-insensitive values Y, YES, TRUE, 1, and ON; every other value, including null, returns false.
Trigger
A caller invokes someString.ToBoolean().
Enforcement
StringExtensions.ToBoolean compares the input against the fixed token list case-insensitively.
Outcome
Returns a bool; unrecognized or null input always yields false rather than throwing.
BR-009 Sentinel dates render as an empty SAIF short date string¶
Field
Value
Statement
DateExtensions.ToSaifShortDateString treats DateTime.MinValue and DateTime.MaxValue as "no date" sentinels and renders them as an empty string; any other date renders as MM/dd/yyyy.
Trigger
A caller invokes ToSaifShortDateString on a DateTime/DateTime? value.
Enforcement
DateExtensions.ToSaifShortDateString checks IsMinOrMax before formatting; a null DateTime? is also treated as empty.
Outcome
Returns an empty string for null/min/max; otherwise returns the culture-sensitive MM/dd/yyyy formatted date.
BR-011 DateRange constructors normalize an open end date to MaxValue¶
Field
Value
Statement
A DateRange constructed via its default, begin-only, or begin/end constructors normalizes an end date of DateTime.MinValue to DateTime.MaxValue; the copy constructor does not re-apply this normalization, so copying an existing DateRange whose EndDate was set to DateTime.MinValue (via its public setter, after construction) preserves the un-normalized MinValue end.
Trigger
A consumer constructs a DateRange via any of its four constructors.
Enforcement
The default, begin-only, and begin/end constructors assign EndDate and substitute DateTime.MaxValue when the supplied end is DateTime.MinValue; the copy constructor (DateRange(DateRange dateRange)) copies BeginDate/EndDate directly from its argument with no such substitution.
Outcome
A DateRange built directly from dates always has a well-defined, non-MinValue end date, enabling IsOpenEnded and range-comparison logic downstream; a DateRange built by copying another instance can still carry a MinValue end if the source instance's EndDate was mutated to MinValue after construction.*
BR-012 A date range is valid only when begin does not exceed end¶
Field
Value
Statement
DateRangeExtensions.IsValidRange requires BeginDate <= EndDate; IsOpenEnded is true when begin equals DateTime.MinValue or end equals DateTime.MaxValue; a future range is valid with begin after now, and a past range is valid with end before now.
Trigger
A caller invokes IsValidRange, IsOpenEnded, IsFutureRange, or IsPastRange on a DateRange.
Enforcement
IsFutureRange/IsPastRange first test IsValidRange and return false immediately when the range is invalid; only a valid range proceeds to the corresponding direct date comparison against DateTime.Now. IsValidRange/IsOpenEnded perform their direct date comparison without this prerequisite.
Outcome
Returns a bool; IsFutureRange/IsPastRange results depend on the server's current clock and time zone.*
BR-013 Overlapping ranges require both ranges valid and respect the same date overlap flag¶
Field
Value
Statement
DateRangeExtensions.IsOverlapping(range1, range2, inAllowSameDateOverlap) is intended to return true only when both ranges pass IsValidRange, treating a shared boundary date as overlap unless inAllowSameDateOverlap is true; as implemented, its range2 != null guard throws NullReferenceException before that logic runs, for essentially every call.
Trigger
A caller invokes IsOverlapping with two DateRange instances, null or not.
Enforcement
IsOverlapping first evaluates range2 != null, which invokes DateRange's custom != operator with d1 = range2, d2 = null; the operator body unconditionally evaluates d2.BeginDate, dereferencing the null literal regardless of whether the caller's range2 was itself null, so the guard throws before the intended IsValidRange/IsBetween logic ever runs.*
Outcome
IsOverlapping throws NullReferenceException for essentially every ordinary call with a non-null range2 as well as a null one — it does not reliably return bool as its signature implies; see BR-016 for the same underlying operator defect.*
BR-014 Contiguous-range evaluation has three selectable policies and a short circuit for fewer than two ranges¶
Field
Value
Statement
DateRangeExtensions.IsContiguous(ranges, option) returns true for fewer than 2 ranges, and otherwise requires every consecutive pair (ordered by BeginDate) to satisfy the selected policy: StartMatchesEnd (begin equals previous end), StartIsNextDay (previous end + 1 day equals begin, the default), or AllowGaps (begin is at or after previous end).
Trigger
A caller invokes IsContiguous on an IEnumerable<DateRange>.
Enforcement
IsContiguous orders the input by BeginDate and applies the selected ContiguousOptions/DateRangeExtensions.ContiguousOptions comparison to each consecutive pair.
Outcome
Returns bool; the default policy (StartIsNextDay) requires a strict day-after-day sequence with no gaps or overlaps.
BR-015 In-range tests default to include-begin exclude-end¶
Field
Value
Statement
DateRangeExtensions.IsInRange and DateRangeExtensions.AsOf<T> default to the FirstOnly comparison mode, which includes the range's begin date and excludes its end date.
Trigger
A caller invokes IsInRange or AsOf<T> without specifying an IncludeComparisons value.
Enforcement
Both members default their IncludeComparisons parameter to FirstOnly and delegate to IComparableExtensions.IsBetween.
Outcome
Returns bool (IsInRange) or the deferred IEnumerable<T> sequence of all matching items (AsOf<T>, built from a LINQ Where clause), not just the first match; a null DateTime? input to IsInRange returns false rather than throwing.
BR-016 DateRange equality is defined by equal begin and end dates¶
Field
Value
Statement
Two DateRange instances are equal (via ==, !=, and Equals) only when their BeginDate and EndDate are both equal.
Trigger
A consumer compares two DateRange instances with ==, !=, or calls .Equals(object).
Enforcement
DateRange overrides ==/!=/Equals/GetHashCode to compare both date fields.
Outcome
Both == and != return bool normally when both operands are ordinary non-null DateRange instances, comparing BeginDate/EndDate directly. Neither operator null-checks its operands, so passing an actual null reference to either side throws NullReferenceException; DateRangeExtensions.IsOverlapping's pairwise overlap check (range2 != null) hits this specifically because it calls != with a literal null second argument, and the operator body still dereferences d2.BeginDate unconditionally.
BR-017 A Julian value requires at least four digits and a day of year within the valid range¶
Field
Value
Statement
Julian.IsValid requires the input to contain at least 4 digits, and Julian.ToDate additionally rejects a day-of-year greater than 365 (or 366 in a leap year).
Trigger
A caller invokes Julian.IsValid(value, out message) or Julian.ToDate(value, millenium).
Enforcement
IsValid checks digit count and returns false with the message "The julian must contain at least 4 numbers" on failure; ToDate calls IsValid first, then throws ApplicationException when the day-of-year check fails.
Outcome
IsValid returns bool with an out message; ToDate throws ApplicationException for an invalid or out-of-range value, otherwise returns the computed DateTime.
BR-018 Two digit EDI years resolve to the 2000s and a missing time means midnight¶
Field
Value
Statement
DateMaker.MakeDateTime treats a 2-digit year as being in the 2000s (prefixed with "20"), and a time argument that is omitted defaults to midnight ("000000", right-padded with zeros).
Trigger
A caller invokes DateMaker.MakeDateTime(date, time) with a 6-digit yyMMdd date or an omitted time.
Enforcement
MakeDateTime prefixes a 6-character date string with "20" before parsing, and right-pads time to 6 characters with '0' before building the DateTime.
Outcome
Returns a DateTime for the 2000s year assumption; this is a documented limitation for dates before 2000 or after 2099.*
BR-019 Title-casing preserves a fixed set of organization abbreviations roman numerals and lowercase words¶
Field
Value
Statement
StringExtensions.ToTitleCase keeps organization abbreviations, US state/direction codes, and Roman numerals (for example SAIF, LLC, II, III, NE, PO, USA) uppercase, keeps listed articles and prepositions (is, in, the, an, of, on, and, de) lowercase except as the first character, and always uppercases the result's first character.
Trigger
A caller invokes someString.ToTitleCase().
Enforcement
StringExtensions.ToTitleCase delegates to the internal TitleCase.ToTitleCase engine, which classifies each word token against its fixed word lists before reassembling the string.
Outcome
Returns the title-cased string; a null or empty input is returned unchanged.
BR-020 A validation result is valid only when it has no errors¶
Field
Value
Statement
ValidationResult.IsValid is true only when Errors contains no entries; entries in Messages do not affect validity.
Trigger
A caller reads ValidationResult.IsValid after populating Errors/Messages.
Enforcement
ValidationResult.IsValid is a virtual property implemented as !Errors.Any().
Outcome
Returns bool; SaifValidationException built from a ValidationResult sets no Exception.Message of its own, so the base Exception.Message default is used.
BR-021 A header is stripped only when found strictly after position zero¶
Field
Value
Statement
ByteExtensions.StripHeader only strips a located header and reports found = true when the header's starting position is greater than 0; a header found exactly at position 0, or not found at all, reports found = false and returns the original array unchanged.
Trigger
A caller invokes ByteExtensions.StripHeader(bytes, searchFor, out found, maxBytesToSearch).
Enforcement
StripHeader checks position > 0 after calling Search, so a position == 0 match takes the same found = false branch as a true not-found result.
Outcome
Returns the (possibly unmodified) byte array and the found flag; a header already at the start of the array is reported as not found.
BR-022 A Windows identity name without a backslash yields the same value for user and domain¶
Field
Value
Statement
SecurityPrincipalExtensions.UserId and .Domain split a DOMAIN\user identity name into its domain and user segments; when the name contains no backslash, both members return the whole name unchanged.
Trigger
A caller invokes UserId/Domain on an IIdentity whose Name may or may not contain a backslash.
Enforcement
Both members check for a \ separator in IIdentity.Name and split on it when present, otherwise return the name as-is.
Outcome
Returns a string; callers cannot distinguish a domain-less name from a parse failure, since both return the full name.
BR-023 Blank input converts to decimal zero while other input converts directly or throws¶
Field
Value
Statement
StringExtensions.ConvertToDecimalOrZero returns 0 only when its input string is null, empty, or all white space; for any other input it calls Convert.ToDecimal without a try/catch, so a non-numeric or out-of-range string propagates the resulting exception (for example FormatException or OverflowException) instead of returning 0. NumericExtensions.IsNumeric is a separate, unrelated check that detects numeric strings using 32-bit integer parsing only, not decimal or floating-point parsing.
Trigger
A caller invokes ConvertToDecimalOrZero or IsNumeric on a string.
Enforcement
ConvertToDecimalOrZero checks for null/empty/whitespace and returns 0 on that path only; otherwise it calls Convert.ToDecimal(decimalValue) directly with no exception handling. IsNumeric delegates to int.TryParse.
Outcome
ConvertToDecimalOrZero returns 0 for blank input, a culture-sensitive converted decimal for ordinary numeric input, and otherwise throws; it does not tolerate non-numeric or overflowing input the way its name implies. IsNumeric returns bool, and reports a string that is numeric but out of 32-bit integer range (for example a large decimal) as not numeric, independent of what ConvertToDecimalOrZero would do with the same input.*
BR-024 Duplicate IncludeComparisons enums create an ambiguous reference risk¶
Field
Value
Statement
IncludeComparisons is independently declared in both SAIF.NetCore.Common.Enums and SAIF.NetCore.Common.Extensions (declared in GenericExtensions.cs at namespace scope, not nested within the GenericExtensions class); a consumer that imports both the Enums and Extensions namespaces and references the bare type name gets an ambiguous-reference compile error (CS0104). ContiguousOptions and OverlappingDateOptions in Enums are distinct, separately named types from the nested DateRangeExtensions.ContiguousOptions/DateRangeExtensions.OverlappingOptions — OverlappingDateOptions does not even share a name with OverlappingOptions — so neither pair creates a CS0104 risk; they are simply two incompatible parameter types that happen to model similar policies, and each API accepts only one specific qualified type.
Trigger
A consumer file has using SAIF.NetCore.Common.Enums; and using SAIF.NetCore.Common.Extensions; together and references IncludeComparisons by its bare name.
Enforcement
C# name lookup resolves the enclosing namespace before using directives, so library call sites such as IComparableExtensions.IsBetween bind to the Extensions-namespace IncludeComparisons even where the file also has using SAIF.NetCore.Common.Enums;; a consumer file with both using directives and no namespace qualifier instead gets CS0104 at compile time for IncludeComparisons only.*
Outcome
Library-internal call sites compile unambiguously because they live inside the Extensions namespace; consumer code that imports both namespaces and references bare IncludeComparisons fails to compile. DateRangeExtensions.IsContiguous(IEnumerable<DateRange>, ContiguousOptions) accepts only the nested DateRangeExtensions.ContiguousOptions, and DateRangeExtensions.IsOverLapping(IEnumerable<DateRange>, OverlappingOptions) accepts only the nested DateRangeExtensions.OverlappingOptions; passing the top-level Enums.ContiguousOptions/Enums.OverlappingDateOptions to either method is a compile-time type mismatch, not an ambiguity.*
BR-025 A dead System Xml Schema using directive has no effect on compiled behavior¶
Field
Value
Statement
DateExtensions.cs imports System.Xml.Schema but no member in the file references any type from that namespace, so the using directive has no effect on compiled behavior.
Trigger
The compiler processes DateExtensions.cs.
Enforcement
The using System.Xml.Schema; directive at the top of the file resolves no identifiers used elsewhere in the file.
Outcome
No behavioral effect; the directive is leftover and could be removed without changing compiled output.
BR-026 A missing MVC route segment defaults to Home Index¶
Field
Value
Statement
UriExtensions.ControllerAndActionName defaults the returned route to "Home/Index" when uri is null or has no controller path segment, and otherwise appends a literal "Index" directly after uri.Segments[2] with no separator inserted — when a controller segment exists but no action segment does. Because Uri.Segments retains any trailing slash that is already present on a path segment, the result can still read as "{Controller}/Index" when the controller segment itself ends in / (for example http://h/app/controller/ → "controller/Index"), while a controller segment with no trailing slash produces a run-together result (for example http://h/app/controller → "controllerIndex").
Trigger
A caller invokes ControllerAndActionName on a Uri with fewer than 4 path segments, or a nullUri.
Enforcement
ControllerAndActionName initializes name = "Home/Index", returns that default immediately for a null uri, concatenates uri.Segments[2] and uri.Segments[3] when more than 3 segments exist, and otherwise falls back to string.Format("{0}Index", uri.Segments[2]) when more than 2 segments exist — the format string itself inserts no separator between the controller segment and "Index"; any / in the result comes solely from a trailing slash already present in uri.Segments[2].
Outcome
Returns a string; unlike PageName, ControllerAndActionName does not throw for a null uri and instead returns the "Home/Index" default. The fallback result is "controller/Index" when the matched segment retains its trailing slash, and "controllerIndex" when it does not — so the result is not unconditionally free of a Controller/Index-shaped string.*
Statements marked with * describe a defect, limitation, or culture/clock dependency that was inferred from reading the implementation rather than from an explicit design document; they remain accurate descriptions of observed behavior.
Statements marked with ** could not be confirmed against a concrete consumer or design decision; treat them as unverified until confirmed against source evidence.
BR-013 and BR-016 describe the same underlying NullReferenceException risk from different call sites: DateRangeExtensions.IsOverlapping's own null guard, and the DateRange.==/!= operator overloads it calls into.
Note: Statements marked with * were inferred from code structure, naming
conventions, or contextual evidence where direct confirmation was not available
in source artifacts. Statements marked with ** could not be confirmed; treat
as unknown until verified.