c# - MimeKit.MimeMessage to Browser-Renderable HTML -


is there way convert mimekit.mimemessage html can rendered in web browser? i'm not concerned message attachments, able display message body, complete embedded images, in browser. i'm new mimekit , couldn't locate in api docs this. info appreciated.

edit: didn't find way natively mimekit, combined htmlagilitypack parse mimemessage.htmbody , fix inline images. seems work , i'll go unless has better idea. reference, here's code:

////////////////////////////////////////////////////////////////////////////////////////// // use mimekit parse message ////////////////////////////////////////////////////////////////////////////////////////// mimekit.mimemessage msg = mimekit.mimemessage.load(stream);  ////////////////////////////////////////////////////////////////////////////////////////// // use htmlagilitypack parse resulting html in order fix inline images ////////////////////////////////////////////////////////////////////////////////////////// htmlagilitypack.htmldocument hdoc = new htmlagilitypack.htmldocument(); hdoc.loadhtml(msg.htmlbody); // find image nodes var images = hdoc.documentnode.descendants("img"); foreach (var img in images) {                             // check inline image     string cid = img.attributes["src"].value;     if (cid.startswith("cid:"))     {         // remove cid part of attribute         cid = cid.remove(0, 4);         // find image object in mimemessage         mimekit.mimepart part = msg.bodyparts.first(x => x.contentid == cid) mimekit.mimepart;         if (part != null)         {             using (memorystream mstream = new memorystream())             {                 // raw image content                 part.contentobject.writeto(mstream);                 mstream.flush();                 byte[] imgbytes = mstream.toarray();                 // fix image source making embedded image                 img.attributes["src"].value = "data:" + part.contenttype.mimetype + ";" + part.contenttransferencoding.tostring().tolower() + "," +                     system.text.asciiencoding.ascii.getstring(imgbytes);             }         }     } }  // write resulting html output stream hdoc.save(outputstream); 

your solution similar logic used use in mimekit's messagereader sample, mimekit provides better solution:

