I'm Keyvan Nayyeri, a 25 years old Ph.D. student at
the Computer Science department of
the University of Texas at San Antonio.
I'm also
a Software Architect and Developer and previously held a B.Sc.
degree in Applied Mathematics.
This is my blog where I publish content about various topics specifically Programming Languages and Compilers, Software
Engineering and Programming.
Last year I had written an SMTP component for .NET 2.0 and called it Gopi. Gopi is a simple library that lets you manage your emailing system in a .NET application and use SQL Server database as storage system for your data to make sure nothing will be missed.
Like many other libraries that are out there, Gopi uses different database columns for storing its data. There is a drawback with this approach and that is the limitations of storing all properties of an email into database easily. One obvious way is to do a lot of work and write many codes with various columns to store several properties but this isn't acceptable from a quality view and yields dirty code.
Prior to Community Server 2007 Telligent was using the same approach to store emails in Community Server but in this new version Ken Robertson came up with a nice technique on serializing an email and storing it in the database. This is a very nice and clever approach in my opinion and I liked it so much.
For a new project that requires professional fundamental for everything I had to implement an emailing system so thought that it would be great to revisit my Gopi component and update with for .NET 3.5 and apply this approach.
In words this is so easy to implement because at first glance you think it's possible to serialize MailMessage object like a normal object and store it in database but when you want to do this some red errors come up and stop you from thinking because none of MailMessage classes are serializable with built-in serializers.
Obviously the solution is finding a way to serialize this class and this can be done by serializing everything by hand!
There are two MailMessage classes in .NET. First one is in System.Web.Mail namespace and is obsolete in .NET 2.0 and later and the second one is available in System.Net.Mail. I don't care about the obsolete one so I just show how you can serialize instances of System.Net.Mail.MailMessage class.
To serialize the properties of a MailMessage object you can create a new class and create a property of MailMessage type for it that embeds your MailMessage in the class. In this new class you can implement IXmlSerializable interface to manually serialize its MailMessage. Here I create this class and call it SerializableMailMessage.
In a similar approach you can derive your class from MailMessage and implement IXmlSerializable interface rather than embedding the MailMessage object in your class but I'd prefer the first method that makes things simpler when using the class.
IXmlSerializable interface has three methods to implement and you may know about this interface.
Here I'm going to use these two latter methods to provide serialization and deserialization features for my SerializableMailMessage class.
Serialization side of the work is as simple as implementing the WriteXml method. The below code is what I've written to do this.
public void WriteXml(XmlWriter writer)
{
if (this != null)
{
writer.WriteStartElement("MailMessage");
writer.WriteAttributeString("Priority", Convert.ToInt16(this.Priority).ToString());
writer.WriteAttributeString("IsBodyHtml", this.IsBodyHtml.ToString());
// From
writer.WriteStartElement("From");
if (!string.IsNullOrEmpty(this.From.DisplayName))
writer.WriteAttributeString("DisplayName", this.From.DisplayName);
writer.WriteRaw(this.From.Address);
writer.WriteEndElement();
// To
writer.WriteStartElement("To");
writer.WriteStartElement("Addresses");
foreach (MailAddress address in this.To)
{
writer.WriteStartElement("Address");
if (!string.IsNullOrEmpty(address.DisplayName))
writer.WriteAttributeString("DisplayName", address.DisplayName);
writer.WriteRaw(address.Address);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
// CC
if (this.CC.Count > 0)
{
writer.WriteStartElement("CC");
writer.WriteStartElement("Addresses");
foreach (MailAddress address in this.CC)
{
writer.WriteStartElement("Address");
if (!string.IsNullOrEmpty(address.DisplayName))
writer.WriteAttributeString("DisplayName", address.DisplayName);
writer.WriteRaw(address.Address);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
// Bcc
if (this.Bcc.Count > 0)
{
writer.WriteStartElement("Bcc");
writer.WriteStartElement("Addresses");
foreach (MailAddress address in this.Bcc)
{
writer.WriteStartElement("Address");
if (!string.IsNullOrEmpty(address.DisplayName))
writer.WriteAttributeString("DisplayName", address.DisplayName);
writer.WriteRaw(address.Address);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
// Subject
writer.WriteStartElement("Subject");
writer.WriteRaw(this.Subject);
writer.WriteEndElement();
// Body
writer.WriteStartElement("Body");
writer.WriteCData(Body);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
There are a few points to mention about this code:
I stored the numeric value of MailPriority enumerator because it's easier to retrieve from the XML in deserialization. I also worked on common properties of MailMessage and didn't serialize all properties to keep it simple. Following same approach you can serialize other properties like Attachments. Next week I'll release the new version of Gopi that contains the updated code that serializes everything so you can wait shortly to get your hands on the code.
In the reverse direction I need to you need to implement ReadXml method to deserialize a MailMessage. The code is shown below.
public void ReadXml(XmlReader reader)
{
XmlDocument xml = new XmlDocument();
xml.Load(reader);
// Properties
XmlNode rootNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage");
this.IsBodyHtml = Convert.ToBoolean(rootNode.Attributes["IsBodyHtml"].Value);
this.Priority = (MailPriority)Convert.ToInt16(rootNode.Attributes["Priority"].Value);
// From
XmlNode fromNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/From");
string fromDisplayName = string.Empty;
if (fromNode.Attributes["DisplayName"] != null)
fromDisplayName = fromNode.Attributes["DisplayName"].Value;
MailAddress fromAddress = new MailAddress(fromNode.InnerText, fromDisplayName);
this.From = fromAddress;
// To
XmlNode toNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/To/Addresses");
foreach (XmlNode node in toNode.ChildNodes)
{
string toDisplayName = string.Empty;
if (node.Attributes["DisplayName"] != null)
toDisplayName = node.Attributes["DisplayName"].Value;
MailAddress toAddress = new MailAddress(node.InnerText, toDisplayName);
this.To.Add(toAddress);
}
// CC
XmlNode ccNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/CC/Addresses");
foreach (XmlNode node in ccNode.ChildNodes)
{
string ccDisplayName = string.Empty;
if (node.Attributes["DisplayName"] != null)
ccDisplayName = node.Attributes["DisplayName"].Value;
MailAddress ccAddress = new MailAddress(node.InnerText, ccDisplayName);
this.CC.Add(ccAddress);
}
// Bcc
XmlNode bccNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/Bcc/Addresses");
foreach (XmlNode node in bccNode.ChildNodes)
{
string bccDisplayName = string.Empty;
if (node.Attributes["DisplayName"] != null)
bccDisplayName = node.Attributes["DisplayName"].Value;
MailAddress bccAddress = new MailAddress(node.InnerText, bccDisplayName);
this.Bcc.Add(bccAddress);
}
// Subject
XmlNode subjectNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/Subject");
this.Subject = subjectNode.InnerText;
// Body
XmlNode bodyNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/Body");
this.Body = bodyNode.InnerText;
}
Above code uses a helper method to get an XmlNode for different sections.
public XmlNode GetConfigSection(XmlDocument xml, string nodePath)
{
return xml.SelectSingleNode(nodePath);
}
This code is self-explanatory and doesn't need any comment but note that you need to update XPath expressions for your class names. Of course, I could use reflection to make this code more general but sometimes I'm a bad guy!!
Alright! Now I can serialize my MailMessage objects easily. I can test this class in a console application that creates an instance of this class and serializes this object and stores its data to an XML file and displays it in the console then deserializes the data in the XML file.
class Program
{
private const string path = @"D:\file.xml";
static void Main(string[] args)
{
Console.Title = "Serialize MailMessage";
MailMessage email = new MailMessage();
email.From = new MailAddress("noreply@nayyeri.net", "Keyvan Nayyeri");
email.To.Add(new MailAddress("test1@test.com", "Test1"));
email.To.Add(new MailAddress("test2@test.com"));
email.CC.Add(new MailAddress("test3@test.com", "Test3"));
email.Bcc.Add(new MailAddress("test4@test.com"));
email.Subject = "Test Email";
email.Body = "<p>This is a test email for serialization!</p>";
email.IsBodyHtml = true;
email.Priority = MailPriority.High;
SerializableMailMessage mailToSerialize = new SerializableMailMessage();
mailToSerialize.Email = email;
XmlSerializer serializer = new XmlSerializer(typeof(SerializableMailMessage));
using (TextWriter streamWriter = new StreamWriter(path))
{
TextWriter writer = Console.Out;
serializer.Serialize(writer, mailToSerialize);
serializer.Serialize(streamWriter, mailToSerialize);
}
SerializableMailMessage mailToDeserialize = new SerializableMailMessage();
using (TextReader streamReader = new StreamReader(path))
{
mailToDeserialize = serializer.Deserialize(streamReader)
as SerializableMailMessage;
}
Console.ReadLine();
}
}
This code generates an XML like this:
<?xml version="1.0" encoding="utf-8"?>
<SerializableMailMessage>
<MailMessage Priority="2" IsBodyHtml="True">
<From DisplayName="Keyvan Nayyeri">noreply@nayyeri.net</From>
<To>
<Addresses>
<Address DisplayName="Test1">test1@test.com</Address>
<Address>test2@test.com</Address>
</Addresses>
</To>
<CC>
<Addresses>
<Address DisplayName="Test3">test3@test.com</Address>
</Addresses>
</CC>
<Bcc>
<Addresses>
<Address>test4@test.com</Address>
</Addresses>
</Bcc>
<Subject>Test Email</Subject>
<Body><![CDATA[<p>This is a test email for serialization!</p>]]></Body>
</MailMessage>
</SerializableMailMessage>
Putting a breakpoint at the last line of the code and using a visualizer I can also check my deserialization process.
_3.png)
That's it! You can download the source code sample for this post from here.
David Barrett
Mar 27, 2008 2:51 PM
#
Great article!
Tudor
Apr 17, 2008 12:40 PM
#
Thanks for the article. I've been trying to figure out a way to serialize a MailMessage. Didn't know it was that simple!
Agha usman
May 07, 2008 1:34 AM
#
Great Article ... but there is one issue .. what if somebody does not want to send BCC or CC in this case above code of deserialization fails.
I myself spent one day sorting out why deserialization is fail. the answer I got is to put a null check on CC like following
XmlNode ccNode = GetConfigSection(xml, "SerializableMailMessage/MailMessage/CC/Addresses");
if (ccNode != null ){
foreach (XmlNode node in ccNode.ChildNodes)
{
string ccDisplayName = string.Empty;
if (node.Attributes["DisplayName"] != null)
ccDisplayName = node.Attributes["DisplayName"].Value;
MailAddress ccAddress = new MailAddress(node.InnerText, ccDisplayName);
this.CC.Add(ccAddress);
}
}
and same as for BCC.
cheers
The Danish Dynamo
Jun 21, 2008 2:00 PM
#
Nice work - great naming - simple and easy to adopt.
My Best Blog Posts in 2008
Dec 31, 2008 1:40 PM
#
In the past 3.5 years of blogging, I haven’t had such best pick up collections in the end of the year, but now that everybody is writing one, why shouldn’t I write my own?! Collecting this list, I could realize some interesting facts that completely changed
Raul
Mar 13, 2009 6:14 PM
#
Thanx... this is just I need.
UncleBob
Jun 02, 2009 8:13 AM
#
Good article, but it doesn't cover serialization of MailMessage.AlternateViews
sri
Aug 25, 2009 7:18 AM
#
how can we attach files while creating mail message
Asutosha
Oct 07, 2009 4:14 AM
#
please send me the same
Neal Blomfield
Oct 28, 2009 7:01 PM
#
var smtpClient = new SmtpClient
{
DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
PickupDirectoryLocation = //... absolute path to folder you want the files created
};
smtpClient.Send( message );
Nikolaj
Dec 09, 2009 2:21 PM
#
Hi,
I have recently made code that fully binary serialize a MailMessage.
The code can be found here
http://meandaspnet.blogspot.com/2009/10/how-to-binary-serialize-mailmessage-for.html
Ekta Mehta
Feb 02, 2010 2:04 PM
#
Leave a Comment