C# Reading multi-part stream - c#

I'm reading a multi-part formdata in an HTTPHandler.
My code:
var content = new StreamContent(context.Request.InputStream);
//content.Headers.Add("Content-Type", context.Request.ContentType);
//content.Headers.TryAddWithoutValidation("Content-Type", context.Request.ContentType);
//content.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data");
//content.Headers.Add("Content-Type", "multipart/form-data");
//content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(context.Request.ContentType);
// content.Headers.Remove("Content-Type");
//content.Headers.Add("Content-Type", context.Request.ContentType);~
content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(context.Request.ContentType);
if (!content.IsMimeMultipartContent())
{
return HttpStatusCode.BadRequest;
}
var multipart =await content.ReadAsMultipartAsync();
I always get:
The format of value 'Content-Type: multipart/form-data; boundary=9fc46...................... is invalid
If I don't try to put the content-type I get another error.
Note: The commented lines are other alternatives that I tried without result

Working solution:
var content = (HttpContent)new StreamContent(context.Request.InputStream);
content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(context.Request.ContentType);
var multipart = new MultipartMemoryStreamProvider();
await content.ReadAsMultipartAsync(multipart);

Try setting up a MultPartFormDataContent object with the following boundary and posting this
var content = new MultipartFormDataContent("---------------" + Guid);
I believe the dashes have something to do with the way that boundarys are read in.

Related

How to set "custom" Content-Type of an HttpClient request in DOT NET CORE?

I'm trying to set Content-Type as "application/x.example.hr.employee.email+json;version=1" of an HttpClient request as required by the API I am calling. The API is of type GET and accepts a JSON body (containing list of emails).
I'm successfully able to set Accept header to "application/x.example.hr.employee+json;version=1". In this case, both - Accept and Content-Type need to be set as mentioned, otherwise API throws a error of 400 (Bad request). I tried How do you set the Content-Type header for an HttpClient request? and several other options but getting run time error when I try to set Content-Type other than "application/json".
This type needs to be applied on the request content but not in the header Content-Type. Below is one of the code snippet I tried:
_httpClient.BaseAddress = new Uri("http://example.com/");
_httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/x.example.hr.employee+json;version=1");
//_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x.example.hr.employee.email+json;version=1"); // Throws exception
List<string> strEmail = new List<string>
{
employeeEmail
};
var jsonEmail = JsonConvert.SerializeObject(strEmail);
var request = new HttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://example.com/employees"),
Content = new StringContent(jsonEmail, Encoding.UTF8, "application/x.example.hr.employee.email+json;version=1")
};
//var response = _httpClient.SendAsync(request).ConfigureAwait(false);
await _httpClient.SendAsync(request)
.ContinueWith(responseTask =>
{
var response = responseTask;
});
For a reason that don't fully understand, the "application/x.example.hr.employee.email+json;version=1" media type is not correctly parsed whenever you build a StringContent (or actually a MediaTypeHeaderValue).
I did find a workaround for this:
List<string> strEmail = new List<string> {
employeeEmail
};
var jsonEmail = JsonConvert.SerializeObject(strEmail);
var content = new StringContent(jsonEmail, Encoding.UTF8);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x.example.hr.employee.email+json");
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("version", "1"));
var request = new HttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://example.com/employees"),
Content = content
};
It's odd that the MediaTypeHeaderValue constructor (which is what StringContent calls) doesn't accept "application/x.example.hr.employee.email+json; version=1".
However, MediaTypeHeaderValue.Parse does.
var contentType = MediaTypeHeaderValue.Parse("application/x.example.hr.employee.email+json; version=1");
var content = new StringContent(jsonEmail, Encoding.UTF8, contentType);
If you're stuck on .NET 6 and below:
var content = new StringContent(jsonEmail, Encoding.UTF8);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x.example.hr.employee.email+json; version=1");
See this GitHub issue for a discussion.

C# Fedex SOAP API - Get Shipping Label

