---
title: "Standardize a Canadian address in C#"
url: https://recursiveai.org/onecontact/cookbook/standardize-canadian-address/
description: "Standardize Canadian addresses in C#: format English and French postal labels, normalize postal codes and check province mismatches with OneContact."
version: "1.0.0"
---

# Standardize a Canadian address in C#

**OneContact** is a free, open-source contact data library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies, no network access.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneContact) · [GitHub](https://github.com/recursive-ai-oss/onecontact-csharp) · [Docs](https://recursiveai.org/onecontact/docs/) · [Download example (.zip)](https://recursiveai.org/onecontact/cookbook/standardize-canadian-address/standardize-canadian-address.zip)

Standardize Canadian postal labels and check address structure, without a delivery-point lookup.

```shell
dotnet add package RecursiveAI.OneContact
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using RecursiveAI.OneContact;

var address = PostalAddress.Parse(
    "10-123 Main Street, Toronto ON m5v2t6",
    Region.CA);
Console.WriteLine(address.ToString(
    AddressStyle.Postal, AddressLayout.Label));
```

```text
10-123 MAIN ST
TORONTO ON  M5V 2T6
```

Unzip [the example](https://recursiveai.org/onecontact/cookbook/standardize-canadian-address/standardize-canadian-address.zip), then `dotnet run`.

## The full program

The example embeds four invented addresses: an English unit-before-street address, a French apartment address, a province/postal-code mismatch and text the rules cannot separate. It prints parsing reasons separately from structural validation issues.

```csharp
using System.Buffers;
using RecursiveAI.OneContact;

string[] inputs =
[
    "10-123 Main Street, Toronto ON m5v2t6",
    "123 rue Principale app 4, Montréal QC h3z2y7",
    "123 Main St, Toronto ON H3Z 2Y7",
    "123 rue Principale Montréal QC H3Z 2Y7"
];
foreach (string input in inputs)
{
    Console.WriteLine($"Input: {input}");
    var status = PostalAddress.TryParse(
        input.AsSpan(), Region.CA,
        out var address, out var detail);
    Console.WriteLine($"Parse: {status}");
    foreach (var issue in detail.Issues)
        Console.WriteLine($"Reason: {issue.Reason}");
    if (status == OperationStatus.Done)
    {
        Console.WriteLine(address.ToString(
            AddressStyle.Postal,
            AddressLayout.Label));
        var issues = address.Validate();
        Console.WriteLine(
            $"Structural issues: {issues.Count}");
        foreach (var issue in issues)
            Console.WriteLine(
                $"{issue.Field}: {issue.Kind}");
    }
    Console.WriteLine();
}
```

`dotnet run` prints:

```text
Input: 10-123 Main Street, Toronto ON m5v2t6
Parse: Done
10-123 MAIN ST
TORONTO ON  M5V 2T6
Structural issues: 0

Input: 123 rue Principale app 4, Montréal QC h3z2y7
Parse: Done
123 RUE PRINCIPALE APP 4
MONTRÉAL QC  H3Z 2Y7
Structural issues: 0

Input: 123 Main St, Toronto ON H3Z 2Y7
Parse: Done
123 MAIN ST
TORONTO ON  H3Z 2Y7
Structural issues: 1
PostalCode: MismatchingValue

Input: 123 rue Principale Montréal QC H3Z 2Y7
Parse: InvalidData
Reason: address.model-required
```

`Parse` throws `ContactParseException` when the rules cannot read the input; the full program uses `TryParse` to get a status and reason instead.

## Variations

**A French apartment address.** Keep a comma or line break between the delivery line and municipality:

```csharp
using RecursiveAI.OneContact;

var address = PostalAddress.Parse(
    "123 rue Principale app 4, Montréal QC h3z2y7",
    Region.CA);
Console.WriteLine(address.ToString(
    AddressStyle.Postal, AddressLayout.Label));
```

```text
123 RUE PRINCIPALE APP 4
MONTRÉAL QC  H3Z 2Y7
```

**Handle text the rules cannot separate.** Inspect the reason without throwing:

```csharp
using System.Buffers;
using RecursiveAI.OneContact;

var status = PostalAddress.TryParse(
    "123 rue Principale Montréal QC H3Z 2Y7".AsSpan(),
    Region.CA, out var address, out var detail);
if (status != OperationStatus.Done)
    Console.WriteLine(detail.PrimaryReason);
```

```text
address.model-required
```

## Related recipes

- [Parse a postal address into fields](https://recursiveai.org/onecontact/cookbook/parse-postal-address/)
- [Format a postal address by country](https://recursiveai.org/onecontact/cookbook/format-postal-address/)
- [Deduplicate contacts](https://recursiveai.org/onecontact/cookbook/deduplicate-contacts/)

## The short answer

Use `PostalAddress.Parse(text, Region.CA)` to read Canadian free text, then `ToString(AddressStyle.Postal, AddressLayout.Label)` for a standardized label. Call `Validate()` separately to inspect required fields, province names and postal-code patterns. Successful parsing is not the same as successful validation, and neither establishes deliverability.

The snippet at the top turns `Main Street` into `MAIN ST`, preserves unit `10` before civic number `123`, and changes `m5v2t6` to `M5V 2T6`. The full program shows why these are separate operations: its third address can be parsed and printed, but its postal-code prefix does not match Ontario.

## How do I standardize a Canadian address?

Pass Canadian input directly to the Canadian rules rather than trying a US parser first. OneContact reads the municipality, province and postal code from the end of the address. It accepts province codes and province names in English and French. The country hint supplies context when the content does not determine the country; contradictory content can produce `address.country-conflict` rather than being silently reinterpreted as Canadian.

For predictable imports, retain commas or line breaks between the delivery line and the municipality. Without separators, the rules need to recognize where the street ends. A French street type before the street name can leave that boundary ambiguous. The final example reports `address.model-required`; version 1.0 does not supply a trained-model fallback. Ask for a corrected separator or separate fields instead of accepting an invented split.

Standardization does not fill gaps. OneContact does not invent a city from a postal code, infer a province from its prefix or guess a unit designator for a bare `#`. Preserve the original input alongside the standardized address when users may need to correct it. The [US and Canada rules](https://recursiveai.org/onecontact/docs/address-rules-us-ca/) document these decisions and the rule identifiers available in `RuleDetail`.

## What is the Canadian postal address format?

For a civic address, [Canada Post’s addressing guidelines](https://www.canadapost-postescanada.ca/cpc/en/support/articles/addressing-guidelines/canadian-addresses.page) place the addressee first, the civic address next, and the municipality, province or territory, and postal code together on the last line.

OneContact’s postal-label renderer uses uppercase letters and standard abbreviations. It puts two spaces between the province and postal code on a label, with one space inside the postal code. The one-line postal rendering uses one space before the code instead. Domestic labels omit the country; the renderer’s `includeCountry` option adds `CANADA` when needed.

Canadian units can precede the civic number, as `10-123`, or follow the street with a designator. The documented English designators include `APT`, `SUITE` and `UNIT`; French includes `APP`, `BUREAU` and `UNITÉ`. The French variation shows `APP 4` staying after the street. Do not translate a French street name into English to make a label: formatting the supplied tokens is a different task from translating an address.

## How do I verify a Canadian address?

Start by deciding what “verify” means. `Validate()` checks the address’s structure: required fields, recognized administrative areas, the postal-code pattern and the prefix expected by the province. It returns field-specific issues, at most one per field, rather than a delivery verdict.

In the full program, `Toronto ON H3Z 2Y7` produces `PostalCode: MismatchingValue`. A structurally sound invented address produces no issues. Neither result comes from a lookup of streets, buildings or residents. The [structural validation documentation](https://recursiveai.org/onecontact/docs/address-structural/#validation) explicitly separates these checks from existence.

If an application needs confirmation that a delivery point exists, use an appropriate postal reference source as a separate step. Keep the parsing reason and structural issues in distinct fields so a user can tell an unreadable address from a readable address with an inconsistent postal code. An empty issue list is useful for form validation, not evidence that a parcel will arrive.
