Creating vCard in ASP.NET Core
It’s time to get back to old era of this blog and bring my vCard action result to today’s ASP.NET Core world. There’s also support for images so we can provide really good looking vCards from ASP.NET Core applications. This blog post gives a good base for custom vCard solutions in ASP.NET Core.
Classic ASP.NET MVC version of this solution is described in my blog posts Creating vCard with image in .Net and Creating vCard action result.
vCard class
Back in time I built vCard class to be simple DTO-style class with ToString() method that builds up string output of vCard.
public class VCard
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Organization { get; set; }
public string JobTitle { get; set; }
public string StreetAddress { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public string Email { get; set; }
public string HomePage { get; set; }
public byte[] Image { get; set; }
public string GetFullName()
{
return FirstName + LastName;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine("BEGIN:VCARD");
builder.AppendLine("VERSION:2.1");
// Name
builder.Append("N:").Append(LastName)
.Append(";").AppendLine(FirstName);
// Full name
builder.Append("FN:").Append(FirstName)
.Append(" ").AppendLine(LastName);
// Address
builder.Append("ADR;HOME;PREF:;;").Append(StreetAddress)
.Append(";").Append(City).Append(";")
.Append(Zip).Append(";").AppendLine(CountryName);
// Other data
builder.Append("ORG:").AppendLine(Organization);
builder.Append("TITLE:").AppendLine(JobTitle);
builder.Append("TEL;WORK;VOICE:").AppendLine(Phone);
builder.Append("TEL;CELL;VOICE:").AppendLine(Mobile);
builder.Append("URL:").AppendLine(HomePage);
builder.Append("EMAIL;PREF;INTERNET:").AppendLine(Email);
// Image
if(Image != null)
{
builder.AppendLine("PHOTO;ENCODING=BASE64;TYPE=JPEG:");
builder.AppendLine(Convert.ToBase64String(Image));
builder.AppendLine(string.Empty);
}
builder.AppendLine("END:VCARD");
return builder.ToString();
}
}
There are more attributes that vCard supports but my code focuses on most important ones. I left out others as I have not needed these is practice over last ten years. Those who need more attributes can take my code and add the ones they need.
vCard action result
Creating action result for vCard is simple. Most of dirty work is done in ToString() method of vCard. Out action result must set response headers and write vCard to response stream.
public class vCardActionResult : IActionResult
{
private readonly VCard _vCard;
public vCardActionResult(VCard vCard)
{
_vCard = vCard;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var fileName = _vCard.GetFullName() + ".vcf";
var disposition = "attachment; filename=" + fileName;
var response = context.HttpContext.Response;
response.ContentType = "text/vcard";
response.Headers.Add("Content-disposition", disposition);
var bytes = Encoding.UTF8.GetBytes(_vCard.ToString());
await response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
Here is my sample controller action to download vCard.
public async Task<IActionResult> vCard()
{
var vcard = new VCard();
vcard.FirstName = "Gunnar";
vcard.LastName = "Peipman";
vcard.Email = "gunnar@example.com";
vcard.City = "Tallinn";
vcard.CountryName = "Estonia";
vcard.Phone = "00-12345";
vcard.Mobile = "00-10101";
vcard.Organization = "Freelancer";
vcard.JobTitle = "Chief Everything Office";
vcard.Image = await System.IO.File.ReadAllBytesAsync("gunnar300.jpg");
return new vCardActionResult(vcard);
}
My controller action works and when opening downloaded vCard with Outlook it shows nice business card.
We have working vCard solution but it’s not yet very convenient to use.
Using vCard action result in practice
First annoying thing for me is creating new instance of vCardActionResult in controller action. ASP.NET Core action results can be often called by special method. To follow the same pattern I created my own base class for controllers.
public class BaseController : Controller
{
public IActionResult VCard(VCard vCard)
{
return new vCardActionResult(vCard);
}
}
It’s simple, it’s primitive but it makes writing of vCard returning actions more intuitive.
public async Task<IActionResult> Download()
{
var vcard = new VCard();
vcard.FirstName = "Gunnar";
vcard.LastName = "Peipman";
vcard.Email = "gunnar@example.com";
vcard.City = "Tallinn";
vcard.CountryName = "Estonia";
vcard.Phone = "00-12345";
vcard.Mobile = "00-10101";
vcard.Organization = "Freelancer";
vcard.JobTitle = "Chief Everything Office";
vcard.Image = await System.IO.File.ReadAllBytesAsync("gunnar300.jpg");
return VCard(vcard);
}
Second annoying thing for me is the code that creates VCard instance. It can be there in controller but I prefer to keep it somewhere else because of reusability. Usually vCard data comes from Customer, Person or Company class. Let’s write simple Customer class.
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
It’s up to concrete solution where to keep the following extension method that converts customer to vCard but here’s the code.
public static class VCardExtensions
{
public static VCard ToVCard(this Customer customer)
{
var vCard = new VCard();
vCard.City = customer.City;
vCard.CountryName = customer.Country;
vCard.Email = customer.Email;
vCard.Mobile = customer.Mobile;
vCard.FirstName = customer.FirstName;
vCard.LastName = customer.LastName;
return vCard;
}
}
Let’s rewrite vCard controller action now the way it uses customer class to get step closer to real scenarios.
public async Task<IActionResult> vCard()
{
var customer = new Customer
{
FirstName = "Gunnar",
LastName = "Peipman",
Email = "gunnar@example.com",
Mobile = "0012345",
City = "Tallinn",
Country = "Estonia"
};
var vcard = customer.ToVCard();
vcard.Image = await System.IO.File.ReadAllBytesAsync("gunnar300.jpg");
return VCard(vcard);
}
This is how our vCard controller action looks in practice.
public async Task<IActionResult> Download(int id)
{
var customer = await _dataContext.Customers
.FirstOrDefaultAsync(c => c.Id == id);
if(customer == null)
{
return NotFound();
}
var vcard = customer.ToVCard();
vcard.Image = await System.IO.File.ReadAllBytesAsync("gunnar300.jpg");
return VCard(vcard);
}
About using base controller. Don’t create base controller only to have nice VCard() method. If your application already uses base controller then it’s safe to add vCard() method there. If you don’t have base controller and you don’t plan to use it then just create new instance of vCardActionResult in actions that return vCard.
Wrapping up
It wan’t hard to take old vCard action result and drag it to present. We added VCard() method to base controller to make it more intuitive for other developers to return vCard from controller actions. To better support real scenarios we created ToVCard() extension method that converts our Customer domain class to vCard. This blog post is good starting point for your own vCard solution in .NET Core.
Super helpful! Mighty thanks!!!