IRS-A2A BulkRequestTransmitter message not formmatted properly and/or cannot be interpreted

Don’t know if it will resolve your issue, but nevertheless i give it a shot. Sometimes help comes from very unexpected sources 🙂

  1. First of all timestamp fields are in a wrong format: one in
    businessheader should NOT contain milliseconds at all. I know it for
    a fact.
  2. In security header timestamp only 3 digits are allowed to represent
    milliseconds.
  3. Remove empty elements like “OriginalReceiptId” from ACATransmitterManifestReqDtl element: they don’t like those.
  4. I hope you are providing them with proper software id, because you have it empty in the payload, but I am sure they would love to have it, imho.:)
  5. And I think the message you’ve got in the response also have something to do with Signature element. I think they want Signature element to have some prefix(“ds” preferably, I guess). But here I am not sure on 100%.

    You see, I am battling same battle as you. And my message security timestamp has prefix “u” and they do not complain about it. Though they didn’t like binarysecuritytoken ever.:) I am struggling to generate signature to the IRS liking. WCF is very secretive and does not allow easy prefix changing on soap envelope or allow to choose CanonicalizationMethod algorithm for a signature.

UPDATE: Been able to successfully send request to the service. Tell you at once: prefixes are unimportant. What was important: CorrectedInd tag must be present in Form1095BUpstreamDetail and attributes recordType=”String” lineNum=”0″ also must be present.

UPDATE2:
Another thing that I’ve changed I’ve placed ACABusinessHeader before ManifestDtl.
Here are my settings: I am using WCF as carrier and SignedXml to generate signature. Also I am using custom gZip encoder(for obvious reasons0 and custom MtomEncoder to read response from service(yes, yes it’s MTOMed:)) can you believe those pokemons?!?!?) and that’s not all: they send response as multipart document with only 1 part!:)) I had to adjust my encoder to handle that. And voilà, service started to behave. Hope it might help.

UPDATE3
First of all make sure data in attachment file correspond to the test scenario you are using as guinea pig. I, probably, sound like a broken record, but that’s REALLY important.
Now I’ll cut the stuff and present what I have. It’s a bit crude, but it does the trick.:)

1.Here is config file portion:
1.1.Make sure system.serviceModel element contains following portion:

<extensions>
  <bindingElementExtensions>
    <add name="gzipMessageEncoding" type="<namespaceWhereEncoderLives>.GZipMessageEncodingElement, GZipEncoder, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </bindingElementExtensions>
</extensions>  

1.2. Make sure binding element contains this:

  <customBinding>
    <binding name="BulkRequestTransmitterBinding">
      <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
      <httpsTransport />
    </binding>
  </customBinding>

1.3. Change binding of BulkRequestTransmitterPort endpoit under client element to “customBinding”(and change binding name to the name of the custom binding as well) and make sure it contains following portion:

    <identity>
      <dns value="domain from cert" />
    </identity>

Also client element should contain following portion:

  <metadata>
    <policyImporters>
      <extension type="NamespaceToToTheLocationOf.GZipMessageEncodingBindingElementImporter, GZipMessageEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </policyImporters>
  </metadata>
  1. GZip encoder you could get from following link:
    https://msdn.microsoft.com/en-us/library/cc138373(v=vs.90).aspx
    Just download WCF example and dully move whole GZipMessageEncoder project under your project.

  2. Get MTOMEncoder(which I renamed from SwaEncoder for clarity reasons) from this link:
    Soap-with-Attachments
    Move following classes into GZipMessageEncoder project:
    MimeContent, MimeParser, MimePart, MTOMEncoder

  3. Modify GZipMessageEncoder class like this:
    4.1. Add following code at the beginning of the class:

       //------------------- MTOM related stuff. Begin. ---------------------
        const string ATTCHMNT_PROP = "attachment_file_content";
        const string ATTCHMNT_CONTENT_ID = "Here goes content id";
    
        private string _ContentType;
        private string _MediaType;
    
        protected MimeContent _MyContent;
        protected MimePart _SoapMimeContent;
        protected MimePart _AttachmentMimeContent;
        protected GZipMessageEncoderFactory _Factory;
        protected MimeParser _MimeParser;
        private void SetupMTOM(GZipMessageEncoderFactory factory)
        {
            //
            _ContentType = "multipart/related";
            _MediaType = _ContentType;
    
            //
            // Create owned objects
            //
            _Factory = factory;
            _MimeParser = new MimeParser();
    
            //
            // Create object for the mime content message
            // 
            _SoapMimeContent = new MimePart()
            {
                ContentTypeStart = "application/xop+xml",
                ContentType = "text/xml",
                ContentId = "Here goes envelope MIME id from HTTP Content-Type header",   // TODO: make content id dynamic or configurable?
                CharSet = "UTF-8",                                  // TODO: make charset configurable?
                TransferEncoding = "8bit"                         // TODO: make transfer-encoding configurable?
            };
            _AttachmentMimeContent = new MimePart()
            {
                ContentType = "application/xml",                    // TODO: AttachmentMimeContent.ContentType configurable?
                ContentId = ATTCHMNT_CONTENT_ID,                    // TODO: AttachmentMimeContent.ContentId configurable/dynamic?
                TransferEncoding = "7bit"                         // TODO: AttachmentMimeContent.TransferEncoding dynamic/configurable?
            };
            _MyContent = new MimeContent()
            {
                Boundary = "here goes boundary id"  // TODO: MimeContent.Boundary configurable/dynamic?
           };
            _MyContent.Parts.Add(_SoapMimeContent);
            _MyContent.Parts.Add(_AttachmentMimeContent);
            _MyContent.SetAsStartPart(_SoapMimeContent);
        }
        //------------------- MTOM related stuff. End. ----------------------
    

