I am attempting to send an attachment through a HttpWebRequest in a Console Application. After a few days of searching and scouring the internet for some understandable assistance on this, I came up with what I think is a decent solution from this site
While I think I've done everything correctly, I am receiving the following error:
Multipart stream ended before a terminating boundary was encountered.
Questions:
I'm hoping to get some assistance/guidance with the multipart error I'm receiving, as well as assistance in attaching the actual byte[] of the XML Document.
Requirements:
The data file that needs to be attached is is an XML file which
should be an MTOM Attachment. In order to make it Mtom, my
understanding is that I need to be sure that the messageEncoding
attribute of the <binding> element in the app.config should have
a value of "Mtom" and this will be encoded as such.
Based on the business requirements (of which is strict), I need to send the byte[] of the file, not simply the contents itself.
Web Request Method
private static HttpWebRequest CreateWebRequest(SoapAction action)
{
// Retrieve URL from Endpoint in the app.Config based on the action passed into the
// method.
string url = GetUrlAddress(action);
if (url != null)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate");
request.Headers.Add(HttpRequestHeader.ContentEncoding, "gzip");
request.Headers.Add("Content-Transfer-Encoding", "8bit");
request.Headers.Add("SOAPAction", action.ToString());
request.Method = "POST";
request.Headers.Add("MIME-Version", "1.0");
request.ContentType = "multipart/form-data; type=\"application/xop+xml;\" boundary=\"" + BoundaryMarker + "\"";
request.ClientCertificates.Add(SecurityCertificate.CertificateObject);
ServicePointManager.Expect100Continue = false;
return request;
}
else
{
throw new NullReferenceException("Address for Endpoint could not be resolved.");
}
}
Method to Submit Request
Based on this post of mine, I believe I am compressing the HttpWebRequest appropriately using GZip.
private static void SubmitRequest(HttpWebRequest request, XDocument soapXml, byte[] formXmlBytes, FileInfo fileToUpload)
{
using (Stream requestStream = request.GetRequestStream())
{
using (GZipStream gz = new GZipStream(requestStream, CompressionMode.Compress, false))
{
soapXml.Save(gz);
WriteToStream(gz, formXmlBytes, fileToUpload.Name);
}
}
}
Method used to Write the MIME information and attachment to the Stream
public static void WriteToStream(Stream stream, byte[] formData, string fileName)
{
// Write a new line to the stream.
byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n");
stream.Write(newLineBytes, 0, newLineBytes.Length);
// Write the header to the stream.
string header = String.Format(HeaderTemplate, BoundaryMarker, fileName, RequestContentID);
byte[] headerBytes = Encoding.UTF8.GetBytes(header);
stream.Write(headerBytes, 0, headerBytes.Length);
// Write a new line to the stream.
stream.Write(newLineBytes, 0, newLineBytes.Length);
// Write the formData to the stream.
stream.Write(formData, 0, formData.Length);
// Write a new line to the stream.
stream.Write(newLineBytes, 0, newLineBytes.Length);
// Write the footer to the stream.
byte[] boundaryFooterBytes = Encoding.UTF8.GetBytes("--" + BoundaryMarker + "--");
stream.Write(boundaryFooterBytes, 0, boundaryFooterBytes.Length);
}
Soap Body Snippet Element
By using Fiddler, I am able to see what the request actually looks like. To me, it appears that the attachment is actually being appended to the request as the XML that it is, instead of (what I thought would be) a byte[].
After this should be the byte[] of the attachment. Currently, the full XML document is displaying.
Accept-Encoding: gzip, deflate
Content-Transfer-Encoding: 8bit
MIME-Version: 1.0
Content-Type: multipart/form-data; type="application/xop+xml;" boundary="--b73acdd180274cab985e4d697bfde428"
Content-Length: 582081
Connection: Keep-Alive
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="..." ...>
...
<soapenv:Body>
<urn:Request version="1.0">
<urn:FileAttachment>cid:b73acdd180274cab985e4d697bfde428</urn:FileAttachment>
</urn:Request>
</soapenv:Body>
</soapenv:Envelope>
----b73acdd180274cab985e4d697bfde428
Content-Disposition: attachment; filename="test.xml"
Content-Type: text/xml
Content-Id: b73acdd180274cab985e4d697bfde428
<XML OF ATTACHMENT>
----b73acdd180274cab985e4d697bfde428--
Related
when i make a HTTP request to the server it responses with the MIME Response Stream which has two or more images in it as Binary data which are separated by Boundary String
Now i need to extract those images only and save them individually into database
Stream Looks like this...
Header has
RETS-Version: RETS/1.0
MIME-Version: 1.0
Content-type: multipart/parallel;
boundary="simple boundary"
Http ResoinseStream Has
--simple boundary
Content-Type: image/jpeg
Content-ID: 123456
Object-ID: 1
<binary data>(Image1)
--simple boundary
Content-Type: image/jpeg
Content-ID: 123457
Object-ID: 1
<binary data>(Image2)
--simple boundary --
I need to extract image1 and image2 and save those in database as binary image.
If you use my MimeKit library, you can do this really easily:
static MimeEntity ParseHttpWebResponse (HttpWebResponse response)
{
var contentType = ContentType.Parse (response.ContentType);
return MimeEntity.Parse (contentType, response.GetResponseStream ());
}
static void SaveAllImages (HttpWebResponse response)
{
var entity = ParseHttpWebResponse (response);
var multipart = entity as Multipart;
if (multipart != null) {
foreach (var image in multipart.OfType<MimePart> ()) {
using (var memory = new MemoryStream ()) {
image.ContentObject.DecodeTo (memory);
var blob = memory.ToArray ();
// save it to your database
}
}
}
}
I'm trying to post to Feed -> SubmitFeed.
However the exception I receive back is:
you must pass a Content-MD5 HTTP header for your feed so we can be
sure it was not corrupted (e.g. dropped a 0 from a price) before we
process it
What I don't understand is the documentation states that it is optional so unsure why I am receiving this. Plus I am actually passing something through.
XML
<?xml version="1.0" encoding="UTF-8" ?>
- <AmazonEnvelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" schemaLocation="amzn-base.xsd">
- <Header>
<DocumentVersion>1</DocumentVersion>
<MerchantIdentifier>A1ddWU5JfffWJEddV9Xf668B</MerchantIdentifier>
</Header>
<MessageType>Product</MessageType>
<PurgeAndReplace>false</PurgeAndReplace>
- <Message>
<MessageId>1</MessageId>
<OperationType>Update</OperationType>
</Message>
- <Product>
<SKU>3308ff6-01</SKU>
</Product>
</AmazonEnvelope>
How I attach the Body/Header
var request = Helper.CreateWebRequest(xmlRequest.Config, genericparameters, signatureHelpers);
request.Headers.Add("x-amazon-user-agent", amazonHostAgent);
request.Method = "POST";
request.ContentType = amazonConfig.ContentType;
request.Timeout = 50000;
Helper.AddXmlDocumentToRequest(xmlRequest.XmlDocument.InnerXml, request);
public static void AddXmlDocumentToRequest(string xmlDocument, WebRequest request)
{
var bytes = Encoding.ASCII.GetBytes(xmlDocument);
request.ContentLength = bytes.Length;
var requestStream = request.GetRequestStream();
request.Headers.Add("Content-MD5", GetMd5HashFromStream(bytes));
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
}
public static string GetMd5HashFromStream(byte[] bytes)
{
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
Within the request when I drill down to Headers I have the following:
{x-amazon-user-agent: MWSApplication/1.0 (Language=c#)
Content-Type: text/xml
Host: mws.amazonservices.co.uk
Content-Length: 478
Expect: 100-continue
Content-MD5: 1xLxj5Y+WVsAVR9BbQfzQA==
}
When I run the xml through scratchpad it returns the same Content-MD5: 1xLxj5Y+WVsAVR9BbQfzQA==
Am I doing something wrong?
Thanks,
Clare
This might not be the answer for everyone however it was for me and if it helps one other person I will be happy :-)
Me being very stupid, added the header after the streaming, this is not what you should be doing.
Create the URL
Add headers and other elements i.e.
request.ContentType
Stream documents (body of request) if required
request.GetResponse.
Thanks,
Clare
I've created a web api, which contains method:
POST Settings/SetPropertyValue?propertyName={propertyName}
public object SetPropertyValue(string propertyName, object propertyValue)
{
switch (propertyName)
{
//Do the property assignment
}
}
When I visit help page, it shows following
When I try to invoke the method from fiddler, using XML example, it works fine, object propertyValue equals to POST value.
XML POST example:
POST http://localhost:99/webapi/Settings/SetPropertyValue?propertyName=myProperty HTTP/1.1
Content-Type: text/xml; charset=UTF-8
Host: localhost:99
Expect: 100-continue
Connection: Keep-Alive
<anyType>
true
</anyType>
But how to POST JSON in this case? Does JSON handles "simple" data types, like object or string?
As far as I see there is no body you send. So both the XML and JSON bodies are empty.
You place all your properties in the query string.
I was reading this article about it and it seems you have to start your method with Post to make it a HTTP POST instead of GET.
Quote:
Note two things about this method:
The method name starts with "Post...". To create a new product, the
client sends an HTTP POST request.
This is my test code. Maybe it is useful to you:
WebRequest request = HttpWebRequest.Create("http://localhost:12345/api/Values");
byte[] byteArray = Encoding.UTF8.GetBytes("5");
request.ContentLength = byteArray.Length;
request.ContentType = "application/json";
request.Method = "POST";
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse response = request.GetResponse();
Stream data = response.GetResponseStream();
StreamReader reader = new StreamReader(data);
// Read the content.
string responseFromServer = reader.ReadToEnd();
The controller action involved here:
// POST api/values
public void Post([FromBody]string value)
{
// check the value here
}
I'm trying to insert a file in google drive using webRequest (since I'm implementing an resumable async upload), but I have no idea how to put data in request "body".
From now, I have:
public static HttpWebRequest CreateUploadRequest(Google.Apis.Drive.v2.DriveService driveService, string uri, Stream contentStream, string title, string mimeType, string description = null)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "PUT";
Dictionary<string, object> requestBody = new Dictionary<string, object>();
requestBody["title"] = title;
requestBody["mimeType"] = mimeType;
if (!string.IsNullOrWhiteSpace(description))
{
requestBody["description"] = description;
}
driveService.Authenticator.ApplyAuthenticationToRequest(request);
Stream requestStream = request.GetRequestStream();
//How to do that???
requestStream.Close();
return request;
}
I set the headers for the HttpWebRequest, how the data of the body should be disposed?
And whats is the property name for the byte[] data of the file to be inserted?
Any example whould be appreciated.
The SDK documentation on this page says:
The body of the request is formatted as a multipart/related content type RFC2387 and contains exactly two parts. The parts are identified by a boundary string, and the final boundary string is followed by two hyphens.
If you've never worked with it, RFC2387 is the MIME definition. Binary data in MIME is typically BASE64 encoded, like in this example from the RFC:
Content-Type: Application/octet-stream
Content-Description: The fixed length records
Content-Transfer-Encoding: base64
Content-ID: <950120.aaCB#XIson.com>
T2xkIE1hY0RvbmFsZCBoYWQgYSBmYXJtCkUgSS
BFIEkgTwpBbmQgb24gaGlzIGZhcm0gaGUgaGFk
IHNvbWUgZHVja3MKRSBJIEUgSSBPCldpdGggYS
BxdWFjayBxdWFjayBoZXJlLAphIHF1YWNrIHF1
YWNrIHRoZXJlLApldmVyeSB3aGVyZSBhIHF1YW
NrIHF1YWNrCkUgSSBFIEkgTwo=
Have a look at this question for an example of uploading binary data to a Web request: Upload files with HTTPWebrequest (multipart/form-data)
I'm currently working on posting a file from a C# application to an image host (KalleLoad.net - with the owners consent, obviously).
I've gotten the actual posting of the request to work, but it's not returning what I expected. The owner of the upload site has provided me with an API (of sorts) which will return some XML with the URLs if I post the data to a certain address. I can successfully post the data and get a response from the server, however it is simply returning the code for the home page instead of the XML. I cannot understand why this is so.
I've also tried posting data to a simple PHP page on my local server and it too returns the code for the page, instead of what I instructed the page to return on post.
Below is the entirety of my current class for sending the data. I have also been comparing the headers I have been sending from my application with those that firefox is sending for the last half-hour and I can see no real game-changing differences between them (as far as I'm aware).
Any help on this would be fantastic and graciously received.
Regards,
Andy Hunt
using System;
using System.Net;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
namespace Skimpt_3._0
{
class PostFile
{
private Hashtable FormElements;
private HttpWebRequest Request;
private MemoryStream FileStream;
private string CONTENT_BOUNDARY = "---------------------------265001916915724";
public string ContentMIMEType;
public string FormURL;
public string FileName;
public string Response;
public string FileBoxName;
//private int BufferSize;
public PostFile(string Url, string strFileName)
{
FormElements = new Hashtable();
FormURL = Url;
Request = (HttpWebRequest)WebRequest.Create(Url);
//BufferSize = 10240;
FileStream = new MemoryStream();
FileName = strFileName;
}
public void Send(Image image)
{
//Assign the request here too, just in case
Request = (HttpWebRequest)WebRequest.Create(FormURL);
Request.Method = "POST";
Request.AllowWriteStreamBuffering = true;
Request.ProtocolVersion = HttpVersion.Version11;
Request.Headers.Add("Cache-Control", "no-cache");
Request.KeepAlive = true;
Request.ContentType = "multipart/form-data; boundary=---------------------------265001916915724";
StartFileStream(FileStream);
//Must be done in this order for stream to write properly:
//----
//Form elements
//File header
//Image
//File trailer
//----
WriteStringToStream(FileStream, GetFormElements());
WriteImageToStream(FileStream, image, FileName);
CloseStream(FileStream);
byte[] FileByteArray = FileStream.ToArray();
Request.ContentLength = FileByteArray.Length;
Stream PostingStream = Request.GetRequestStream();
PostingStream.Write(FileByteArray, 0, FileByteArray.Length);
WebResponse resp = (HttpWebResponse)Request.GetResponse();
StreamReader SR = new StreamReader(resp.GetResponseStream());
PostingStream.Close();
FileStream.Close();
Request.GetRequestStream().Close();
Response = SR.ReadToEnd();
Request = null;
}
private void CloseStream(MemoryStream FileStream)
{
byte[] BytesToWrite = Encoding.ASCII.GetBytes(CONTENT_BOUNDARY);
FileStream.Write(BytesToWrite, 0, BytesToWrite.Length);
}
private void StartFileStream(MemoryStream FileStream)
{
// \r\n = new line
string str = "POST " + FormURL +"Content-Type: multipart/form-data; boundary="+CONTENT_BOUNDARY+" \r\n \r\n" + CONTENT_BOUNDARY;
byte[] BytesToWrite = Encoding.ASCII.GetBytes(str);
FileStream.Write(BytesToWrite, 0, BytesToWrite.Length);
}
private Byte[] ConvertImageToByteArray(Image img)
{
//Method taken from http://www.csharp-station.com/Articles/Thumbnails.aspx and adapted
MemoryStream memStream = new MemoryStream();
img.Save(memStream, System.Drawing.Imaging.ImageFormat.Png);
byte[] byteArray = new Byte[memStream.Length];
memStream.Position = 0;
memStream.Read(byteArray, 0, (int)memStream.Length);
return byteArray;
}
public void AddFormElement(string ElementName, string ElementValue)
{
FormElements[ElementName] = ElementValue;
}
private string GetFormElements()
{
string str = "";
IDictionaryEnumerator myEnumerator = FormElements.GetEnumerator();
while (myEnumerator.MoveNext())
{
str += CONTENT_BOUNDARY + "\r\n" +
"Content-Disposition: form-data; name=" + myEnumerator.Key +
"\r\n\r\n" +
myEnumerator.Value +"\r\n";
}
return str;
}
private void WriteStringToStream(System.IO.MemoryStream stream, string String)
{
byte[] PostData = System.Text.Encoding.ASCII.GetBytes(String);
stream.Write(PostData, 0, PostData.Length);
}
private void WriteImageToStream(System.IO.MemoryStream Stream, Image img, string FileName)
{
byte[] ByteArray = ConvertImageToByteArray(img);
string head = CONTENT_BOUNDARY + "\r\n" +
"Content-Disposition: form-data; name=\"" + FileBoxName + "\"; filename=\"" + FileName + "\"\r\n" +
"Content-Type: " + ContentMIMEType + "\r\n\r\n";
byte[] header = Encoding.ASCII.GetBytes(head);
Stream.Write(header, 0, header.Length);
Stream.Write(ByteArray, 0, ByteArray.Length);
}
}
}
From what I can tell the POST request that you're submitting isn't formed correctly. I don't think the server understands how to interpret the data that you're sending (hence you're not getting the response you're expecting).
What a POST request to upload should look like
POST /_layouts/Upload.aspx HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7d9192265018e
Host: 127.0.0.1:25540
Content-Length: 3573
Connection: Keep-Alive
---------------------------7d9192265018e
-----------------------------7d9192265018e
Content-Disposition: form-data; name="uploadFile"; filename="C:\Temp\TestDocument.txt"
Content-Type: text/plain
blah
-----------------------------7d9192265018e--
(Note: the Content-Length value above is wrong: I removed some stuff for brevity.)
What a POST request that you're generating looks like
POST /WebSite1/Default.aspx HTTP/1.1
Cache-Control: no-cache
Content-Type: multipart/form-data; boundary=---------------------------265001916915724
Host: 127.0.0.1:25540
Content-Length: 8626
Expect: 100-continue
Connection: Keep-Alive
POST http://ipv4.fiddler:25540/WebSite1/Default.aspxContent-Type: multipart/form-data; boundary=-----------------------------265001916915724
-----------------------------265001916915724-----------------------------265001916915724
Content-Disposition: form-data; name=""; filename="Test.png"
Content-Type:
?PNG
-----------------------------265001916915724--
(Note: I've ommitted the PNG file data that I tested with)
By comparing the two, I can see the following problems:
The boundary is used incorrectly. You're using the same string in all places. After setting a boundary value (example: boundary=aaaBBBcccDDDeee) using the Content-Type header you use boundary prefixed with "--" (example: --boundary=aaaBBBcccDDDeee) to delimit each part of your multi-part form submission. The final boundary marks the end of the multi-part data and must be delimited with a "--" prefix and suffix (example: --boundary=aaaBBBcccDDDeee--. See RFC 1341 (MIME): 7 for more info.
There's a weird Expect: 100-continue header in your request. This looks like an issue with the .NET HttpWebRequest class and can be disabled. See HttpWebRequest and the Expect: 100-continue Header Problem for more.
The StartFileStream method is trying to write out a header value but you're putting it's output in the request body. It's also writing out headers incorrectly. I think this should be left out entirely (it looks like it adapted from some other upload code).
The field ContentMIMEType should be set to MIME type of the image you're uploading. It's not set at all so the Content-Type: of the image you're uploading is empty.
Ditto for the field FileBoxName.
How to debug this
You can use Fiddler to see the request that your sending. To set it up you'll need to be able to send a request to a web server. You should be able to run it and have it intercept any request you're trying to send.
Whew! That was long. Good luck and I hope that helps!