infopathpic

Rejoice InfoPath developers everywhere, for I bring upon thee, that which has long been asked, but ne’er been answered!  Finally, I have written borrowed, modified, and to some extent written, some code which will email an InfoPath form view as the body of an email AND with the attached files as actual file attachments in the email!  Woohoo!  Let’s check it out….

First things first, make sure that you have an email friendly view of your form that you want to send.  There are numerous blogs out there on this, and I had a bitch of a time getting it just right, but among the recommendations made are to avoid using tables with multiple merged/split cells, and also to make sure that all widths on controls are set (rather than auto).  In my case, I created a button on the data entry form, that would handle switching views and sending the email.  My solution is comprised of 3 code files: FormCode.cs, which is the default InfoPath code, and 2 helper classes: AttachmendHelper.cs (which uses code from this nice Microsoft support resource) and MimeExtensionHelper.cs (which borrows from this post).

Here is my form code (it was for an Expense Report form, so you’ll see that around):


using Microsoft.Office.InfoPath;
using System;
using System.Xml;
using System.Xml.XPath;
using System.Net.Mail;
using System.Xml.Xsl;
using System.IO;
using System.Text;

namespace ExpenseReport
{
public partial class FormCode
{
public void InternalStartup()
{
((ButtonEvent)EventManager.ControlEvents["btn_PayrollSubmit"]).Clicked += new ClickedEventHandler(SendEmailWithAttachments);
}

public void SendEmailWithAttachments(object sender, ClickedEventArgs e)
{
// Switch to the email view you want first
this.ViewInfos.SwitchView("Email View");

XPathNavigator root = this.MainDataSource.CreateNavigator();
XmlNamespaceManager nsManager = this.NamespaceManager;

// Modify these next few lines to fit your needs

string toAddress = root.SelectSingleNode("/my:myFields/my:PayrollEmail", nsManager).Value;
string fromAddress = "whateveryouwant.yourcompany.org";
string subject = "You've got mail";
string body = ConvertViewToHtml();

// Create the Mail Message
using (MailMessage msg = new MailMessage(fromAddress, toAddress))
{
msg.Subject = subject;
msg.Body = body;
msg.IsBodyHtml = true;

// Get the file attachments node - copy the xpath of your repeating group (not the individual File)
XPathNodeIterator fileIterator = root.Select("/my:myFields/my:Attachments/my:Files", nsManager);

// Iterate through file attachments
while (fileIterator.MoveNext())
{
// Get the Base64 encoded file string
string fileString = fileIterator.Current.Value;
if (fileString.Length > 0)
{
// Decode the string into a byte array using the helper, then add it as an Attachment
AttachmentHelper attachmentHelper = new AttachmentHelper(fileString);

Attachment attachment = new Attachment(new MemoryStream(attachmentHelper.DecodedAttachment), attachmentHelper.Filename, attachmentHelper.MimeType );
msg.Attachments.Add(attachment);
}
}

SmtpClient smtp = new SmtpClient("your.smtp.server");
smtp.Send(msg);
}
}

public string ConvertViewToHtml()
{
try
{
byte[] sourceFile = null;

XPathNavigator root = MainDataSource.CreateNavigator();
string myViewName = this.CurrentView.ViewInfo.Name.Replace(" ", string.Empty);
string myViewXslFile = myViewName + ".xsl";

// Create the xsl transformer
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(ExtractFromPackage(myViewXslFile));

// Generate a temporary HTML file
string fileName = Guid.NewGuid().ToString() + ".htm";
string filePath = Path.Combine(Path.GetTempPath(), fileName);

using (XmlWriter writer = XmlWriter.Create(filePath))
{
// Convert the XML to HTML
transform.Transform(root, writer);
writer.Close();
}

// Return the HTML as a string
sourceFile = File.ReadAllBytes(filePath);
return System.Text.Encoding.UTF8.GetString(sourceFile);

}
catch (Exception ex)
{
return "<html><body>Unable to convert the view to HTML <p>" + ex.Message + "</p></body></html>";
}
}

public XmlDocument ExtractFromPackage(string fileName)
{
try
{
XmlDocument doc = new XmlDocument();

using (Stream stream = Template.OpenFileFromPackage(fileName))
doc.Load(stream);

return doc;
}
catch (Exception ex)
{
throw new Exception(string.Format("Error extracting '{0}': {1}",
fileName, ex.Message), ex);
}
}
}
}

Each of the attached files is saved as a Base64 encoded string within the xml document, so I use the AttachmentHelper class to read the string into a byte array, then pull the header information from it to get the filename.  The byte array is then converted to a MemoryStream which can be used as an Attachment for the MailMessage.


using System;
using System.IO;
using System.Text;
using System.Web;

namespace ExpenseReport
{
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class AttachmentHelper
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;

private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
private string mimeType;

/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public AttachmentHelper(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);

DecodeAttachment(theReader);
}
}

private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to obtain the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);

fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;

byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;

//Set the filename
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);

//Set the decoded attachment byte array
decodedAttachment = theReader.ReadBytes(fileSize);

// Get the mime type
mimeType = MimeExtensionHelper.GetMimeMapping(attachmentName);
}

public string MimeType
{
get { return mimeType; }
}

public string Filename
{
get { return attachmentName; }
}

public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
}
}

The final piece to the attachment puzzle was getting the MIME type for the files.  I found that System.Web had a great static method for mapping the file extensions to a mime type, but alas, it’s only public in .NET 4.5 and InfoPath 2010 is stuck on 3.5.  Fortunately I found the very helpful post mentioned above that uses reflection to access the very private System.Web.MimeMapping function from the assembly and let us use it!  You will get a warning that the LoadWithPartialName method is deprecated but just ignore it.


using System;
using System.IO;
using System.Text;
using System.Reflection;

namespace ExpenseReport
{
/// <summary>
/// This class allows access to the internal MimeMapping-Class in System.Web
/// </summary>
public class MimeExtensionHelper
{
static MethodInfo getMimeMappingMethod;

static MimeExtensionHelper()
{
// dirty trick - Assembly.LoadWIthPartialName has been deprecated
Assembly ass = Assembly.LoadWithPartialName("System.Web");
Type t = ass.GetType("System.Web.MimeMapping");

getMimeMappingMethod = t.GetMethod("GetMimeMapping", BindingFlags.Static | BindingFlags.NonPublic);
}

/// <summary>
/// Returns a MIME type depending on the passed files extension
/// </summary>
/// <param name="fileName">File to get a MIME type for</param>
/// <returns>MIME type according to the files extension</returns>
public static string GetMimeMapping(string fileName) {
return (string)getMimeMappingMethod.Invoke(null, new object[] { fileName });
}
}
}

And there it is.  You’re welcome!  See that Beer Me button at the top right???? :D

Leave your Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>