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????
-
Buy me a beer!
If you find any of the stuff here to be useful or interesting, buy me a beer and I'll raise a glass to you!
-
Recent Posts
- Hiding the Time Interval in Calendar Web Parts When Multiple Events are on the Same Day
- Hide the Upload Multiple Files Link and Overwrite Existing Files Checkbox in a SharePoint Document Library
- InfoPath Won’t Open a Custom List Form in SharePoint or Office365 – The SOAP Message Cannot Be Parsed
- Hide Time Interval in a SharePoint Calendar in Office365
- Restoring Missing SharePoint Picture Library Controls When Using IE11
Recent Comments
- Vince Pangan on Making File Attachments Read Only in SharePoint List Custom InfoPath Forms
- Jason on Making File Attachments Read Only in SharePoint List Custom InfoPath Forms
- Vince Pangan on Hiding the time intervals in a calendar view and web part
- Vince Pangan on Hiding the time intervals in a calendar view and web part
- Vince Pangan on Hiding the time intervals in a calendar view and web part
Archives
Categories