6

I'm creating an email with a MIME attachment from a BizTalk 2016 SMTP Send Port. However, I think any knowledge that anyone can share from any other language about the oddities of Outlook and MIME might help me fix the issue below.

In Outlook, the attachment shows as body.txt, but when I click "File Save" it shows the name that I used when I created it (and that's what the user wants to see).

What I'm referring to is the the left side where it says "body.txt" above the 5k and to the right of the attachment icon in the screen shot below:

enter image description here

In BizTalk C# Pipeline component, that attachment was set with the following code, where we are setting Context properties on the BizTalk Message. I also tried setting ContentHeader and ContentID.

strFilename = "MyFileName_693.txt";  // Just for example. 
pInMsg.BodyPart.PartProperties.Write(
              "FileName",
              "http://schemas.microsoft.com/BizTalk/2003/mime-properties",
               strFilename);

When I forwarded the email to my Gmail, the attachment was shown with the proper name. So my question is particular to making it appear with the desired name in Outlook (2016).

Dijkgraaf
  • 11,049
  • 17
  • 42
  • 54
NealWalters
  • 17,197
  • 42
  • 141
  • 251
  • See https://social.msdn.microsoft.com/Forums/en-US/988b0d91-1e5a-4f73-b30d-417d6ea9fa75/attachment-name-in-outlook-is-ok-see-on-exchange-always-named-body?forum=biztalkgeneral and http://stackoverflow.com/questions/39407158/why-is-the-email-atachment-i-sent-by-biztalk-body-csv-in-outlook-2016 Are you setting the (MIME.FileName) property as well? – Dijkgraaf May 11 '17 at 21:23
  • My question is almost a duplicate of that one, except I'm in a pipeline component and he's in an orchestration. My code above is how you set MIME.FileName in a pipeline. The MSDN link gave me this clue - which 'm going to try: And after that Make sure that you r adding your custom pipeline component after MIME Encoder component in Encode stage of send pipeline. I had tried it after, but not before! – NealWalters May 15 '17 at 17:14
  • The MSDN is same issue, but he is in orchestration as well; they talk about using a dynamic pipeline. But so far, I have a content-based routing solution with no orchestrations. I tried the MIME encoder and results got worse, the attachment then was called "Attachment" when I did "File Save". This seems to be related to more recently versions of Outlook, as it looks fine in GMail. In the MSN Post, he was showing Outlook 2013 vs Outlook 2016. – NealWalters May 15 '17 at 18:36
  • Still haven't got it to work, going to try an orchestration as per that example to see if anything different happens. But I really wanted to do it in Pipeline. – NealWalters May 16 '17 at 17:45
  • If you get it working via an Orchestration, you can then compare the MIME messages produced and see what the difference is. Then it might be possible do apply that to the Pipeline component as well – Dijkgraaf May 16 '17 at 21:15
  • Got this working on BT2016 in Dec 2020. See also: https://stackoverflow.com/questions/65509140/biztalk-smtp-the-part-partattachment-of-message-msg-email-contained-a-null-v/65509355#65509355 and https://stackoverflow.com/questions/65480570/biztalk-setting-email-contenttype-causes-error-there-is-an-error-in-xml-docum/65496694?noredirect=1#comment115797600_65496694 – NealWalters Dec 30 '20 at 18:58

1 Answers1

2

So far I've got this working with an orchestration with a dynamic send port. It's still a bit of work, but it gets the job done with the stock component. Following description is based on the stock SMTP-adapter included in BizTalk 2013R2.

Note: even though my solution works, it feels like a workaround and something i shouldn't have to do, if the adapter was just slightly smarter about this.

First of all, let's look example email snippet which causes issues in some clients:

------=_NextPart_000_0001_01D4502F.8A6A1500
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="utf-8"

See attached email.
------=_NextPart_000_0001_01D4502F.8A6A1500
Content-Type: application/pdf; name="CDM_Order - Copy.pdf"
Content-Disposition: attachment; filename="CDM_Order - Copy.pdf"
Content-Description: body
Content-Transfer-Encoding: base64

JVBERi0xLjQKJeLjz9MNCjUgMCBvYmoKPDwvRFsgMyAwIFIvWFlaIG51bGwgODQxLjg4OTc3IG51
bGwgXQo+PgplbmRvYmoKOCAwIG9iago8PC9EWyAzIDAgUi9YWVogbnVsbCAyOTAuMjM2NTcgbnVs
bCBdCj4+ (etc etc base64 your file)...

Notice the Content-Description: body part. This is the reason why some clients read body.xml or in my case body.pdf, even though the Disposition part looks great: Content-Disposition: attachment; filename="CDM_Order - Copy.pdf".

Hard setting MIME.FileName isn't just going to work, even though it will set the Content-Disposition right eventually, it'll never update the Content-Description. This is because either on a static send port you've set the Attach only body part or you specified the corresponding numeric value 1 on a dynamic send port.

However, it will work with the Attach all parts or 2 value for the type MessagePartsAttachments. This involves making a multi-part message in your orchestration. This will have two parts;

  • First one is the BodyPart, now this one will include your message text and not your attachment. Make sure you specify this one as Message Body Part in the Message Type.
  • Second part will be your actual attachment, specify this type according to your attachment type. I named this Attachment in this example.

Now you might think it will send the BodyPart as attachment as well since i've said we needed Attach all parts. This is true, so to correct that, your BodyPart has to be defined as a RawString, this turns the string into plain text in the BizTalk message part. For completeness i'll put the C# class at the bottom for reference.

Now that it's defined as a RawString, the SMTP adapter will put this as the body instead of as attachment. As a side effect, the SMTP adapter will no longer put the Content-Description: body part in the attachment part, but in the actual body part instead. It looks like this:

------=_NextPart_000_0001_01D450E4.A7E9A5E0
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="utf-8"
Content-Description: body

See attached email.
------=_NextPart_000_0001_01D450E4.A7E9A5E0
Content-Type: application/pdf; name="ID_0_Nummer_0.pdf"
Content-Disposition: attachment; filename="ID_0_Nummer_0.pdf"
Content-Transfer-Encoding: base64

JVBERi0xLjQKJeLjz9MNCjUgMCBvYmoKPDwvRFsgMyAwIFIvWFlaIG51bGwgODQxLjg4OTc3IG51
bGwgXQo+PgplbmRvYmoKOCAwIG9iago8PC9EWyAzIDAgUi9YWVogbnVsbCAyOTAuMjM2NTcgbnVs
bCBdCj4+ (etc etc base64 your file)...

Really nothing else is different except the placement of the Content-Description: body part, exactly what we want. Now the email looks fine for every client.

The most important properties, besides the ones i already mentioned, must be set as well to make it behave properly:

Content type of your body:

MsgPdfOrder.BodyPart(Microsoft.XLANGs.BaseTypes.ContentType) = "text/plain";

Content type of your attachment:

MsgPdfOrder.Attachment(Microsoft.XLANGs.BaseTypes.ContentType) = "application/pdf";

Attachment filename:

MsgPdfOrder.Attachment(MIME.FileName) =  "CDM_Order - Copy.pdf"

Body character set (will result in Unknown Error Description if not set):

MsgPdfOrder(SMTP.EmailBodyTextCharset) = "UTF-8";

Make sure you don't set the SMTP.EmailBodyText because we already have the BodyPart for that.

RawString class, use it like this in an orchestration MsgPdfOrder.BodyPart = new Yournamespace.Components.RawString("See attached email."); :

using System.Runtime.Serialization;
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.XLANGs.BaseTypes;

namespace Yournamespace.Components
{
    public abstract class BaseFormatter : IFormatter
    {
        public virtual SerializationBinder Binder
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual StreamingContext Context
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual ISurrogateSelector SurrogateSelector
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public abstract void Serialize(Stream stm, object obj);
        public abstract object Deserialize(Stream stm);
    }

    public class RawStringFormatter : BaseFormatter
    {
        public override void Serialize(Stream s, object o)
        {
            RawString rs = (RawString)o;
            byte[] ba = rs.ToByteArray();
            s.Write(ba, 0, ba.Length);
        }

        public override object Deserialize(Stream stm)
        {
            StreamReader sr = new StreamReader(stm, true);
            string s = sr.ReadToEnd();
            return new RawString(s);
        }
    }

    [CustomFormatter(typeof(RawStringFormatter))]
    [Serializable]
    public class RawString
    {
        [XmlIgnore]
        string _val;

        public RawString(string s)
        {
            if (null == s)
                throw new ArgumentNullException();
            _val = s;
        }

        public RawString()
        {
        }

        public byte[] ToByteArray()
        {
            return Encoding.UTF8.GetBytes(_val);
        }

        public override string ToString()
        {
            return _val;
        }
    }
}
r3verse
  • 1,000
  • 8
  • 19
  • 1
    Great! I'm no longer even working at that client; but I'm sure your answer will be useful to someone in the future. – NealWalters Sep 21 '18 at 17:43
  • Now I'm trying this again at another client. Just posted a related issue: I posted a new related issue. When I try to set contentType I get a run time error. https://stackoverflow.com/questions/65480570/biztalk-setting-email-contenttype-causes-error – NealWalters Dec 28 '20 at 16:24