4.2. Modify Method WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) like this:

public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
        {
            ArraySegment<byte> buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, 0);
            var requestSOAPEnvelopeXml = System.Text.Encoding.UTF8.GetString(buffer.Array);

            //Here you create Security node and sign the request. For ex:
            requestSOAPEnvelopeXml = SigngEnvelope(requestSOAPEnvelopeXml);
            //Here you are getting 1094\1095 forms xml payload.
            string fileContent = GetAttachmentFileContent();

            //Here comes the MTOMing...
            _SoapMimeContent.Content = System.Text.Encoding.UTF8.GetBytes(requestSOAPEnvelopeXml);
            _AttachmentMimeContent.Content = System.Text.Encoding.UTF8.GetBytes(fileContent);

            _MyContent.Parts.Where(m=> m.ContentId!=null && m.ContentId.Equals(ATTCHMNT_CONTENT_ID)).Single().ContentDisposition = GetFileName(envelope);
            // Now create the message content for the stream
            byte[] MimeContentBytes = _MimeParser.SerializeMimeContent(_MyContent);
            int MimeContentLength = MimeContentBytes.Length;

            // Write the mime content into the section of the buffer passed into the method
            byte[] TargetBuffer = bufferManager.TakeBuffer(MimeContentLength + messageOffset);
            Array.Copy(MimeContentBytes, 0, TargetBuffer, messageOffset, MimeContentLength);

            // Return the segment of the buffer to the framework
            return CompressBuffer(new ArraySegment<byte>(TargetBuffer, messageOffset, MimeContentLength), bufferManager, messageOffset);                
        }

4.3. Override couple more methods like this:

public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
        {
            ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager);

            MtomEncoder mtomEncoder = new MtomEncoder(innerEncoder, _Factory);
            Message returnMessage = mtomEncoder.ReadMessage(buffer, bufferManager, contentType);
            returnMessage.Properties.Encoder = mtomEncoder;

            return returnMessage;
        }

        public override bool IsContentTypeSupported(string contentType)
        {
            return true;
        }

4.4. Make sure GZipMessage constructor looks like this:

        internal GZipMessageEncoder(MessageEncoder messageEncoder, GZipMessageEncoderFactory factory)
            : base()
        {
            if (messageEncoder == null)
                throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder");
            innerEncoder = messageEncoder;

            SetupMTOM(factory);
        }

