C# 浏览器可呈现HTML的MimeKit.MimeMessage
有没有办法将MimeKit.MimeMessage转换为可以在web浏览器中呈现的HTML?我不关心邮件附件,但希望能够在浏览器中显示包含嵌入图像的邮件正文。我是MimeKit的新手,在API文档中找不到关于此的任何内容。欢迎提供任何信息 编辑:我没有找到一种方法在MimeKit中实现这一点,但我将其与HtmlAgilityPack结合起来,解析MimeMessage.HtmBody并修复内联图像。这似乎管用,除非有人有更好的主意,否则我会同意。以下代码供参考:C# 浏览器可呈现HTML的MimeKit.MimeMessage,c#,mimekit,C#,Mimekit,有没有办法将MimeKit.MimeMessage转换为可以在web浏览器中呈现的HTML?我不关心邮件附件,但希望能够在浏览器中显示包含嵌入图像的邮件正文。我是MimeKit的新手,在API文档中找不到关于此的任何内容。欢迎提供任何信息 编辑:我没有找到一种方法在MimeKit中实现这一点,但我将其与HtmlAgilityPack结合起来,解析MimeMessage.HtmBody并修复内联图像。这似乎管用,除非有人有更好的主意,否则我会同意。以下代码供参考: ////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// use MimeKit to parse the message
//////////////////////////////////////////////////////////////////////////////////////////
MimeKit.MimeMessage msg = MimeKit.MimeMessage.Load(stream);
//////////////////////////////////////////////////////////////////////////////////////////
// use HtmlAgilityPack to parse the resulting html in order to fix inline images
//////////////////////////////////////////////////////////////////////////////////////////
HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
hdoc.LoadHtml(msg.HtmlBody);
// find all image nodes
var images = hdoc.DocumentNode.Descendants("img");
foreach (var img in images)
{
// check that this is an inline image
string cid = img.Attributes["src"].Value;
if (cid.StartsWith("cid:"))
{
// remove the cid part of the attribute
cid = cid.Remove(0, 4);
// find image object in MimeMessage
MimeKit.MimePart part = msg.BodyParts.First(x => x.ContentId == cid) as MimeKit.MimePart;
if (part != null)
{
using (MemoryStream mstream = new MemoryStream())
{
// get the raw image content
part.ContentObject.WriteTo(mstream);
mstream.Flush();
byte[] imgbytes = mstream.ToArray();
// fix the image source by making it an embedded image
img.Attributes["src"].Value = "data:" + part.ContentType.MimeType + ";" + part.ContentTransferEncoding.ToString().ToLower() + "," +
System.Text.ASCIIEncoding.ASCII.GetString(imgbytes);
}
}
}
}
// write the resulting html to the output stream
hdoc.Save(outputStream);
您的解决方案与我在中使用的逻辑类似,但现在MimeKit提供了更好的解决方案:
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a 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 a new HtmlPreviewVisitor.
/// </summary>
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor (string tempDirectory)
{
tempDir = tempDirectory;
}
/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments {
get { return attachments; }
}
/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody {
get { return body ?? string.Empty; }
}
protected override void VisitMultipartAlternative (MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && body == null; i--)
alternative[i].Accept (this);
}
protected override void VisitMultipartRelated (MultipartRelated related)
{
var root = related.Root;
// push this multipart/related onto our stack
stack.Add (related);
// visit the root document
root.Accept (this);
// pop this multipart/related off our stack
stack.RemoveAt (stack.Count - 1);
}
// look up the image based on the 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;
}
for (int i = stack.Count - 1; i >= 0; i--) {
if ((index = stack[i].IndexOf (uri)) == -1)
continue;
image = stack[i][index] as MimePart;
return image != null;
}
image = null;
return false;
}
// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "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 that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
ctx.WriteTag (htmlWriter, false);
// replace the src attribute with a 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 the tag through to the output
ctx.WriteTag (htmlWriter, true);
}
}
protected override void VisitTextPart (TextPart entity)
{
TextConverter converter;
if (body != null) {
// since we've already found the body, treat this as an 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 any attachments in the MS-TNEF part
attachments.AddRange (entity.ExtractAttachments ());
}
protected override void VisitMessagePart (MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add (entity);
}
protected override void VisitMimePart (MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add (entity);
}
}
我知道这看起来像是很多代码,但它涵盖的不仅仅是简单的案例。您会注意到,它还处理渲染text/plain
以及text/plain;如果HTML不可用,则format=流动的
正文。它还正确地仅使用封装多部分/相关树中的图像
修改此代码的一种方法是将图像嵌入img
标记中,而不是使用临时目录。要做到这一点,您需要将SaveImage
方法修改为类似以下内容(请注意,下一段代码未经测试):
看起来我基本上走在正确的轨道上,但您的解决方案要比您指出的全面得多。这很有帮助。谢谢。没问题,是的,你肯定走对了方向:-)很好的解决方案!实验性的SaveImage正在使用.Net 4.5.2的web环境中运行。@jstedfast这与mimessage.HtmlBody
相比如何?如果这是更全面的,那么为什么不把它放在库中呢?MimeMessage.HtmlBody
只返回一个字符串,其中包含text/html
正文部分的内容,但是如果消息中有嵌入的图像,html通过cid:
URL引用,然后在浏览器控件上设置HtmlBody
字符串将无法找到这些图像HtmlPreviewVisitor
不是库的一部分,因为每个希望呈现消息的邮件客户端应用程序可能希望以不同的方式呈现消息。Outlook呈现的邮件与Thunderbird不同,Thunderbird呈现的邮件与GMail不同,等等。HtmlPreviewVisitor
实际上只是一个起点。
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;
}
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));
}
}