How Set Attachment Name to Show Properly in Outloo

2019-02-17 09:49发布

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).

1条回答
等我变得足够好
2楼-- · 2019-02-17 10:04

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;
        }
    }
}
查看更多
登录 后发表回答