5. Make sure GZipMessageEncodingBindingElement class has following method:

    public override void ApplyConfiguration(BindingElement bindingElement)
    {
        GZipMessageEncodingBindingElement binding = (GZipMessageEncodingBindingElement)bindingElement;
        PropertyInformationCollection propertyInfo = this.ElementInformation.Properties;
        if (propertyInfo["innerMessageEncoding"].ValueOrigin != PropertyValueOrigin.Default)
        {
            switch (this.InnerMessageEncoding)
            {
                case "textMessageEncoding":
                    binding.InnerMessageEncodingBindingElement = 
                        new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
                    break;
                case "binaryMessageEncoding":
                    binding.InnerMessageEncodingBindingElement = new BinaryMessageEncodingBindingElement();
                    break;
            }
        }
    }
  1. Modify MTOMEncoder class. Make sure that following method looks like this:

    public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType)
    {
        VerifyOperationContext();
    
        if (contentType.ToLower().StartsWith("multipart/related"))
        {
            byte[] ContentBytes = new byte[stream.Length];
            stream.Read(ContentBytes, 0, ContentBytes.Length);
            MimeContent Content = _MimeParser.DeserializeMimeContent(contentType, ContentBytes);
    
            if (Content.Parts.Count >= 1)
            {
                MemoryStream ms = new MemoryStream(Content.Parts[0].Content);
                //At least for now IRS is sending SOAP envelope as 1st part(and only part(sic!) of MULTIpart response) as xml. 
                Message Msg = ReadMessage(ms, int.MaxValue, "text/xml");//Content.Parts[0].ContentType);
    
                if( Content.Parts.Count>1 )
                    Msg.Properties.Add(ATTCHMNT_PROP, Content.Parts[1].Content);
    
                return Msg;
            }
            else
            {
                throw new ApplicationException("Invalid mime message sent! Soap with attachments makes sense, only, with at least 2 mime message content parts!");
            }
        }
        else if (contentType.ToLower().StartsWith("text/xml"))
        {
            XmlReader Reader = XmlReader.Create(stream);
            return Message.CreateMessage(Reader, maxSizeOfHeaders, MessageVersion);
        }
        else
        {
            throw new ApplicationException(
                string.Format(
                    "Invalid content type for reading message: {0}! Supported content types are multipart/related and text/xml!",
                    contentType));
        }
    }
    
  2. GZipMessageEncoderFactory class constructor should look like this:

       public GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory)
    {
        if (messageEncoderFactory == null)
            throw new ArgumentNullException("messageEncoderFactory", "A valid message encoder factory must be passed to the GZipEncoder");
        encoder = new GZipMessageEncoder(messageEncoderFactory.Encoder, this);
    }
    
  3. This is how I call the service:

       var requestClient = new BulkRequestTransmitterPortTypeClient("BulkRequestTransmitterPort");
    
            requestClient.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
     #if DEBUG
            var vs = requestClient.Endpoint.Behaviors.Where((i) => i.GetType().Namespace.Contains("VisualStudio"));
            if( vs!=null )
             requestClient.Endpoint.Behaviors.Remove((System.ServiceModel.Description.IEndpointBehavior)vs.Single());
    #endif                
       using (var scope = new OperationContextScope(requestClient.InnerChannel))
            {
                 //Adding proper HTTP Header to an outgoing requqest.
                HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
    
                requestMessage.Headers["Content-Encoding"] = "gzip";
                requestMessage.Headers["Content-Type"] = @"multipart/related; type=""application/xop+xml"";start=""<Here goes envelope boundary id>"";start-info=""text/xml"";boundary=""here goes boundary id""";
                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
    
                response = requestClient.BulkRequestTransmitter(request.ACASecurityHeader,
                                                                    request.Security, ref request.ACABusinessHeader,
                                                                    request.ACATransmitterManifestReqDtl, 
                                                                    request.ACABulkRequestTransmitter);
            }
    
  4. Modify Mime Part:

9.1. Add new method:

    public void GetHeader(StringBuilder Builder)
    {
        if (string.IsNullOrEmpty(ContentId) && string.IsNullOrEmpty(ContentType) && string.IsNullOrEmpty(TransferEncoding))
            return;

        if (!string.IsNullOrEmpty(ContentTypeStart))
        {
            Builder.Append(string.Format("Content-Type: {0}", ContentTypeStart));
            Builder.Append(string.Format("; type=\"{0}\"", ContentType));
        }
        else
            Builder.Append(string.Format("Content-Type: {0}", ContentType));

        if (!string.IsNullOrEmpty(CharSet)) Builder.Append(string.Format("; charset={0}", CharSet));
        Builder.Append(new char[] { '\r', '\n' });
        Builder.Append(string.Format("Content-Transfer-Encoding: {0}", TransferEncoding));
        Builder.Append(new char[] { '\r', '\n' });
        Builder.Append(string.Format("Content-Id: {0}", ContentId));
        Builder.Append(new char[] { '\r', '\n' });
        if (!string.IsNullOrEmpty(ContentDisposition))
            Builder.Append(string.Format("Content-Disposition: attachment; filename=\"{0}\"", ContentDisposition));
    }

9.2. Add property:

   public string ContentDisposition { get; set; }
  1. Modify MimeParser SerializeMimeContent() method:
    replace this block of code:

           Builder.Append(string.Format("Content-Type: {0}", item.ContentType));
            if (!string.IsNullOrEmpty(item.CharSet)) Builder.Append(string.Format("; charset={0}", item.CharSet));
            Builder.Append(new char[] { '\r', '\n' });
            Builder.Append(string.Format("Content-Transfer-Encoding: {0}", item.TransferEncoding));
            Builder.Append(new char[] { '\r', '\n' });
            Builder.Append(string.Format("Content-Id: {0}", item.ContentId));
    

with this:

item.GetHeader(Builder);

And that’s should be it! Kick off your shoes and dig the blues!:)))

Leave a Comment