I'm trying to use the Fedex API to send SOAP requests and get shipping labels. The developer documentation on Fedex's website isn't very helpful. Does anyone have any sample code showing how to get shipping labels from your requests? Below is what I'm trying - looks like my request works, but my filestream returns a broken pdf. Unless Fedex is responding to my request incorrectly I'm at a loss for where to continue looking.
FedExHelper fedExHelper = new();
// builds the xml for the request
var parsedXml = fedExHelper.BuildShipmentRequest();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://ws.fedex.com/web-services");
client.DefaultRequestHeaders.Add("Accept", "image/gif, image/jpeg, image/pjpeg, text/plain, text/html, */*"); // for Accept header
HttpRequestMessage request = new();
request.Method = HttpMethod.Post;
request.Content = new StringContent(parsedXml);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
var response = client.Send(request);
return new FileStreamResult(await response.Content.ReadAsStreamAsync(), "application/pdf")
{
FileDownloadName = "test.pdf"
};
}
Also tried the below after reading the xml response. There's a label node that contains a base64 image. Decoding the base64 string still downloads a broken pdf.
var responseText = FedExHelper.SampleResponseText();
XDocument xml = XDocument.Load(new StringReader(responseText));
var label = xml.Root.Descendants().Where(x => x.Name.LocalName == "Label").FirstOrDefault();
var labelParts = label.Descendants().Where(x => x.Name.LocalName == "Parts").FirstOrDefault();
var image = labelParts.Descendants().Where(x => x.Name.LocalName == "Image").FirstOrDefault();
byte[] bytes = Convert.FromBase64String(image.Value);
MemoryStream stream = new(bytes);
return new FileStreamResult(stream, "application/pdf")
{
FileDownloadName = "test.pdf"
};
#Charleh you were absolutely correct. I had the incorrect ImageType value set in the xml I was posting. I can now return PDF labels.
For anyone else looking at this:
I had ZPLII set as the ImageType. This should have been PDF. I was adapting my code from an old dBase program, having no knowledge of working with the Fedex API.

Presigned PUT works from PostMan, but 403 Forbidden from C# HttpClient

I have generated a pre-signed url from S3 using the following .Net code (in a service that has the appropriate IAM role/permission for the bucket)
var key = GenerateKey(jobId, batchId);
var linkExpiresAt = _dateTimeService.Now().AddMinutes(_expiryTime);
var request = new GetPreSignedUrlRequest
{
BucketName = _bucketName,
Key = key,
Verb = HttpVerb.PUT,
ContentType = "application/json",
Expires = linkExpiresAt,
ServerSideEncryptionMethod = ServerSideEncryptionMethod.None
};
var url = _s3Client.GetPreSignedURL(request);
I can use this url in Postman to do a PUT with content 'JSON', but when I try to use it from code, I get 403
var content = new StringContent(mooStr, Encoding.ASCII, "application/json");
var fileStreamResponse = await httpClient.PutAsync(
url,
content);
Is there anything that stands out as wrong with the .Net attempt to PUT to the url?
If anyone comes across the same issue, I found the solution.
When I ran Fiddler, I captured the successful request from Postman and the failing request from .Net code. The only difference I could spot was that the successful one had this header (I'd changed to text/plain since my first post, but the problem remained):-
Content-Type: text/plain
and the failing one from .Net had:-
Content-Type: text/plain; charset=utf-8
A bit of a search around StackOverflow found me these posts
How to remove charset=utf8 from Content-Type header generated by HttpClient.PostAsJsonAsync()?
How do I not exclude charset in Content-Type when using HttpClient?
I edited the captured request in Fiddler and removed the charset and it worked. Altering my original code to the following worked (note - setting text/plain on the StringContent didn't work):-
var content = new StringContent(mooStr);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
var fileStreamResponse = await httpClient.PutAsync(
Url,
content);
Following code worked for me (for posting a file of type IFormFile):
public async Task<bool> UploadObject(string preSignedUrl)
{
try
{
using (var client = new HttpClient())
{
StreamContent streamContent = new StreamContent(file.OpenReadStream());
var result = await client.PutAsync(preSignedUrl, streamContent);
if (result.StatusCode != HttpStatusCode.OK)
{
throw new Exception();
}
}
return true;
}
catch (Exception e)
{
....
}
return false;
}
Create the preSignedUrl with the IAmazonS3 package.

Sending a POST request with FILE using in XAMARIN C#

looking on the web, I saw a little bit how to POST a 'file' and therefore, I wrote my code in this way:
var upfilebytes = File.ReadAllBytes((string)fPath);
//create new HttpClient and MultipartFormDataContent and add our file, and StudentId
HttpClient client = new HttpClient();
MultipartFormDataContent content = new MultipartFormDataContent();
ByteArrayContent baContent = new ByteArrayContent(upfilebytes);
content.Add(baContent, "img", fPath);
StringContent emailText = new StringContent(lbl_email.Text);
content.Add(emailText, "email");
string url = "http://192.168.178.77/TestLoginURL/api/updateUserImage.php";
//upload MultipartFormDataContent content async and store response in response var
var response =
await client.PostAsync(url, content);
//read response result as a string async into json var
var responsestr = response.Content.ReadAsStringAsync().Result;
now the problem is another ... this is my API code:
<?php
$response = array();
if($_SERVER['REQUEST_METHOD']=='POST'){
//getting values
$img = $_FILES['img']['name'];
$email = $_POST['email'];
//including the db operation file
require_once '../includes/DbOperation.php';
$db = new DbOperation();
$target = "/Applications/XAMPP/xamppfiles/htdocs/TestLoginURL/images/".basename($img);
//inserting values
if($db->updateImage((String)basename($img),$email)){
$response['error']=false;
$response['message']='Image added successfully - test fileName = '.(String)basename($img);
}else{
$response['error']=true;
$response['message']='Could not add image';
}
if(move_uploaded_file($_FILES['img']['tmp_name'], $target)) {
/*$response['error']=false;
$response = "Image uploaded successfully";*/
}else{
$response['error']=true;
$response = "Failed to upload image";
}
}else{
$response['error']=true;
$response['message']='You are not authorized';
}
echo json_encode($response);
With Postman it works perfectly, updates the name of the image in the database and physically inserts the image in the designated path and the response message for example is this: {"error": false, "message": "Image added successfully - test fileName = trollolollo.png"}
Now, the app saves the file in the right repository but does NOT UPDATE the name of the 'image' in the database ... BUT strangely, the "response" message in the debugger also correctly shows the name of the FILE ... So I just don't understand where I'm wrong ... Could someone help me with the code please? Thanks
OFF_TOPIC: usually, when I have to send only strings, I send a post request written in this way
string url = "http://192.168.178.77/TestLoginURL/api/insertUser.php";
FormUrlEncodedContent formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("nickname", nickname),
new KeyValuePair<string, string>("password", password1),
new KeyValuePair<string, string>("email", email)
});
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
try
{
CancellationTokenSource cts = new CancellationTokenSource();
var responseMessage = httpClient.PostAsync(url, formContent).Result;
}
catch (Exception ex)
{
ex.Message.ToString();
throw;
}
but now, being a file and having no experience about it I am having difficulties...
thanks again, I hope someone can help me with the code
Are you sure that you are handling update query correctly ?
Update query will only return false on a failure if you messed up the SQL query or anything like that. So you have to use mysqli_stmt_affected_rows to see if a row has been updated in your PHP code.
If postman can do it HttpClient must be able to do it too, with the proper configuration.
Try to use all the headers postman is using you are probably missing something, maybe the filename is causing the DB query to fail.
By the way is there any difference between how you handle jpg and png in your server ? you can check that too.

