I am receiving the following error when attempting to submit through the BulkRequestTransmitter Web Service. The Composition Guide is less than helpful as far as this messa
First off, a quick disclaimer. This answer was made possible by the great stuff provided by fatherOfWine, Russ, and Bon across this and other SO questions. All I really did was combine a bunch of stuff from them them and hack through the issues I still had until it worked. More importantly, the code provided here is BAD and should probably not be used as-is. I plan on cleaning this up quite a bit now that I know what works, and I'd recommend anyone making use of it to do the same. A big thing that will likely jump out to anyone looking at this is the plethora of static variables I used as a quick hack to get to things all through the pipeline. Seriously, don't use this as-is in production, it is the product of many hours of just throwing things at the wall until something stuck, but it should provide a good starting point to get something better going.
There's too much code to really include it all here, so I'll just go through some highlights and general discoveries then include a link to the VS solution.
Outside of getting all the gzip and MTOM stuff setup (again, thanks a million to fatherOfWine for that help) the bulk of what finally worked for me is done in single general-use class (which I cleverly called "General"). Again, this is bad code and was the product of just needing to get something (anything!) to work properly. I'll go ahead and include it here in the answer though in case it provides a quick "ah ha!" to anyone else working this problem.
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Xml;
using IrsAcaClient.ACABulkRequestTransmitterService;
namespace IrsAcaClient
{
public class General
{
/*****************************************************
*
* What I'm doing here (with static vars) is VERY BAD but this whole thing is just a dirty hack for now.
* Hopefully I can clean this up later.
* - JRS 2016-05-10
*
*****************************************************/
public const string SecurityTimestampStringFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffZ";
public const string EnvelopeContentID = "<rootpart>";
public static string AttachmentFilePath;
public static string AttachmentFileName { get { return Path.GetFileName(General.AttachmentFilePath); } }
public static string AttachmentContentID {get { return string.Format("<{0}>", General.AttachmentFileName); }}
public const string MIMEBoundary = "MIME_boundary";
public static string TCCode;
public static Guid TransmissionGuid;
public static string UniqueTransmissionId
{
get { return string.Format("{0}:SYS12:{1}::T", TransmissionGuid, TCCode); }
}
public static string SecurityTimeStampWsuId;
public static string ManifestWsuId;
public static string BusinessHeaderWsuId;
public static string SignatureWsuId;
public static string CertificatePath;
public static string CertificatePassword;
public static DateTime SecurityTimestampUTC;
private static string _replacementSoapEnvelope;
public static string ReplacementSoapEnvelope{get { return _replacementSoapEnvelope; }}
private static void GenerateReference(string elementID, string inclusivePrefixList, SignedXmlWithId xSigned)
{
var reference = new Reference()
{
Uri = "#" + elementID
};
XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
env.InclusiveNamespacesPrefixList = inclusivePrefixList;
reference.AddTransform(env);
xSigned.AddReference(reference);
}
public static string GetAttachmentFileContent()
{
//probably not ideal
return File.ReadAllText(AttachmentFilePath);
}
public static string GetFileName()
{
//TODO: this may need to be tweaked slightly from the real filename
return General.AttachmentFileName;
}
public static string GenerateWsuId(string prefix)
{
return string.Format("{0}-{1}", prefix, Guid.NewGuid().ToString().Replace("-", "").ToUpper());
}
internal static void GenerateReplacementSoapEnvelope(ACABulkRequestTransmitterService.SecurityHeaderType securityHeader, ACABulkRequestTransmitterService.ACABulkBusinessHeaderRequestType businessHeader, ACABulkRequestTransmitterService.ACATrnsmtManifestReqDtlType manifest, ACABulkRequestTransmitterService.ACABulkRequestTransmitterType bulkTrans)
{
//load the base envelope xml
var doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.Load("BaseSoapEnvelope.xml");
/* Need a bunch of namespaces defined
* xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
* xmlns:urn="urn:us:gov:treasury:irs:ext:aca:air:7.0"
* xmlns:urn1="urn:us:gov:treasury:irs:common"
* xmlns:urn2="urn:us:gov:treasury:irs:msg:acabusinessheader"
* xmlns:urn3="urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter"
* xmlns:wsa="http://www.w3.org/2005/08/addressing"
* xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
* xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
* xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
* xmlns:xop="http://www.w3.org/2004/08/xop/include"
*/
XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
nsMgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsMgr.AddNamespace("urn", "urn:us:gov:treasury:irs:ext:aca:air:7.0");
nsMgr.AddNamespace("urn1", "urn:us:gov:treasury:irs:common");
nsMgr.AddNamespace("urn2", "urn:us:gov:treasury:irs:msg:acabusinessheader");
nsMgr.AddNamespace("urn3", "urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter");
nsMgr.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing");
nsMgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
nsMgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
nsMgr.AddNamespace("xop","http://www.w3.org/2004/08/xop/include");
//start replacing values in it
//for securityHeader, should have the following
/*
* securityHeader.Signature.Id
* securityHeader.Timestamp.Id
* securityHeader.Timestamp.Created.Value
* securityHeader.Timestamp.Expires.Value
*/
//doc.SelectSingleNode("//wsse:Security/ds:Signature", nsMgr).Attributes["Id"].Value = securityHeader.Signature.Id;
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp", nsMgr).Attributes["wsu:Id"].Value = securityHeader.Timestamp.Id;
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Created", nsMgr).InnerText = securityHeader.Timestamp.Created.Value;
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Expires", nsMgr).InnerText = securityHeader.Timestamp.Expires.Value;
//for businessHeader, should have the following
/*
* businessHeader.UniqueTransmissionId
* businessHeader.Timestamp
* businessHeader.Id
*/
doc.SelectSingleNode("//urn2:ACABusinessHeader", nsMgr).Attributes["wsu:Id"].Value = businessHeader.Id;
doc.SelectSingleNode("//urn2:ACABusinessHeader/urn:UniqueTransmissionId", nsMgr).InnerText = businessHeader.UniqueTransmissionId;
doc.SelectSingleNode("//urn2:ACABusinessHeader/urn1:Timestamp", nsMgr).InnerText = businessHeader.Timestamp.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ");
//for manifest, should have the following, some of which will need some conversions
/*
* manifest.Id
* manifest.BinaryFormatCd - convert from enum
* manifest.PaymentYr
* manifest.PriorYearDataInd - convert from enum
* manifest.EIN
* manifest.TransmissionTypeCd - convert from enum
* manifest.TestFileCd
* manifest.TransmitterNameGrp.BusinessNameLine1Txt
* manifest.CompanyInformationGrp.CompanyNm
* manifest.CompanyInformationGrp.MailingAddressGrp.Item.AddressLine1Txt
* manifest.CompanyInformationGrp.MailingAddressGrp.Item.CityNm
* manifest.CompanyInformationGrp.MailingAddressGrp.Item.USStateCd - convert from enum
* manifest.CompanyInformationGrp.MailingAddressGrp.Item.USZIPCd
* manifest.CompanyInformationGrp.ContactNameGrp.PersonFirstNm
* manifest.CompanyInformationGrp.ContactNameGrp.PersonLastNm
* manifest.CompanyInformationGrp.ContactPhoneNum
* manifest.VendorInformationGrp.VendorCd
* manifest.VendorInformationGrp.ContactNameGrp.PersonFirstNm
* manifest.VendorInformationGrp.ContactNameGrp.PersonLastNm
* manifest.VendorInformationGrp.ContactPhoneNum
* manifest.TotalPayeeRecordCnt
* manifest.TotalPayerRecordCnt
* manifest.SoftwareId
* manifest.FormTypeCd - convert from enum
* manifest.ChecksumAugmentationNum
* manifest.AttachmentByteSizeNum
* manifest.DocumentSystemFileNm
*/
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl", nsMgr).Attributes["wsu:Id"].Value = manifest.Id;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:PaymentYr", nsMgr).InnerText = manifest.PaymentYr;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:PriorYearDataInd", nsMgr).InnerText = manifest.PriorYearDataInd.GetXmlEnumAttributeValueFromEnum();
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:EIN", nsMgr).InnerText = manifest.EIN;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TransmissionTypeCd", nsMgr).InnerText = manifest.TransmissionTypeCd.ToString();
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TestFileCd", nsMgr).InnerText = manifest.TestFileCd;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TransmitterNameGrp/urn:BusinessNameLine1Txt", nsMgr).InnerText = manifest.TransmitterNameGrp.BusinessNameLine1Txt;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:CompanyNm", nsMgr).InnerText = manifest.CompanyInformationGrp.CompanyNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn:AddressLine1Txt", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).AddressLine1Txt;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn1:CityNm", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).CityNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn:USStateCd", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).USStateCd.ToString();
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:MailingAddressGrp/urn:USAddressGrp/urn1:USZIPCd", nsMgr).InnerText = ((USAddressGrpType)manifest.CompanyInformationGrp.MailingAddressGrp.Item).USZIPCd;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactNameGrp/urn:PersonFirstNm", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactNameGrp.PersonFirstNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactNameGrp/urn:PersonLastNm", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactNameGrp.PersonLastNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:CompanyInformationGrp/urn:ContactPhoneNum", nsMgr).InnerText = manifest.CompanyInformationGrp.ContactPhoneNum;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:VendorCd", nsMgr).InnerText = manifest.VendorInformationGrp.VendorCd;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactNameGrp/urn:PersonFirstNm", nsMgr).InnerText = manifest.VendorInformationGrp.ContactNameGrp.PersonFirstNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactNameGrp/urn:PersonLastNm", nsMgr).InnerText = manifest.VendorInformationGrp.ContactNameGrp.PersonLastNm;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:VendorInformationGrp/urn:ContactPhoneNum", nsMgr).InnerText = manifest.VendorInformationGrp.ContactPhoneNum;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TotalPayeeRecordCnt", nsMgr).InnerText = manifest.TotalPayeeRecordCnt;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:TotalPayerRecordCnt", nsMgr).InnerText = manifest.TotalPayerRecordCnt;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:SoftwareId", nsMgr).InnerText = manifest.SoftwareId;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:FormTypeCd", nsMgr).InnerText = manifest.FormTypeCd.GetXmlEnumAttributeValueFromEnum();
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:BinaryFormatCd", nsMgr).InnerText = manifest.BinaryFormatCd.GetXmlEnumAttributeValueFromEnum();
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:ChecksumAugmentationNum", nsMgr).InnerText = manifest.ChecksumAugmentationNum;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn1:AttachmentByteSizeNum", nsMgr).InnerText = manifest.AttachmentByteSizeNum;
doc.SelectSingleNode("//urn:ACATransmitterManifestReqDtl/urn:DocumentSystemFileNm", nsMgr).InnerText = manifest.DocumentSystemFileNm;
//for bulkTrans, should have the following
/*
* bulkTrans.BulkExchangeFile.Include.href
*/
doc.SelectSingleNode("//urn3:ACABulkRequestTransmitter/urn1:BulkExchangeFile/xop:Include", nsMgr).Attributes["href"].Value = bulkTrans.BulkExchangeFile.Include.href;
//now do some more security setup
var cert = new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.MachineKeySet);
var exported = cert.Export(X509ContentType.Cert, CertificatePassword);
var base64 = Convert.ToBase64String(exported);
//now compute all the signing stuff
var xSigned = new SignedXmlWithId(doc);
xSigned.Signature.Id = securityHeader.Signature.Id;
// Add the key to the SignedXml document.
xSigned.SigningKey = cert.PrivateKey;
xSigned.Signature.Id = SignatureWsuId;
xSigned.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NWithCommentsTransformUrl;
var keyInfo = new KeyInfo
{
Id = GenerateWsuId("KI")
};
//need to get the keyinfo into the signed xml stuff before we compute sigs, and because it is using some stuff that
//doesn't appear to be supported out of the box we'll work around it by adding a node directly
var sbKeyInfo = new StringBuilder();
sbKeyInfo.Append("<root xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">");
sbKeyInfo.Append("<wsse:SecurityTokenReference wsu:Id=\"" + GenerateWsuId("STR") + "\">");
sbKeyInfo.Append("<wsse:KeyIdentifier EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\" ValueType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3\">" + base64.ToString());
sbKeyInfo.Append("</wsse:KeyIdentifier>");
sbKeyInfo.Append("</wsse:SecurityTokenReference>");
sbKeyInfo.Append("</root>");
XmlDocument tempDoc = new XmlDocument();
tempDoc.LoadXml(sbKeyInfo.ToString());
keyInfo.AddClause(new KeyInfoNode((XmlElement)tempDoc.FirstChild.FirstChild));
xSigned.KeyInfo = keyInfo;
GenerateReference(SecurityTimeStampWsuId, "wsse wsa soapenv urn urn1 urn2 urn3", xSigned);
GenerateReference(BusinessHeaderWsuId, "wsa soapenv urn urn1 urn3", xSigned);
GenerateReference(ManifestWsuId, "wsa soapenv urn1 urn2 urn3", xSigned);
// Compute the Signature.
xSigned.ComputeSignature();
//signing stuff must come before the timestamp or the IRS service complains
doc.SelectSingleNode("//wsse:Security", nsMgr).InsertBefore(xSigned.GetXml(), doc.SelectSingleNode("//wsse:Security", nsMgr).FirstChild);
//
_replacementSoapEnvelope = doc.OuterXml;
}
public static ACABulkRequestTransmitterResponseType Run(ACABulkRequestTransmitterService.SecurityHeaderType securityHeader, ACABulkRequestTransmitterService.ACABulkBusinessHeaderRequestType businessHeader, ACABulkRequestTransmitterService.ACATrnsmtManifestReqDtlType manifest, ACABulkRequestTransmitterService.ACABulkRequestTransmitterType bulkTrans)
{
//had some issues early on with the cert on the IRS server, this should probably be removed and retested without it
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
var acaSecurityHeader = new ACABulkRequestTransmitterService.TransmitterACASecurityHeaderType(); //leave this empty for transmitting via ISS-A2A
var requestClient = new ACABulkRequestTransmitterService.BulkRequestTransmitterPortTypeClient("BulkRequestTransmitterPort");
requestClient.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
//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());
//generate the real envelope we want
GenerateReplacementSoapEnvelope(securityHeader, businessHeader, manifest, bulkTrans);
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"] = string.Format(@"multipart/related; type=""application/xop+xml"";start=""{0}"";start-info=""text/xml"";boundary=""{1}""", General.EnvelopeContentID, General.MIMEBoundary);
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
var response = requestClient.BulkRequestTransmitter(acaSecurityHeader,
securityHeader,
ref businessHeader,
manifest,
bulkTrans);
//we got a response! now do something with it
return response;
}
}
}
Here is the complete solution, just needs all of your own data supplied (including the complete attachment file with all the payee and payer records, which is outside the scope of this but should be pretty easy to generate). Also note that this is submission of forms only, not status checks. When I get that working I'll try to remember to return and update this answer (but if someone else already has it and wants to share, that'd be pretty rad as well).
Edit for Status Service
I've combined a cleaned up version of the classes generated from the wsdl and my own junk code to get messages through and process the responses. Note that this isn't 100% tested yet, needs sanity checks, etc. but like the previous stuff should at least help anyone else struggling with this mess. Usage here is pretty straightforward:
var statusResponse = StatusService.CheckStatus(receipt, tCCode, certificatePath, certificatePassword, "https://la.www4.irs.gov/airp/aca/a2a/1095BC_Status_Request_AATS2016");
And here is the full class (with bonus generated classes namespace):
See my second answer for the status service code
Don't know if it will resolve your issue, but nevertheless i give it a shot. Sometimes help comes from very unexpected sources :)
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>
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.
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
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;
}
}
}
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));
}
}
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);
}
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);
}
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; }
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!:)))
Seconds answer to include status service, rather than just another link that could disappear.
Here is the main class:
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
namespace IrsAcaClient
{
public class StatusService
{
private const string SecurityTimestampStringFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffZ";
public static ACABulkRequestStatusService.ACABulkRequestTransmitterStatusDetailResponseType CheckStatus(string receiptID, string tCCode, string certificatePath, string certificatePassword, string statusServiceUrl)
{
//go ahead and generate some of the ids and timestamps we'll need
var securityTimeStampWsuId = GenerateWsuId("TS");
var businessHeaderWsuId = GenerateWsuId("id");
var detailRequestWsuId = GenerateWsuId("id");
var signatureWsuId = GenerateWsuId("SIG");
var securityTimestampUTC = DateTime.UtcNow;
var securityTimestampCreated = securityTimestampUTC.ToString(SecurityTimestampStringFormat);
var securityTimestampExpires = securityTimestampUTC.AddMinutes(10).ToString(SecurityTimestampStringFormat);
//build the envelope
//load the base envelope xml
var doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.Load("BaseStatusRequestEnvelope.xml");
/* Need a bunch of namespaces defined
* xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
* xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
* xmlns:urn="urn:us:gov:treasury:irs:msg:irstransmitterstatusrequest"
* xmlns:urn1="urn:us:gov:treasury:irs:ext:aca:air:7.0"
* xmlns:urn2="urn:us:gov:treasury:irs:common"
* xmlns:urn3="urn:us:gov:treasury:irs:msg:acasecurityheader"
* xmlns:wsa="http://www.w3.org/2005/08/addressing"
* xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
* xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
* xmlns:ds="http://www.w3.org/2000/09/xmldsig#");
*/
XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
nsMgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsMgr.AddNamespace("urn", "urn:us:gov:treasury:irs:msg:irstransmitterstatusrequest");
nsMgr.AddNamespace("urn1", "urn:us:gov:treasury:irs:ext:aca:air:7.0");
nsMgr.AddNamespace("urn2", "urn:us:gov:treasury:irs:common");
nsMgr.AddNamespace("urn3", "urn:us:gov:treasury:irs:msg:acasecurityheader");
nsMgr.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing");
nsMgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
nsMgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
nsMgr.AddNamespace("oas1", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
//start replacing values in it
//for securityHeader, should have the following
/*
* securityHeader.Timestamp.Id
* securityHeader.Timestamp.Created.Value
* securityHeader.Timestamp.Expires.Value
*/
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp", nsMgr).Attributes["wsu:Id"].Value = securityTimeStampWsuId;
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Created", nsMgr).InnerText = securityTimestampCreated;
doc.SelectSingleNode("//wsse:Security/wsu:Timestamp/wsu:Expires", nsMgr).InnerText = securityTimestampExpires;
//for businessHeader, should have the following
/*
* businessHeader.UniqueTransmissionId
* businessHeader.Timestamp
* businessHeader.Id
*/
doc.SelectSingleNode("//urn:ACABusinessHeader", nsMgr).Attributes["wsu:Id"].Value = businessHeaderWsuId;
doc.SelectSingleNode("//urn:ACABusinessHeader/urn1:UniqueTransmissionId", nsMgr).InnerText = GetUniqueTransmissionId(Guid.NewGuid(), tCCode);
doc.SelectSingleNode("//urn:ACABusinessHeader/urn2:Timestamp", nsMgr).InnerText = securityTimestampUTC.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ");
//for ACABulkRequestTransmitterStatusDetailRequest, should have the following
/*
* ACABulkRequestTransmitterStatusDetailRequest.Id
* ACABulkRequestTransmitterStatusDetailRequest.ACABulkReqTrnsmtStsReqGrpDtl.ReceiptId
*/
doc.SelectSingleNode("//urn:ACABulkRequestTransmitterStatusDetailRequest", nsMgr).Attributes["wsu:Id"].Value = detailRequestWsuId;
doc.SelectSingleNode("//urn:ACABulkRequestTransmitterStatusDetailRequest/urn1:ACABulkReqTrnsmtStsReqGrpDtl/urn2:ReceiptId", nsMgr).InnerText = receiptID;
//now do some more security setup
var cert = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet);
var exported = cert.Export(X509ContentType.Cert, certificatePassword);
var base64 = Convert.ToBase64String(exported);
//now compute all the signing stuff
var xSigned = new SignedXmlWithId(doc);
// Add the key to the SignedXml document.
xSigned.SigningKey = cert.PrivateKey;
xSigned.Signature.Id = signatureWsuId;
xSigned.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NWithCommentsTransformUrl;
var keyInfo = new KeyInfo
{
Id = GenerateWsuId("KI")
};
//need to get the keyinfo into the signed xml stuff before we compute sigs, and because it is using some stuff that
//doesn't appear to be supported out of the box we'll work around it by adding a node directly
var sbKeyInfo = new StringBuilder();
sbKeyInfo.Append("<root xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">");
sbKeyInfo.Append("<wsse:SecurityTokenReference wsu:Id=\"" + GenerateWsuId("STR") + "\">");
sbKeyInfo.Append("<wsse:KeyIdentifier EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\" ValueType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3\">" + base64.ToString());
sbKeyInfo.Append("</wsse:KeyIdentifier>");
sbKeyInfo.Append("</wsse:SecurityTokenReference>");
sbKeyInfo.Append("</root>");
XmlDocument tempDoc = new XmlDocument();
tempDoc.LoadXml(sbKeyInfo.ToString());
keyInfo.AddClause(new KeyInfoNode((XmlElement)tempDoc.FirstChild.FirstChild));
xSigned.KeyInfo = keyInfo;
GenerateReference(securityTimeStampWsuId, "wsse wsa oas1 soapenv urn urn1 urn2 urn3", xSigned);
GenerateReference(businessHeaderWsuId, "wsa oas1 soapenv urn1 urn2 urn3", xSigned);
GenerateReference(detailRequestWsuId, "oas1 soapenv urn1 urn2 urn3", xSigned);
// Compute the Signature.
xSigned.ComputeSignature();
//signing stuff must come before the timestamp or the IRS service complains
doc.SelectSingleNode("//wsse:Security", nsMgr).InsertBefore(xSigned.GetXml(), doc.SelectSingleNode("//wsse:Security", nsMgr).FirstChild);
//get the completed envelope
var envelope = doc.OuterXml;
//start the webrequest
//get the request object
var request = CreateWebRequest(statusServiceUrl);
//get the request stream and then get a writer on it
using (var stream = request.GetRequestStream())
using (var gz = new GZipStream(stream, CompressionMode.Compress))
using (var writer = new StreamWriter(gz))
{
//start by writing the soap envelope to the stream
writer.WriteLine(envelope);
writer.Close();
stream.Close();
}
//get the response
WebResponse response;
//let an exception get thrown up the stack
response = request.GetResponse();
//get the response stream, get a reader on it, and read the response as text
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream, Encoding.UTF8))
{
var responseText = reader.ReadToEnd();
//rip the one element (and children) we need out
var match = Regex.Match(responseText, @"<(?'prefix'[\w\d]*):ACABulkRequestTransmitterStatusDetailResponse.*<\/\k<prefix>:ACABulkRequestTransmitterStatusDetailResponse>");
return Deserialize<ACABulkRequestStatusService.ACABulkRequestTransmitterStatusDetailResponseType>(match.ToString());
}
}
private static string GetUniqueTransmissionId(Guid transmissionGuid, string tCCode)
{
return string.Format("{0}:SYS12:{1}::T", transmissionGuid, tCCode);
}
private static string GenerateWsuId(string prefix)
{
return string.Format("{0}-{1}", prefix, Guid.NewGuid().ToString().Replace("-", "").ToUpper());
}
private static void GenerateReference(string elementID, string inclusivePrefixList, SignedXmlWithId xSigned)
{
var reference = new Reference()
{
Uri = "#" + elementID
};
XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
env.InclusiveNamespacesPrefixList = inclusivePrefixList;
reference.AddTransform(env);
xSigned.AddReference(reference);
}
/// <summary>
/// creates a webrequest object and prefills some required headers and such
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static HttpWebRequest CreateWebRequest(string url)
{
//setup a web request with all the headers and such that the service requires
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ProtocolVersion = HttpVersion.Version11;
webRequest.Headers.Add(HttpRequestHeader.ContentEncoding, "gzip");
webRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate");
webRequest.ContentType = "text/xml;charset=UTF-8";
webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
webRequest.Headers.Add("SOAPAction", "RequestSubmissionStatusDetail");
webRequest.KeepAlive = true;
return webRequest;
}
/// <summary>
/// deserializes the xml string into an object
/// </summary>
/// <param name="xmlString"></param>
/// <returns></returns>
public static T Deserialize<T>(string xmlString) where T : class
{
//if the string is empty, just return null
if (xmlString.Length <= 0)
{
return null;
}
//create a serializer
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
T output;
//create the reader that the serializer will read from, passing it the string
using (var reader = new System.IO.StringReader(xmlString))
{
//rebuild the list object
output = (T)serializer.Deserialize(reader);
}
//return the list
return output;
}
}
}
Here is the relevant base xml:
<?xml version="1.0" encoding="utf-8" ?>
<soapenv:Envelope
xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:us:gov:treasury:irs:msg:irstransmitterstatusrequest"
xmlns:urn1="urn:us:gov:treasury:irs:ext:aca:air:7.0" xmlns:urn2="urn:us:gov:treasury:irs:common"
xmlns:urn3="urn:us:gov:treasury:irs:msg:acasecurityheader">
<soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Timestamp wsu:Id="XXXXXXXXXXXXXXXXXX">
<wsu:Created>XXXXXXXXXXXXXXXXXX</wsu:Created>
<wsu:Expires>XXXXXXXXXXXXXXXXXX</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
<urn:ACABusinessHeader wsu:Id="XXXXXXXXXXXXXXXXXX" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<urn1:UniqueTransmissionId>
XXXXXXXXXXXXXXXXXX
</urn1:UniqueTransmissionId>
<urn2:Timestamp>XXXXXXXXXXXXXXXXXX</urn2:Timestamp>
</urn:ACABusinessHeader>
<urn3:ACASecurityHeader />
<wsa:Action>RequestSubmissionStatusDetail</wsa:Action>
</soapenv:Header>
<soapenv:Body>
<urn:ACABulkRequestTransmitterStatusDetailRequest version="1.0" wsu:Id="XXXXXXXXXXXXXXXXXX" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<urn1:ACABulkReqTrnsmtStsReqGrpDtl>
<urn2:ReceiptId>XXXXXXXXXXXXXXXXXX</urn2:ReceiptId>
</urn1:ACABulkReqTrnsmtStsReqGrpDtl>
</urn:ACABulkRequestTransmitterStatusDetailRequest>
</soapenv:Body>
</soapenv:Envelope>
For this one, the main change I needed to make to the WSDL-generated classes was the following:
[System.SerializableAttribute()]
[XmlRoot("ACABulkRequestTransmitterStatusDetailResponse", Namespace = "urn:us:gov:treasury:irs:msg:irstransmitterstatusrequest")]
public class ACABulkRequestTransmitterStatusDetailResponseType
{
private ACABulkRequestTransmitterResponseType aCABulkRequestTransmitterResponseField;
private ACABulkReqTrnsmtStsRespGrpDtlType aCABulkReqTrnsmtStsRespGrpDtlField;
private string versionField;
public ACABulkRequestTransmitterStatusDetailResponseType()
{
this.versionField = "1.0";
}
[System.Xml.Serialization.XmlElementAttribute(Namespace = "urn:us:gov:treasury:irs:ext:aca:air:7.0", Order = 0)]
public ACABulkRequestTransmitterResponseType ACABulkRequestTransmitterResponse
{
get
{
return this.aCABulkRequestTransmitterResponseField;
}
set
{
this.aCABulkRequestTransmitterResponseField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Namespace = "urn:us:gov:treasury:irs:ext:aca:air:7.0", Order = 1)]
public ACABulkReqTrnsmtStsRespGrpDtlType ACABulkReqTrnsmtStsRespGrpDtl
{
get
{
return this.aCABulkReqTrnsmtStsRespGrpDtlField;
}
set
{
this.aCABulkReqTrnsmtStsRespGrpDtlField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
}
}
}
Adding the ds prefix is actually what is breaking this. The SignatureValue element contains the x509 hash of the serialized SignedInfo element (this is where having consistent whitespace matters).
By modifying the SignedInfo element, the authentication will fail when the IRS compares the SignatureValue hash you send against their computation of the SignedInfo hash using the certificate you uploaded and associated with the provided TCC.
Just remove your modification of the SignedInfo element and all should be good. It works for me.
This was an edit made to the above post, which added more information to jstill's post. Unfortunately, peer reviewers rejected it.
In addition to the changes jstill made to the Status' Reference.cs
file, I also had to include the BulkExchangeFileType
and IncludeFileType
updates he made to the Submission's Reference.cs
file in order to get the Deserializer
method to function partially.
The Deserializer
method will return the TransmissionStatusCd
and the ReceiptId
elements as expected, however, it will not populate the ErrorDataFile
element properly.
Since, at this time, I am unable to get the ErrorDataFile
object populated properly, I am not utilizing the ACABulkRequestTransmitterStatusDetailResponseType
object to capture the response returned from the Status Web Service. Instead, I have chosen to read the ResponseStream
into a string
object and parse the (up to) two MIME
parts of the response, and process those as necessary.
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34283")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:us:gov:treasury:irs:common")]
public partial class BulkExchangeFileType : object, System.ComponentModel.INotifyPropertyChanged
{
private IncludeType includeField;
/// <remarks/>
[System.Xml.Serialization.XmlElement(Order = 0, Namespace = "http://www.w3.org/2004/08/xop/include")]
public IncludeType Include
{
get { return this.includeField; }
set
{
this.includeField = value;
this.RaisePropertyChanged("Include");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34283")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.w3.org/2004/08/xop/include")]
public partial class IncludeType : object, System.ComponentModel.INotifyPropertyChanged
{
private System.Xml.XmlNode[] anyField;
private string hrefField;
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
[System.Xml.Serialization.XmlAnyElementAttribute(Order = 0)]
public System.Xml.XmlNode[] Any
{
get { return this.anyField; }
set
{
this.anyField = value;
this.RaisePropertyChanged("Any");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute(DataType = "string")]
public string href
{
get { return this.hrefField; }
set
{
this.hrefField = value;
this.RaisePropertyChanged("href");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
For those running into the following error:
AIRMF3002 Rejected transmission - Unable to process your request because validation failure occurs in the Attachment Byte Size Number
It appears there is an issue on the IRS side with the size they are expecting (as per the Documentation) and the size they actually accept. Originally, I had the following code:
// Size in Bytes of File: This code returns the "Size" located on the File's Property Page.
// Result: TRANSMISSION REJECTED ON INCORRECT FILE SIZE!
manifestHeader.AttachmentByteSizeNum = new FileInfo(FormDataFilePath).Length.ToString();
I replaced the above code with the following, and the error I was receiving was resolved.
// Read the contents of the file, and retrieve the length of the content of the file itself..
// Result: TRANSMISSION WAS ACCEPTED USING THIS FILE SIZE.
manifestHeader.AttachmentByteSizeNum = File.ReadAllText(FormDataFilePath).Length.ToString();
It appears as though the Web Service is actually expecting the size of the file content and not the size of the actual file. The difference in size pertaining to the test scenarios was approximately 3 bytes. I assume that is because retreiving the size of the file adds some additional file-related information not belonging to the actual content.
I have notified the IRS about this issue regarding their documentation.