IBAN Validation in C# (.NET) — Complete Guide with Examples
IbanNet NuGet package, a zero-dependency manual MOD-97 implementation, ASP.NET Core data annotation integration, and FluentValidation support — all with .NET 8 examples and xUnit tests.
IBAN validation in C# can be implemented with the IbanNet NuGet package for a full-featured approach, a manual MOD-97 implementation for zero-dependency scenarios, or a REST API call for validation with bank metadata. This guide covers all three approaches with .NET 8 examples, ASP.NET Core integration, and a complete test suite.
Validation Requirements: Why a Simple Regex Is Not Enough
A valid IBAN must pass three checks in sequence:
- Format: Starts with a two-letter country code, followed by two numeric check digits and an alphanumeric BBAN segment.
- Length: Each country specifies an exact IBAN length. Germany (DE) is 22 characters; France (FR) is 27; the UK (GB) is 22. A regex can verify format but not country-specific lengths unless you embed all 84 country lengths in the pattern.
- MOD-97: The ISO 13616 check digit algorithm must yield a remainder of 1. This cannot be implemented as a regex — it requires numeric computation.
Approach 1: IbanNet NuGet Package
IbanNet is the most widely used .NET IBAN library. It validates format, length, and MOD-97, supports all 84 IBAN countries, and integrates with ASP.NET Core and FluentValidation.
dotnet add package IbanNet
dotnet add package IbanNet.DataAnnotationsBasic usage:
using IbanNet;
IIbanValidator validator = new IbanValidator();
ValidationResult result = validator.Validate("DE89370400440532013000");
if (result.IsValid)
{
Console.WriteLine($"IBAN is valid");
Console.WriteLine($"Country: {result.Value?.Country.TwoLetterISORegionName}");
}
else
{
Console.WriteLine($"Invalid: {result.Error?.Message}");
}IbanNet provides a structured Iban type you can parse to:
using IbanNet;
if (Iban.TryParse("DE89370400440532013000", out Iban? iban))
{
Console.WriteLine(iban.ToString(IbanFormat.Partitioned));
// DE89 3704 0044 0532 0130 00
Console.WriteLine(iban.Country.DisplayName);
// Germany
Console.WriteLine(iban.BankIdentifier);
// 37040044
}
else
{
Console.WriteLine("Parse failed");
}ASP.NET Core: Data Annotation Validation
IbanNet.DataAnnotations provides a [IbanAttribute] that plugs into ASP.NET Core's model validation pipeline:
using IbanNet.DataAnnotations;
using System.ComponentModel.DataAnnotations;
public class PaymentRequest
{
[Required]
[Iban]
public string Iban { get; set; } = string.Empty;
[Required]
[MaxLength(140)]
public string BeneficiaryName { get; set; } = string.Empty;
[Range(0.01, double.MaxValue)]
public decimal Amount { get; set; }
}[ApiController]
[Route("api/payments")]
public class PaymentController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] PaymentRequest request)
{
if (!ModelState.IsValid)
return ValidationProblem(ModelState);
// request.Iban is structurally valid here
return Ok();
}
}Dependency injection for IbanValidator (recommended for testability):
// Program.cs
builder.Services.AddIbanNet();
// Inject in controller or service:
public class PaymentService(IIbanValidator ibanValidator)
{
public bool ValidateIban(string iban) =>
ibanValidator.Validate(iban).IsValid;
}Approach 2: Manual MOD-97 Implementation
For scenarios requiring zero NuGet dependencies — portable class libraries, Unity, Blazor WASM with size constraints — implement the ISO 13616 algorithm in C#:
using System.Text;
using System.Text.RegularExpressions;
public static class IbanValidator
{
private static readonly Dictionary<string, int> IbanLengths = new()
{
["AD"] = 24, ["AE"] = 23, ["AT"] = 20, ["AZ"] = 28,
["BA"] = 20, ["BE"] = 16, ["BG"] = 22, ["BH"] = 22,
["BR"] = 29, ["BY"] = 28, ["CH"] = 21, ["CR"] = 22,
["CY"] = 28, ["CZ"] = 24, ["DE"] = 22, ["DK"] = 18,
["DO"] = 28, ["EE"] = 20, ["ES"] = 24, ["FI"] = 18,
["FR"] = 27, ["GB"] = 22, ["GE"] = 22, ["GI"] = 23,
["GR"] = 27, ["GT"] = 28, ["HR"] = 21, ["HU"] = 28,
["IE"] = 22, ["IL"] = 23, ["IQ"] = 23, ["IS"] = 26,
["IT"] = 27, ["JO"] = 30, ["KW"] = 30, ["KZ"] = 20,
["LB"] = 28, ["LC"] = 32, ["LI"] = 21, ["LT"] = 20,
["LU"] = 20, ["LV"] = 21, ["MC"] = 27, ["MD"] = 24,
["ME"] = 22, ["MK"] = 19, ["MR"] = 27, ["MT"] = 31,
["MU"] = 30, ["NL"] = 18, ["NO"] = 15, ["PK"] = 24,
["PL"] = 28, ["PS"] = 29, ["PT"] = 25, ["QA"] = 29,
["RO"] = 24, ["RS"] = 22, ["SA"] = 24, ["SC"] = 31,
["SE"] = 24, ["SI"] = 19, ["SK"] = 24, ["SM"] = 27,
["ST"] = 25, ["SV"] = 28, ["TL"] = 23, ["TN"] = 24,
["TR"] = 26, ["UA"] = 29, ["VA"] = 22, ["VG"] = 24,
["XK"] = 20,
};
public static string Normalize(string raw) =>
raw?.Trim().Replace(" ", "").Replace("-", "").ToUpperInvariant()
?? string.Empty;
public static (bool IsValid, string Error) Validate(string raw)
{
string iban = Normalize(raw);
if (!Regex.IsMatch(iban, @"^[A-Z]{2}[0-9]{2}[A-Z0-9]+$"))
return (false, "Invalid format");
string country = iban[..2];
if (!IbanLengths.TryGetValue(country, out int expectedLength))
return (false, $"Unknown country code: {country}");
if (iban.Length != expectedLength)
return (false, $"Wrong length: expected {expectedLength}, got {iban.Length}");
string rearranged = iban[4..] + iban[..4];
var numericBuilder = new StringBuilder();
foreach (char c in rearranged)
{
numericBuilder.Append(char.IsLetter(c) ? c - 'A' + 10 : c - '0');
}
int remainder = 0;
foreach (char c in numericBuilder.ToString())
{
remainder = (remainder * 10 + (c - '0')) % 97;
}
return remainder == 1
? (true, string.Empty)
: (false, "Invalid check digits (MOD-97 failed)");
}
}The digit-by-digit modulo computation keeps arithmetic within int range — no BigInteger needed.
Approach 3: REST API Call with HttpClient
Use the ibanchecker.cash API when you need bank name, BIC, or country metadata:
using System.Net.Http.Json;
using System.Text.Json;
public record IbanValidationResponse(
bool Valid,
string? Iban,
string? Country,
string? BankName,
string? Bic,
string? Error
);
public class IbanApiClient(HttpClient httpClient)
{
public async Task<IbanValidationResponse?> ValidateAsync(string iban)
{
var response = await httpClient.PostAsJsonAsync(
"https://ibanchecker.cash/api/v1/validate",
new { iban }
);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<IbanValidationResponse>();
}
}
// Registration in Program.cs:
builder.Services.AddHttpClient<IbanApiClient>();Usage:
var result = await ibanApiClient.ValidateAsync("DE89370400440532013000");
if (result?.Valid == true)
{
Console.WriteLine($"Bank: {result.BankName}"); // Deutsche Bank
Console.WriteLine($"BIC: {result.Bic}"); // DEUTDEDB
}FluentValidation Integration
using FluentValidation;
using IbanNet;
public class PaymentRequestValidator : AbstractValidator<PaymentRequest>
{
public PaymentRequestValidator(IIbanValidator ibanValidator)
{
RuleFor(x => x.Iban)
.NotEmpty()
.Must(iban => ibanValidator.Validate(iban).IsValid)
.WithMessage("'{PropertyValue}' is not a valid IBAN.");
RuleFor(x => x.BeneficiaryName)
.NotEmpty()
.MaximumLength(140);
RuleFor(x => x.Amount)
.GreaterThan(0);
}
}Unit Tests with xUnit
using Xunit;
public class IbanValidatorTests
{
[Theory]
[InlineData("DE89370400440532013000")]
[InlineData("GB29NWBK60161331926819")]
[InlineData("FR7630006000011234567890189")]
[InlineData("NL91ABNA0417164300")]
[InlineData("DE89 3704 0044 0532 0130 00")] // with spaces
[InlineData("de89370400440532013000")] // lowercase
public void Validate_ValidIban_ReturnsTrue(string iban)
{
var (isValid, _) = IbanValidator.Validate(iban);
Assert.True(isValid);
}
[Theory]
[InlineData("DE89370400440532013001")] // wrong check digit
[InlineData("DE8937040044053201300")] // too short
[InlineData("XX89370400440532013000")] // unknown country
[InlineData("")]
[InlineData("not-an-iban")]
public void Validate_InvalidIban_ReturnsFalse(string iban)
{
var (isValid, _) = IbanValidator.Validate(iban);
Assert.False(isValid);
}
[Fact]
public void Normalize_HandlesAllFormats()
{
Assert.Equal("DE89370400440532013000", IbanValidator.Normalize("DE89 3704 0044 0532 0130 00"));
Assert.Equal("DE89370400440532013000", IbanValidator.Normalize("de89370400440532013000"));
Assert.Equal("DE89370400440532013000", IbanValidator.Normalize("DE89-3704-0044-0532-0130-00"));
Assert.Equal("DE89370400440532013000", IbanValidator.Normalize(" DE89370400440532013000 "));
}
[Fact]
public void Validate_ReturnsError_WhenWrongLength()
{
var (isValid, error) = IbanValidator.Validate("DE8937040044053201300");
Assert.False(isValid);
Assert.Contains("Wrong length", error);
}
}Which Approach to Choose?
Use the ibanchecker.cash API when you need bank name, BIC, and country metadata and your service has outbound HTTP access. Use IbanNet when you want a well-maintained library with ASP.NET Core integration and offline capability. Use the manual implementation when you need zero NuGet dependencies or are targeting constrained environments like Unity or WASM.
For most .NET web APIs, IbanNet with the API as a fallback for metadata gives you the best combination of correctness, maintainability, and enriched validation data.
Last updated: June 2026
Validate an IBAN instantly
Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.
Open IBAN Checker →Related Articles