You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BizTalk无编排自定义发送管道组件实现带附件SMTP邮件发送问题

核心问题排查

你的消息卡在「已排队(等待处理)」及功能异常由以下几类错误导致:

  • 输入流读取后未重置位置:使用XPathDocument读取输入消息的BodyPart流后,流指针停在末尾,后续管道组件无法读取消息内容,是消息挂起的核心原因
  • 正文内容未写入流:仅配置了正文相关属性,未将实际正文内容写入BodyPart的数据流
  • 正文ContentType配置错误:text/application为非法格式,纯文本正文应使用text/plain,HTML正文应使用text/html
  • 附件对象重复创建问题:仅在循环外创建一次消息Part对象,导致所有附件最终都为最后一个附件的内容
  • SMTP属性配置位置错误:Subject、EmailBodyText等SMTP属性需配置在消息上下文而非消息Part的属性中,SMTP适配器无法识别Part属性中的配置
  • 数据流位置未重置:所有新创建的MemoryStream写入内容后未将指针重置到起始位置,后续读取会返回空内容
  • 变量拼写错误:Attachment类的文件名属性拼写混乱,会引发空引用或文件名错误
修正后完整代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.XPath;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;

public class CustomSendPipelineComponent : IBaseComponent, IComponent, IComponentUI
{
    public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
    {
        XPathNavigator nav = ReadXmlFromMsgBox(pInMsg);

        var outMsg = pContext.GetMessageFactory().CreateMessage();
        outMsg.Context = PipelineUtil.CloneMessageContext(pInMsg.Context);  

        GetRegistratorProperties(nav, pContext, outMsg);

        var attachments = GetAttachments(nav);

        ProcessAttachments(pContext, outMsg, attachments);

        return outMsg;
    }

    private void GetRegistratorProperties(XPathNavigator _nav, IPipelineContext _pContext, IBaseMessage _msg)
    {
        // 从XML读取UUID作为正文内容
        string uuid = _nav.SelectSingleNode("ArchiveobjectsListErrands/ArchiveobjectErrand/UUID")?.Value ?? "无业务UUID";
        string emailBody = $"业务单据UUID:{uuid}\r\n业务处理完成,附件请查收";
        
        var bodyPart = _pContext.GetMessageFactory().CreateMessagePart();             
        bodyPart.ContentType = "text/plain;charset=utf-8";  
        
        // 写入正文内容到流
        byte[] bodyBytes = System.Text.Encoding.UTF8.GetBytes(emailBody);
        bodyPart.Data = new MemoryStream(bodyBytes);
        bodyPart.Data.Seek(0, SeekOrigin.Begin);

        // SMTP属性写入消息上下文,而非Part属性
        _msg.Context.Write("EmailBodyText", "http://schemas.microsoft.com/BizTalk/2003/smtp-properties", emailBody);
        _msg.Context.Write("Subject", "http://schemas.microsoft.com/BizTalk/2003/smtp-properties", "Registratorsubject - Create errand");
        _msg.Context.Write("SMTPMessageBody", "http://schemas.microsoft.com/BizTalk/2003/smtp-properties", "1"); // 标记使用自定义正文

        _msg.AddPart("Body", bodyPart, true);
    }

    private void ProcessAttachments(IPipelineContext _pContext, IBaseMessage _msg, IList<Attachment> _attachments)
    {
        int i = 0;            
        foreach (var item in _attachments)
        {    
            // 每次循环新建独立的Part对象
            var msgPart = _pContext.GetMessageFactory().CreateMessagePart();
            msgPart.PartProperties.Write("FileName", "http://schemas.microsoft.com/BizTalk/2003/mime-properties", $"{item.FileName}{item.Extension}");
            msgPart.PartProperties.Write("ContentDescription", "http://schemas.microsoft.com/BizTalk/2003/mime-properties", item.ContentType);
            
            byte[] attachmentBytes = BytesFromBase64String(item.Base64String);
            msgPart.Data = new MemoryStream(attachmentBytes);
            msgPart.Data.Seek(0, SeekOrigin.Begin);
            
            msgPart.ContentType = item.ContentType;  
            _msg.AddPart($"Attachment part {i}", msgPart, false); 
            i++;
        }
    }

    private IList<Attachment> GetAttachments(XPathNavigator _nav)
    {
        // 替换为你实际的附件XPath路径
        XPathNodeIterator iterator =  _nav.Select("ArchiveobjectsListErrands/ArchiveobjectErrand/ArchiveobjectListPaper/Attachments");
        IList<Attachment> myList = new List<Attachment>();                        
        
        while (iterator.MoveNext())
        {
            XPathNavigator node = iterator.Current;
            Attachment attachment = new Attachment();              
            attachment.FileName = node.SelectSingleNode("Name")?.Value ?? "未命名附件";
            attachment.Extension = node.SelectSingleNode("Extension")?.Value ?? ".dat";
            attachment.ContentType = node.SelectSingleNode("Mimetype")?.Value ?? "application/octet-stream";
            attachment.Base64String = node.SelectSingleNode("Base64String")?.Value ?? "";
            if(!string.IsNullOrEmpty(attachment.Base64String))
                myList.Add(attachment);
        }
        return myList;
    }

    private XPathNavigator ReadXmlFromMsgBox(IBaseMessage pInMsg)
    {
        Stream originalMessage = pInMsg.BodyPart.GetOriginalDataStream();
        XPathNavigator _navigator = new XPathDocument(originalMessage).CreateNavigator();
        // 读取完成后重置输入流位置,供后续组件使用
        originalMessage.Seek(0, SeekOrigin.Begin);
        return _navigator;
    }        

    private static byte[] BytesFromBase64String(string base64Str)
    {
        if(string.IsNullOrEmpty(base64Str)) return Array.Empty<byte>();
        return Convert.FromBase64String(base64Str);
    }

    [Serializable]
    private class Attachment
    {
        public string FileName { get; set; }
        public string Extension { get; set; }
        public string ContentType { get; set; }
        public string Base64String { get; set; }
    }

    #region 管道组件基础实现(保留向导生成的内容即可)
    public string Name => "自定义SMTP发送管道组件";
    public string Version => "1.0.0.0";
    public string Description => "处理Base64附件并生成SMTP邮件";
    public IntPtr Icon => IntPtr.Zero;
    public System.Collections.IEnumerator Validate(object projectSystem) => null;
    #endregion
}
部署验证注意事项
  • 发送管道编码阶段组件顺序:自定义组件在前,MIME/SMIME编码器在后
  • SMTP适配器配置:需开启「正文为MIME部分」选项,否则自定义添加的附件不会被识别
  • 组件部署:更新代码后需将DLL加入GAC,重启对应主机实例后再测试

内容的提问来源于stack exchange,提问作者Emperor 2052

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 23:36:06