Scenario
So you know how to
send an email using MVC
but now you'd like to attach an invitation to a meeting or some fancy event to someone.
We're gonna have a quick look into how to how to accomplish this and it should, hopefully, work
for the bigger emailing apps like Outlook and Gmail.
This is my story.
Overview
In this snippet I will pick up from a previous full length article I published here. Above and beyond the technologies we used before, we'll be using an ICS... or an iCal... or an iCalendar... or an Internet Calendar (they're the same thing) format for our invitation.
Straight Into It
Before we start I'd like to say that when I had decided to write this short article I had sorely overestimated my, already lacking, skill in relation to the supposed straight forward nature of this topic when I was doing my research and development. And I was rightfully humbled, but I have overcome!
Since we're merely following from a previously posted article, we won't do much setting up of the project and you're even free to download and follow from the previous example. Don't worry, I'll be sure to include a link to download this complete project at the end.
EmailModel.cs
First we're going to update our Model so that it can hold the data needed for, what I believe, are the
minimum requirements for an invitation.
Add the following code to the EmailModel.cs file:
- public string Location { get; set; }
- public DateTime StartTime { get; set; }
- public DateTime EndTime { get; set; }
- public DateTime Date { get; set; }
- public string Summary { get; set; }
- public string Description { get; set; }
The above code is pretty self-explanatory, maybe the public string Summary
and the public string Description
aren't. The one is kind of the name of the event/meeting/invitation/whatever and the other is or can be a lengthy description of the
invitation respectively.
Your Model should bare a resemblance to this when you're done:
- public string Name { get; set; }
- public string Addess { get; set; }
- public string Subject { get; set; }
- public string Body { get; set; }
- public string Location { get; set; }
- public DateTime StartTime { get; set; }
- public DateTime EndTime { get; set; }
- public DateTime Date { get; set; }
- public string Summary { get; set; }
- public string Description { get; set; }
Now that our Model has been altered, we're now able to do the same with our View and we'll be able to more easily access our properties there.
Index.cshtml
Open the Index.cshtml View and add these lines of code after the form fields and before the submit button we had previously created:
- <label asp-for="Location">Location</label> <br />
- <input asp-for="Location" type="text" name="Location" /> <br />
- <label asp-for="Date">Date</label> <br />
- <input asp-for="Date" type="date" name="Date" /><br />
- <label asp-for="StartTime">Start Time</label> <br />
- <input asp-for="StartTime" type="time" name="StartTime" /><br />
- <label asp-for="EndTime">End Time</label> <br />
- <input asp-for="EndTime" type="time" name="EndTime" /><br />
- <label asp-for="Summary">Summary</label> <br />
- <input asp-for="Summary" type="text" name="Summary" /><br />
- <label asp-for="Description">Description</label> <br />
- <textarea asp-for="Description" type="" name="Description"></textarea> <br />
The last I checked, some form controls don't work as intented in IE. I think the date and time input fields work just like plain old text fields if I'm not mistaken.
With our View updated we should be able to get our data from it in our Controller when we post our form.
EmailController.cs
The more complex part is up next and the code is quite lengthy. I'd say I'll explain but some of the lines are waaaay over my head but I'll try my best to explain.
- DateTime date = emailModel.Date;
- DateTime startTime = emailModel.StartTime;
- DateTime endTime = emailModel.EndTime;
- string now = DateTime.Now.ToString("yyyyMMddTHHmmssZ");
- string ics = string.Format(
- "BEGIN:VCALENDAR\r\n" +
- "PRODID:-//Comapany & Co.//Their Application//EN\r\n" +
- "VERSION:2.0\r\n" +
- "METHOD:REQUEST\r\n" +
- "BEGIN:VEVENT\r\n" +
- "CREATED:{0}\r\n" +
- "DTSTART:{1}\r\n" +
- "DTEND:{2}\r\n" +
- "DTSTAMP:{3}\r\n" +
- "UID:{4}\r\n" +
- "ATTENDEE;CN=\"{5}\";RSVP=TRUE:mailto:{6}\r\n" +
- "X-ALT-DESC;FMTTYPE=text/html:{7}\r\n" +
- "LOCATION:{8}\r\n" +
- "ORGANIZER;CN=\"Mailer App Name Here\":mailto:[[The email address that the ivitation is being sent from]]\r\n" +
- "SEQUENCE:0\r\n" +
- "SUMMARY:{9}\r\n" +
- "TRANSP:OPAQUE\r\n" +
- "END:VEVENT\r\n" +
- "END:VCALENDAR",
- now,
- string.Concat(date.Year, date.Month.ToString("D2"), date.Day.ToString("D2"), "T", startTime.Hour.ToString("D2"), startTime.Minute.ToString("D2"), startTime.Second.ToString("D2")),
- string.Concat(date.Year, date.Month.ToString("D2"), date.Day.ToString("D2"), "T", endTime.Hour.ToString("D2"), endTime.Minute.ToString("D2"), endTime.Second.ToString("D2")),
- now,
- Guid.NewGuid(),
- emailModel.Name,
- emailModel.Addess,
- emailModel.Description,
- emailModel.Location,
- emailModel.Summary);
- ContentType contentType = new ContentType("text/calendar");
- contentType.Parameters.Add("method", "REQUEST");
- contentType.Parameters.Add("name", "invitation.ics");
- byte[] bytes = Encoding.UTF8.GetBytes(ics);
- MemoryStream stream = new MemoryStream(bytes);
- Attachment icsAttachment = new Attachment(stream, "invitation.ics", "text/calendar");
- mailMessage.Attachments.Add(icsAttachment);
Okay okay, 40+ lines of code is a bit excessive to stomach in one sitting but lines 7 - 25 is what we really came here for. These lines are where we're building our ical file. Directly below I'll list and have a link to what most of these properties are if you're extra curious.
Property List
When I was doing some research this site really helped me understand. The links below reside within it.
I couldn't find X-ALT-DESC
so I'm not sure if it was added after the initial specification.
But I've linked it anyway.
I'm gonna skip some lines when it comes to the explanation of the Properties (lines 7 - 25) but you're free to follow the links I provided above if you're feeling a little uncertain.
- In lines 1 - 3 we're just assigning our dates and times to variables to lessen the mess that will come later.
-
Line 4 we're setting the date/time format because the ical format is extremely specific. Dates need to adhere to
this format:
yyyyMMddTHHmmssZ
. -
In line 6 we're creating our string, if you prefer
StringBuilder
and appending new lines then it will still work. - Line 7 is where we start or open our ical.
-
Line 17 is where we say who's attending. If you're sending an invitation to multiple recipients you'll have to write this
line out for each recipient. You can accomplish this with some kind of looping function to iterate through each one. Also
take note of the
RSVP=TRUE
if you're interested in requesting a response. -
In place of line 18, I've seen examples of
DESCRIPTION
instead ofX-ALT-DESC
. Apparently it allows for HTML. - Line 20 you need to note that if the emails for Attendee and Organizer are the same, the Organizer won't be able to accept (obvious I know but it took me longer than I want to admit to figure that out).
It won't be enought to just add \n
at the end of each line (lines 7 - 24) because it won't adhere to the format specifications
so be sure to use \n\r
.
-
Lines 27 and 28 is a bit messing because we want to get the date of the meeting as well as the start and end times and
also format it in the
yyyyMMddTHHmmssZ
format. -
Lines 37 - 39 are SUPER important and I've struggled to understand why the .ics file was only attached but
the email itself wasn't recognized as an invitation.
This article
saved my life. You need to specify that what you're sending is of type
text/calendar
. - Lines 40 - 44 is where we build and attached our ical in .ics format.
Full Code
Below is the full code for the EmailController.cs.
- using Email_Invitation_MVC.Models;
- using Microsoft.AspNetCore.Mvc;
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Mail;
- using System.Net.Mime;
- using System.Text;
- namespace Email_Invitation_MVC.Controllers
- {
- public class EmailController : Controller
- {
- public IActionResult Index()
- {
- return View();
- }
-
- [HttpPost]
- public IActionResult Index(EmailModel emailModel)
- {
- if (ModelState.IsValid)
- {
- try
- {
- SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
- MailMessage mailMessage = new MailMessage();
- smtpClient.Credentials = new NetworkCredential
- {
- UserName = "[[Your email address here]]",
- Password = "[[Aboves email address password here]]"
- };
-
- smtpClient.EnableSsl = true;
- smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
-
- mailMessage.From = new MailAddress("[[Your email address here]]");
- mailMessage.To.Add(emailModel.Addess);
- mailMessage.Subject = emailModel.Subject;
- mailMessage.Body = string.Format("{0} has sent you an email, their email address: {1}.\n\n" +
- "They had this to say: \n\"{2}\"", emailModel.Name, emailModel.Addess, emailModel.Body);
- //Invitation
- DateTime date = emailModel.Date;
- DateTime startTime = emailModel.StartTime;
- DateTime endTime = emailModel.EndTime;
- string now = DateTime.Now.ToString("yyyyMMddTHHmmssZ");
- string ics = string.Format(
- "BEGIN:VCALENDAR\r\n" +
- "PRODID:-//Comapany & Co.//Their Application//EN\r\n" +
- "VERSION:2.0\r\n" +
- "METHOD:REQUEST\r\n" +
- "BEGIN:VEVENT\r\n" +
- "CREATED:{0}\r\n" +
- "DTSTART:{1}\r\n" +
- "DTEND:{2}\r\n" +
- "DTSTAMP:{3}\r\n" +
- "UID:{4}\r\n" +
- "ATTENDEE;CN=\"{5}\";RSVP=TRUE:mailto:{6}\r\n" +
- "X-ALT-DESC;FMTTYPE=text/html:{7}\r\n" +
- "LOCATION:{8}\r\n" +
- "ORGANIZER;CN=\"Mailer App Name Here\":mailto:[[The email address that the ivitation is being sent from]]\r\n" +
- "SEQUENCE:0\r\n" +
- "SUMMARY:{9}\r\n" +
- "TRANSP:OPAQUE\r\n" +
- "END:VEVENT\r\n" +
- "END:VCALENDAR",
- now,
- string.Concat(date.Year, date.Month.ToString("D2"), date.Day.ToString("D2"), "T", startTime.Hour.ToString("D2"), startTime.Minute.ToString("D2"), startTime.Second.ToString("D2")),
- string.Concat(date.Year, date.Month.ToString("D2"), date.Day.ToString("D2"), "T", endTime.Hour.ToString("D2"), endTime.Minute.ToString("D2"), endTime.Second.ToString("D2")),
- now,
- Guid.NewGuid(),
- emailModel.Name,
- emailModel.Addess,
- emailModel.Description,
- emailModel.Location,
- emailModel.Summary);
- ContentType contentType = new ContentType("text/calendar");
- contentType.Parameters.Add("method", "REQUEST");
- contentType.Parameters.Add("name", "invitation.ics");
- byte[] bytes = Encoding.UTF8.GetBytes(ics);
- MemoryStream stream = new MemoryStream(bytes);
- Attachment icsAttachment = new Attachment(stream, "invitation.ics", "text/calendar");
- mailMessage.Attachments.Add(icsAttachment);
- smtpClient.Send(mailMessage);
- }
- catch (Exception e)
- {
- ViewBag.EmailError = ("Error: {0}", e);
- }
- }
- return View();
- }
- }
- }
You can validate your .ics files at iCalendar Validator if you're having a hard time getting the format right.
I hope this follow up on my previous article proved insightful and that you've learned
as much as I did. Please feel free to comment and ask any questions or even leave suggestions.
Thanks for reading.