/// <summary> /// visits mimemessage , generates html suitable rendered browser control. /// </summary> class htmlpreviewvisitor : mimevisitor {     list<multipartrelated> stack = new list<multipartrelated> ();     list<mimeentity> attachments = new list<mimeentity> ();     readonly string tempdir;     string body;      /// <summary>     /// creates new htmlpreviewvisitor.     /// </summary>     /// <param name="tempdirectory">a temporary directory used storing image files.</param>     public htmlpreviewvisitor (string tempdirectory)     {         tempdir = tempdirectory;     }      /// <summary>     /// list of attachments in mimemessage.     /// </summary>     public ilist<mimeentity> attachments {         { return attachments; }     }      /// <summary>     /// html string can set on browsercontrol.     /// </summary>     public string htmlbody {         { return body ?? string.empty; }     }      protected override void visitmultipartalternative (multipartalternative alternative)     {         // walk multipart/alternative children backwards greatest level of faithfulness least faithful         (int = alternative.count - 1; >= 0 && body == null; i--)             alternative[i].accept (this);     }      protected override void visitmultipartrelated (multipartrelated related)     {         var root = related.root;          // push multipart/related onto our stack         stack.add (related);          // visit root document         root.accept (this);          // pop multipart/related off our stack         stack.removeat (stack.count - 1);     }      // image based on img src url within our multipart/related stack     bool trygetimage (string url, out mimepart image)     {         urikind kind;         int index;         uri uri;          if (uri.iswellformeduristring (url, urikind.absolute))             kind = urikind.absolute;         else if (uri.iswellformeduristring (url, urikind.relative))             kind = urikind.relative;         else             kind = urikind.relativeorabsolute;          try {             uri = new uri (url, kind);         } catch {             image = null;             return false;         }          (int = stack.count - 1; >= 0; i--) {             if ((index = stack[i].indexof (uri)) == -1)                 continue;              image = stack[i][index] mimepart;             return image != null;         }          image = null;          return false;     }      // save image our temp directory , return "file://" url suitable     // browser control load.     // note: if you'd rather embed image data html, can construct     // "data:" url instead.     string saveimage (mimepart image, string url)     {         string filename = url.replace (':', '_').replace ('\\', '_').replace ('/', '_');         string path = path.combine (tempdir, filename);          if (!file.exists (path)) {             using (var output = file.create (path))                 image.contentobject.decodeto (output);         }          return "file://" + path.replace ('\\', '/');     }      // replaces <img src=...> urls refer images embedded within message     // "file://" urls browser control able load.     void htmltagcallback (htmltagcontext ctx, htmlwriter htmlwriter)     {         if (ctx.tagid == htmltagid.image && !ctx.isendtag && stack.count > 0) {             ctx.writetag (htmlwriter, false);              // replace src attribute file:// url             foreach (var attribute in ctx.attributes) {                 if (attribute.id == htmlattributeid.src) {                     mimepart image;                     string url;                      if (!trygetimage (attribute.value, out image)) {                         htmlwriter.writeattribute (attribute);                         continue;                     }                      url = saveimage (image, attribute.value);                      htmlwriter.writeattributename (attribute.name);                     htmlwriter.writeattributevalue (url);                 } else {                     htmlwriter.writeattribute (attribute);                 }             }         } else if (ctx.tagid == htmltagid.body && !ctx.isendtag) {             ctx.writetag (htmlwriter, false);              // add and/or replace oncontextmenu="return false;"             foreach (var attribute in ctx.attributes) {                 if (attribute.name.tolowerinvariant () == "oncontextmenu")                     continue;                  htmlwriter.writeattribute (attribute);             }              htmlwriter.writeattribute ("oncontextmenu", "return false;");         } else {             // pass tag through output             ctx.writetag (htmlwriter, true);         }     }      protected override void visittextpart (textpart entity)     {         textconverter converter;          if (body != null) {             // since we've found body, treat attachment             attachments.add (entity);             return;         }          if (entity.ishtml) {             converter = new htmltohtml {                 htmltagcallback = htmltagcallback             };         } else if (entity.isflowed) {             var flowed = new flowedtohtml ();             string delsp;              if (entity.contenttype.parameters.trygetvalue ("delsp", out delsp))                 flowed.deletespace = delsp.tolowerinvariant () == "yes";              converter = flowed;         } else {             converter = new texttohtml ();         }          string text = entity.text;          body = converter.convert (entity.text);     }      protected override void visittnefpart (tnefpart entity)     {         // extract attachments in ms-tnef part         attachments.addrange (entity.extractattachments ());     }      protected override void visitmessagepart (messagepart entity)     {         // treat message/rfc822 parts attachments         attachments.add (entity);     }      protected override void visitmimepart (mimepart entity)     {         // realistically, if we've gotten far, can treat attachment         // if isattachment property false.         attachments.add (entity);     } } 

and use custom htmlpreviewvisitor class, you'd have method this:

void render (webbrowser browser, mimemessage message) {     var tmpdir = path.combine (path.gettemppath (), message.messageid);     var visitor = new htmlpreviewvisitor (tmpdir);      directory.createdirectory (tmpdir);      message.accept (visitor);      browser.documenttext = visitor.htmlbody; } 

i know seems lot of code, it's covering lot more simple cases. you'll notice handles rendering text/plain text/plain; format=flowed bodies if html not available. correctly uses images part of encapsulating multipart/related tree.

one way modify code embed images img tags instead of using temp directory. that, you'd modify saveimage method (be warned, next segment of code untested):

string saveimage (mimepart image, string url) {     using (var output = new memorystream ()) {         image.contentobject.decodeto (output);          var buffer = output.getbuffer ();         int length = (int) output.length;          return string.format ("data:{0};base64,{1}", image.contenttype.mimetype, convert.tobase64string (buffer, 0, length));     } } 

Comments