I am writing a WCF client to a SOAP service that returns a mime multi-part result with binary data (actually, a PDF file). It uses a custom message encoder.
The service doesn't seem to mind if I make the request a single-part format, so I am able to get a result back. There are two problems with the result from what I can see:
It only seems to return the first part of the multi-part message.
The data I get back cannot be decoded by my custom encoder.
I have tried utilizing MTOM binding, but that messes up the request. It fails to add the "boundary" parameter in the content-type, so the server cannot understand the request.
I think what I want is a basic text SOAP request, but a response decoded MTOM-style. I have no idea how to set that up, however.
The closest solution I have found is this: http://blogs.msdn.com/b/carlosfigueira/archive/2011/02/16/using-mtom-in-a-wcf-custom-encoder.aspx
But it seems like a very invasive change to my project.
I figured this out. First of all, I was incorrect when I said that I was only getting the first part of the multi-part message using the MTOM encoder; I was getting the whole thing. I was looking at it in the debugger and the bottom must have gotten clipped in the debug viewer. Chalk it up to my inexperience manually looking at and deciphering multi-part messages.
To the second point, all I had to do was use the MTOM encoder when the Content-Type was multipart/related and everything worked just fine. If you read the referenced article above, it's all about dynamically detecting whether the message is multipart or regular text, and choosing the proper encoder based on that. Essentially, it's a custom encoder that has both a text encoder and MTOM encoder built into it, and switches back and forth based on the content-type of the incoming message.
Our project requires some post processing of the response message before it's handed off to the main program logic. So, we get the incoming SOAP content as an XML string, and do some XML manipulation on it.
This is a slight departure from the solution recommended in the article. All that's required in the article's solution is reading the message using the right encoder into a System.ServiceModel.Channels.Message, and returning that. In our solution, we need to interrupt this process and do the post-processing.
To do that, implement the following in your custom encoder:
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
//First, get the incoming message as a byte array
var messageData = new byte[buffer.Count];
Array.Copy(buffer.Array, buffer.Offset, messageData, 0, messageData.Length);
bufferManager.ReturnBuffer(buffer.Array);
//Now convert it into a string for post-processing. Look at the content-type to determine which encoder to use.
string stringResult;
if (contentType != null && contentType.Contains("multipart/related"))
{
Message unprocessedMessageResult = this.mtomEncoder.ReadMessage(buffer, bufferManager, contentType);
stringResult = unprocessedMessageResult.ToString();
}
else {
//If it's not a multi-part message, the byte array already has the complete content, and it simply needs to be converted to a string
stringResult = Encoding.UTF8.GetString(messageData);
}
Message processedMessageResult = functionToDoPostProccessing(stringResult);
return processedMessageResult;
}
Related
I am trying to send a SOAP message via WCF to the IRS, and it keeps getting rejected because my MTOM attachment is formatted incorrectly.
I've narrowed down the issue to my Content-Transfer-Encoding value. It is set to Binary (shorthand for 8-bit).
The IRS service wants me to use 7-bit, with an 8-bit-encoded attachment (in other words, encode with UTF-8 and then guarantee that I'm not using any non-ASCII characters).
I'm already using a custom message encoder in order to gzip my requests (responses come back plain-text, ugh). This is what my WriteMessage looks like right now.
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) {
// get an instance of the underlying encoder
var encoder = new MtomMessageEncodingBindingElement() {
MessageVersion = MessageVersion.Soap11WSAddressing10,
WriteEncoding = System.Text.Encoding.UTF8
}.CreateMessageEncoderFactory().Encoder;
// write the message contents
var uncompressed = encoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
// compresses the resulting byte array
return CompressBuffer(uncompressed, bufferManager, messageOffset);
}
Any ideas? When I change the WriteEncoding property to ASCII or UTF7 .NET throws an ArgumentException and tells me the format is not supported.
It appears the built in MTOM encoder in WCF will not encode a request compatible with the IRS service. It encodes whatever it finds in the request that's base64 encoded including the BinarySecurityToken in the signed request. I was able to get a request closer to IRS requirements by creating a custom encoder. Within WriteMessage, you can append and prepend MIME separators and reencode the file as an attachment. An outgoing message inspector is required to properly set the headers: https://blogs.msdn.microsoft.com/carlosfigueira/2011/04/18/wcf-extensibility-message-inspectors/
I am using Java Apache CXF and WSS4J for the IRS solution, but if you are getting this error "The message was not formatted properly and/or cannot be interpreted. Please review the XML standards outlined in Section 3 of the AIR Submission Composition and Reference Guide located at https://www.irs.gov/for-Tax-Pros/Software-Developers/Information-Returns/Affordable-Care-Act-Information-Return-AIR-Program, correct any issues, and try again." it is because the IRS is expecting this:
Content-Type: application/xml
Content-Transfer-Encoding: 7bit
Content-ID: <6920edd2-a3c7-463b-b336-323a422041d4-1#blahurn:us:gov:treasury:irs:common>
Content-Disposition: attachment;name="1094B_Request_BBBBB_20151019T121002000Z.xml"
I am integrating with a third party API via a PUT request (required). However the URL is not what I consider standard.
https://standard.com/services/U2FsdGVkX18qfVhkTn7JsiXWA8q4vco7vpU%2BmeKqiXKafdKxfJqsq1ELGFOHIilpoR7VXqUcg89yXiabxjjfVXiRmVqGOffsKw%2BKLp5OU6%2FJZaTyn6BYxi%2F10ndtWFLG2KgxRmm%2BxtgTAopUi6m7wWTnSoAlL8qoS%2F3UbiippOw%3D.
Note that the reservation number isn't in the body nor as a parameter, its just a / then the number.
They have encoded the reservation number (requested via another API call). The issue is I'm using HttpRequestMessage, this decodes the %2F to /. Meaning it fails. I have tried double encoding the special characters but this isn't returning a consistent response, sometimes it fails other times it works (using different reservation numbers, only the %2F is double encoded). Is there a way to stop HttpRequestMessage decoding part of the URL?
I have tried encoding/decoding but it always gets stripped out here (unless double encoded:
var path = "https://standard.com/services/U2FsdGVkX18qfVhkTn7JsiXWA8q4vco7vpU%2BmeKqiXKafdKxfJqsq1ELGFOHIilpoR7VXqUcg89yXiabxjjfVXiRmVqGOffsKw%2BKLp5OU6%2FJZaTyn6BYxi%2F10ndtWFLG2KgxRmm%2BxtgTAopUi6m7wWTnSoAlL8qoS%2F3UbiippOw%3D";
var requestMessage = new HttpRequestMessage(HttpMethod.PUT, path);
var requestMessage = (HttpWebRequest)WebRequest.Create(path);
var requestMessage = new HttpRequestMessage(method, new Uri(path));
Update: This was a design decision made by Microsoft:
https://connect.microsoft.com/VisualStudio/feedback/details/511010/erroneous-uri-parsing-for-encoded-reserved-characters-according-to-rfc-3986
The solution from their point of view is to add a setting in the
web.config. But this is a class library and therefore used by more
than one project. I don't want to alter that unless I really have to
This was a design decision made by Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/511010/erroneous-uri-parsing-for-encoded-reserved-characters-according-to-rfc-3986
For reasons outlined here I need to review a set values from they querystring or formdata before each request (so I can perform some authentication). The keys are the same each time and should be present in each request, however they will be located in the querystring for GET requests, and in the formdata for POST and others
As this is for authentication purposes, this needs to run before the request; At the moment I am using a MessageHandler.
I can work out whether I should be reading the querystring or formdata based on the method, and when it's a GET I can read the querystring OK using Request.GetQueryNameValuePairs(); however the problem is reading the formdata when it's a POST.
I can get the formdata using Request.Content.ReadAsFormDataAsync(), however formdata can only be read once, and when I read it here it is no longer available for the request (i.e. my controller actions get null models)
What is the most appropriate way to consistently and non-intrusively read querystring and/or formdata from a request before it gets to the request logic?
Regarding your question of which place would be better, in this case i believe the AuthorizationFilters to be better than a message handler, but either way i see that the problem is related to reading the body multiple times.
After doing "Request.Content.ReadAsFormDataAsync()" in your message handler, Can you try doing the following?
Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0; //resetting to 0 as ReadAsFormDataAsync might have read the entire stream and position would be at the end of the stream causing no bytes to be read during parameter binding and you are seeing null values.
note: The ability of a request's content to be read single time only or multiple times depends on the host's buffer policy. By default, the host's buffer policy is set as always Buffered. In this case, you will be able to reset the position back to 0. However, if you explicitly make the policy to be Streamed, then you cannot reset back to 0.
What about using ActionFilterAtrributes?
this code worked well for me
public HttpResponseMessage AddEditCheck(Check check)
{
var request= ((System.Web.HttpContextWrapper)Request.Properties.ToList<KeyValuePair<string, object>>().First().Value).Request;
var i = request.Form["txtCheckDate"];
return Request.CreateResponse(HttpStatusCode.Ok);
}
Does any one know how to get attachment from System.ServiceModel.Channels.Message?
When client requests from the server, and server is returning a xop element with attachment in the response. But I don't know how to get the attachment from the Message obj returned from the service call.
I don't fully understand all the circumstances, but there are several options the first is to use appropriate message encoder in binding, for example CustomBinding with MtomMessageEncodingBindingElement element. In that case, XmlDictionaryReader returned by Message.GetReaderAtBodyContents() will automatically replace xop:Include elements by corresponding attachment. In case, if there are needs to manipulate attachments at lower level, or in case if non-MTOM encoding is used, you will need to write appropriate message encoder that will meets you requirements.
I have a resource that looks something like this:
/users/{id}/summary?format={format}
When format is "xml" or "json" I respond with a user summary object that gets automagically encoded by WCF - fine so far. But when format equals "pdf", I want my response to consist of a trivial HTTP response body and a PDF file attachment.
How is this done? Hacking on WebOperationContext.Current.OutgoingResponse doesn't seem to work, and wouldn't be the right thing even if it did. Including the bits of the file in a CDATA section or something in the response isn't safe. Should I create a subclass of Message, then provide a custom IDispatchMessageFormatter that responds with it? I went a short distance down that path but ultimately found the documentation opaque.
What's the right thing?
It turns out that what I need is WCF "raw" mode, as described here. Broadly speaking, I want to do this:
[OperationContract, WebGet(UriTemplate = "/users/{id}/summary?format={format}"]
public Stream GetUserSummary(string id, string format)
{
if(format == "pdf")
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/pdf";
return new MemoryStream(CreatePdfSummaryFileForUser(id));
}
else
{
// XML or JSON serialization. I can't figure out a way to not do this explicitly, but my use case involved custom formatters anyway so I don't care.
}
}
In theory you could do it with a multi-part content MIME type (see http://www.faqs.org/rfcs/rfc2387.html). However, it would be much easier to return a URL in the XML/JSON response and the client can do a GET on that link to return the file.