Recursive AI

Read a vCard (.vcf) file in C#

All onemail recipes

Parse the file with ContentDocument.Parse and read each card through VCardDocument.Cards: names, emails, phones, addresses and dates come back typed and decoded. vCard 2.1, 3.0 and 4.0 all read the same way. The program below reads three contacts, one in each version.

Terminal
dotnet new console -o ReadVcf && cd ReadVcf
dotnet add package RecursiveAI.OneMail.Calendar --version 0.2.0-preview.2

Save this as contacts.vcf. The 2.1 card is quoted-printable UTF-8, as older phones and Outlook export it.

Text
BEGIN:VCARD
VERSION:3.0
FN:Ana Ruiz
N:Ruiz;Ana;;;
ORG:Example Corp;Sales
TITLE:Account manager
EMAIL;TYPE=INTERNET,WORK:[email protected]
TEL;TYPE=WORK,VOICE:+1 555 0100
TEL;TYPE=CELL:+1 555 0199
ADR;TYPE=WORK:;;100 Main St;Springfield;IL;62701;USA
BDAY:1988-04-12
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:M=C3=BCller;J=C3=BCrgen
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:J=C3=BCrgen M=C3=BCller
EMAIL;PREF;INTERNET:[email protected]
TEL;HOME:+49 30 1234567
END:VCARD
BEGIN:VCARD
VERSION:4.0
FN:Sam Lee
N:Lee;Sam;;;
EMAIL;TYPE=home;PREF=1:[email protected]
TEL;VALUE=uri;TYPE="voice,cell":tel:+1-555-0142
BDAY:--0615
END:VCARD

Put this in Program.cs and run dotnet run:

C#
using RecursiveAI.OneMail.Calendar;
using RecursiveAI.OneMail.Calendar.VCard;
 
// vCard 2.1, 3.0 and 4.0 in one file; quoted-printable and CHARSET are decoded
ContentDocument doc = ContentDocument.Parse(File.ReadAllBytes("contacts.vcf"));
 
foreach (VCardView card in VCardDocument.Cards(doc))
{
    Console.WriteLine($"{card.FormattedName} (vCard {card.VersionText})");
    if (card.Name is { } n)
        Console.WriteLine($"  name:  {Join(n.Given)} / {Join(n.Family)}");
    if (card.Organization.Count > 0)
    {
        string org = string.Join(", ", card.Organization);
        Console.WriteLine($"  org:   {org}; {card.Title}");
    }
    foreach (VCardTyped email in card.Emails)
        Console.WriteLine($"  email: {email.Value} [{string.Join(",", email.Types)}]");
    foreach (VCardTyped phone in card.Phones)
        Console.WriteLine($"  phone: {phone.Value} [{string.Join(",", phone.Types)}]");
    foreach (VCardAddress adr in card.Addresses)
        Console.WriteLine($"  addr:  {Join(adr.Street)}, {Join(adr.Locality)}");
    // vCard 4.0 allows a birthday without a year
    if (card.Birthday is { } b)
    {
        string year = b.Year?.ToString() ?? "-";
        Console.WriteLine($"  bday:  {year}-{b.Month:00}-{b.Day:00}");
    }
}
 
static string Join(IReadOnlyList<string> parts) => string.Join(" ", parts);
Text
Ana Ruiz (vCard 3.0)
  name:  Ana / Ruiz
  org:   Example Corp, Sales; Account manager
  email: [email protected] [INTERNET,WORK]
  phone: +1 555 0100 [WORK,VOICE]
  phone: +1 555 0199 [CELL]
  addr:  100 Main St, Springfield
  bday:  1988-04-12
Jürgen Müller (vCard 2.1)
  name:  Jürgen / Müller
  email: [email protected] [PREF,INTERNET]
  phone: +49 30 1234567 [HOME]
Sam Lee (vCard 4.0)
  name:  Sam / Lee
  email: [email protected] [home]
  phone: tel:+1-555-0142 [voice,cell]
  bday:  --06-15

The quoted-printable name decodes to “Jürgen Müller”. Type parameters are kept as written in each version (INTERNET,WORK, PREF, voice,cell). The 4.0 birthday has no year, which vCard 4.0 allows.

What you get

  • Every field as a list. VCardName and VCardAddress hold lists, because a vCard field can have several values.
  • The rest of RFC 6350: Photos (inline or by URL), Impps, Related, Categories, Note, Uid and more.
  • Lossless. An untouched file writes back byte for byte with doc.Write(stream).
  • Outlook contacts. RecursiveAI.OneMail reads contacts from MSG and PST files as MapiContact, and converts them to and from vCard with a loss report.

Limits

  • A preview. 0.2.0-preview.2: the API can change before 1.0.
  • Unchecked against Outlook. The project lists this gap: “no Outlook-produced contact PST or MSG with the full property set, and no Outlook import/export of vCards to compare against”.
  • Phone and email slots. Outlook’s fixed slots (business phone, mobile, email 1 to 3) map to vCard only approximately. The conversion reports each such change (PhoneSlotChanged, EmailSlotChanged).

Everything about contacts: Contacts. The parser: ICS and vCard.