Is it possible to upload a file as well as post data using servicestack?

I want to be able to post a file and as part of that post add data.
Here is what I have:
var restRequest = new RestRequest(Method.POST);
restRequest.Resource = "some-resource";
restRequest.RequestFormat = DataFormat.Json;
string request = JsonConvert.SerializeObject(model);
restRequest.AddParameter("text/json", request, ParameterType.RequestBody);
var fileModel = model as IHaveFileUrl;
var bytes = File.ReadAllBytes(fileModel.LocalStoreUrl);
restRequest.AddFile("FileData", bytes, "file.zip", "application/zip");
var async = RestClient.ExecuteAsync(restRequest, response =>
{
if (PostComplete != null)
PostComplete.Invoke(
new Object(),
new GotResponseEventArgs
<T>(response));
});
It posts the file fine but the data is not present - is this even possible?
[UPDATE]
I have amended the code to use the multi-part header:
var restRequest = new RestRequest(Method.POST);
Type t = GetType();
Type g = t.GetGenericArguments()[0];
restRequest.Resource = string.Format("/{0}", g.Name);
restRequest.RequestFormat = DataFormat.Json;
restRequest.AddHeader("content-type", "multipart/form-data");
string request = JsonConvert.SerializeObject(model);
restRequest.AddParameter("text/json", request, ParameterType.RequestBody);
var fileModel = model as IHaveFileUrl;
var bytes = File.ReadAllBytes(fileModel.LocalStoreUrl);
restRequest.AddFile("FileData", bytes, "file.zip", "application/zip");
var async = RestClient.ExecuteAsync(restRequest, response =>
{
if (PostComplete != null)
PostComplete.Invoke(
new Object(),
new GotResponseEventArgs
<T>(response));
});
Still no luck... any pointers?
I am not an expert in C# but I used the same principle in Grails/Java for multipart requests.
Some pointers (ServiceStack/C#)
Multipart Form Post
MSDN MIME Message
ServiceStack File Attachment
Java corresponds:
Posting File and Data as JSON in REST Service
I hope this helps.
I am not sure if this is going to help. But give it a try.
Since you are attempting to pass it as text/json you may try to convert your byte array to a string and add it to the request.
To convert it to a string you may do something like this.
public string ContentsInText
{
get
{
return Encoding.Default.GetString(_bytecontents);
}
}
To convert it to a byte array you may do this. Most probably you will have to do this in your web service.
public byte[] ContentsInBytes
{
get { return Encoding.Default.GetBytes(_textcontents); }
}

Categories

Resources