Sending blob files to server - javascript

I am trying to send a audio recording to server and save it as .wav. I am using angular on the front end and .net core on server. I was able to record, then make a blob of type "audio/wav". For sending it to server, I convert it into an array buffer and then the array buffer to base64 string which I post to controller.
On the server side when I write those bytes(after extracting array buffer from base 64), to a wav file, I cant play it. Can someone help me what I am doing wrong on the .net controller side.
If someone knows a cleaner way of doing this, please let me know

You don't have to create an array buffer. Just use a file-input and send form-data.
Assuming you are on angular 4.3++ and using HttpClientModule from #angular/common/http:
The angular-service method
public uploadFile(file: File): Observable<any> {
const formData = new FormData();
formData.Append('myFile', file);
return this.http.post('my-api-url', formData);
}
now you asp.net-core endpoint
[HttpPost]
// attention name of formfile must be equal to the key u have used for formdata
public async Task<IActionResult> UploadFileAsync([FromForm] IFormFile myFile)
{
var totalSize = myFile.Length;
var fileBytes = new byte[myFile.Length];
using (var fileStream = myFile.OpenReadStream())
{
var offset = 0;
while (offset < myFile.Length)
{
var chunkSize = totalSize - offset < 8192 ? (int) totalSize - offset : 8192;
offset += await fileStream.ReadAsync(fileBytes, offset, chunkSize);
}
}
// now save the file on the filesystem
StoreFileBytes("mypath", fileBytes);
return Ok();
}

Related

Base64 to Image File Convertion in JS

I am working on a project where I have to upload an image as form data along with other text fields. I have my file in Base64 string at first, then I convert it into a file before uploading it to the server.
const data = await fetch(base64String);
const blob = await data.blob();
const file = await new File([blob], 'avatar', { type: 'image/png' });
I logged the base64String in the client side before uploading it to the server. Then I upload file to the server as a File. Before saving it to MongoDB when I log it as a base64 string again in the server side, I see my string is not the same as before. I feel like while converting the base64 to file in the client side I am doing something wrong. Help me out please.
I have figured out my problem. When I take image file input from my computer I get a base64 string like below -
dataimage/jpegbase64/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA...
But, when I convert it back into a file it expects a string like below -
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA....
So, basically, I had to trim the string accordingly to match the expected format and wrote a base64 to file conversion function following this answer.
Here is my function to convert a base64 string to an image file
export function getFileFromBase64(string64:string, fileName:string) {
const trimmedString = string64.replace('dataimage/jpegbase64', '');
const imageContent = atob(trimmedString);
const buffer = new ArrayBuffer(imageContent.length);
const view = new Uint8Array(buffer);
for (let n = 0; n < imageContent.length; n++) {
view[n] = imageContent.charCodeAt(n);
}
const type = 'image/jpeg';
const blob = new Blob([buffer], { type });
return new File([blob], fileName, { lastModified: new Date().getTime(), type });
}

Retrieving file data in chunks using Web API for display in browser (WIP)

I have this working but I want to share this out to see if I missed anything obvious and to solve a mystery as to why my file chunk size has to be a multiple of 2049. The main requirements are:
Files uploaded from website must be stored in SQL server, not as files
Website must be able to download and display file data as a file (opened in a separate window.
Website is angularjs/javascript SPA, no server side code, no MVC
API is Web API 2 (again not MVC)
I'm just going to focus on the download part here. Basically what I'm doing is:
Read a chunk of data from SQL server varbinary field
Web API 2 api returns file name, mime type and byte data as a base64 string. NOTE - tried returning byte array but Web API just serializes it into base64 string anyway.
concatenate the chunks, convert the chunks to a blob and display
VB library function that returns a dataset with the chunk (I have to use this library which handles the database connection but doesn't support parameter queries)
Public Function GetWebApplicationAttachment(ByVal intId As Integer, ByVal intChunkNumber As Integer, ByVal intChunkSize As Integer) As DataSet
' the starting number is NOT 0 based
Dim intStart As Integer = 1
If intChunkNumber > 1 Then intStart = ((intChunkNumber - 1) * intChunkSize) + 1
Dim strQuery As String = ""
strQuery += "SELECT FileName, "
strQuery += "SUBSTRING(ByteData," & intStart.ToString & "," & intChunkSize.ToString & ") AS ByteData "
strQuery += "FROM FileAttachments WHERE Id = " + intId.ToString + " "
Try
Return Query(strQuery)
Catch ex As Exception
...
End Try
End Function
Web API business rules bit that creates the file object from the dataset
...
result.FileName = ds.Tables[0].Rows[0]["FileName"].ToString();
// NOTE: Web API converts a byte array to base 64 string so the result is the same either way
// the result of this is that the returned data will be about 30% bigger than the chunk size requested
result.StringData = Convert.ToBase64String((byte[])ds.Tables[0].Rows[0]["ByteData"]);
//result.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
... some code to get the mime type
result.MIMEType = ...
Web API controller (simplified - all security and error handling removed)
public IHttpActionResult GetFileAttachment([FromUri] int id, int chunkSize, int chunkNumber) {
brs = new Files(...);
fileResult file = brs.GetFileAttachment(appID, chunkNumber, chunkSize);
return Ok(file);
}
angularjs Service that gets the chunks recurively and puts them together
function getFileAttachment2(id, chunkSize, chunkNumber, def, fileData, mimeType) {
var deferred = def || $q.defer();
$http.get(webServicesPath + "api/files/get-file-attachment?id=" + id + "&chunkSize=" + chunkSize + "&chunkNumber=" + chunkNumber).then(
function (response) {
// when completed string data will be empty
if (response.data.StringData === "") {
response.data.MIMEType = mimeType;
response.data.StringData = fileData;
deferred.resolve(response.data);
} else {
if (chunkNumber === 1) {
// only the first chunk computes the mime type
mimeType = response.data.MIMEType;
}
fileData += response.data.StringData;
chunkNumber += 1;
getFileAttachment2(appID, detailID, orgID, GUID, type, chunkSize, chunkNumber, deferred, fileData, mimeType);
}
},
function (response) {
... error stuff
}
);
return deferred.promise;
}
angular controller method that makes the calls.
function viewFile(id) {
sharedInfo.getWebPortalSetting("FileChunkSize").then(function (result) {
// chunk size must be a multiple of 2049 ???
var chunkSize = 0;
if (result !== null) chunkSize = parseInt(result);
fileHelper.getFileAttachment2(id, chunkSize, 1, null, "", "").then(function (result) {
if (result.error === null) {
if (!fileHelper.viewAsFile(result.StringData, result.FileName, result.MIMEType)) {
... error
}
result = {};
} else {
... error;
}
});
});
}
And finally the bit of javascript that displays the file as a download
function viewAsFile(fileData, fileName, fileType) {
try {
fileData = window.atob(fileData);
var ab = new ArrayBuffer(fileData.length);
var ia = new Uint8Array(ab); // ia provides window into array buffer
for (var i = 0; i < fileData.length; i++) {
ia[i] = fileData.charCodeAt(i);
}
var file = new Blob([ab], { type: fileType });
fileData = "";
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, fileName);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
return true;
} catch (e) {
... error stuff
}
}
I see already that a more RESTful approach would be to use headers to indicate chunk range and to separate the file meta data from the file chunks. Also I could try returning a data stream rather than Base64 encoded string. If anyone has tips on that let me know.
Well that was entirely the wrong way to go about that. In case it helps here's what I ended up doing.
Dynamically create the href address of an anchor tag to return a file (security token and parameters in query string)
get byte array from database
web api call return response message (see code below)
This is much faster and more reliable, but provides less in the way of progress monitoring.
business rule method uses...
...
file.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
...
web api controller
public HttpResponseMessage ViewFileAttachment([FromUri] int id, string token) {
HttpResponseMessage response = new HttpResponseMessage();
... security stuff
fileInfoClass file = ... code to get file info
response.Content = new ByteArrayContent(file.ByteData);
response.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") {
FileName = file.FileName
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue(file.MIMEType);
return response;
This could even be improved with streaming

Saving jpg file with cloud-code Parse-Server

I'm trying to save jpg files with cloud code on parse server ...
On Android I can do it using this way
Bitmap bitmap = ((BitmapDrawable) myImageView.getDrawable()).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte [] byteArrayPhotoUpdate = stream.toByteArray();
final ParseFile pictureFileParse = new ParseFile( newUserInfo.getObjectId() + ".JPEG",byteArrayPhotoUpdate);
newUserInfo.put("profile_picture",pictureFileParse);
newUserInfo.saveInBackground();
But I have no idea how to do this in the cloud code. I call my cloud code functions like this
HashMap<String, String> params = new HashMap();
ParseCloud.callFunctionInBackground("myCloudFuncion", params, new FunctionCallback<String>() {
#Override
public void done(String aFloat, ParseException e) {
}
});
but I have no idea how to pass a bitmap in hashmap params.
I already searched the internet, but nothing that I found in helped, the links that refer to something useful, is already old and outdated, from the epoch of the old parse ...
In parse docs I found this
var base64 = "V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=";
var file = new Parse.File("myfile.txt", { base64: base64 });
Which made me confused because I do not know if the 2 "base64" parameters refer to variable or base64 type
Should I convert my bitmap to base64 and send it as parameter to the cloud code?
If you have been through this and know how, I will be very happy to know your solution.
Thank you!
you need convert your image bitmap for base64 like that:
Bitmap bitmap = ((BitmapDrawable) img.getDrawable()).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte [] byteArrayPhotoUpdate = stream.toByteArray();
String encodedfile = new String(Base64.encodeBase64(byteArrayPhotoUpdate), "UTF-8");
And then, send your string base64 in params, like that:
HashMap<String, String> params = new HashMap();
params.put("fileInfo",encodedfile);
ParseCloud.callFunctionInBackground("saveParseUserInfo", params, new FunctionCallback<String>() {
#Override
public void done(String aFloat, ParseException e) {
Log.i("ewaeaweaweaweawe", "done: " + aFloat);
}
});
Now in your cloud code, use that:
Parse.Cloud.define("saveParseUserInfo", function(request, response) {
var userId = request.user.id;
var base64 = request.params.fileInfo;
var userClass = Parse.Object.extend("User");
//create a user object to set ACL
var userObject = userClass.createWithoutData(userId);
//create new ParseObject
var userPublicClass = Parse.Object.extend("userPublic");
var userPublic = new userPublicClass();
var aclAction = new Parse.ACL(userObject);
aclAction.setPublicReadAccess(true);
userPublic.setACL(aclAction);
userPublic.set("name", "name random");
userPublic.set("username", "username_random");
//Now create a Parse File object
var file = new Parse.File("photo.jpeg", { base64: base64 });
//set file object in a colum profile_picture
userPublic.set("profile_picture",file);
//save
userPublic.save(null, { useMasterKey: true,
success: function(actionSuccess) {
response.success("saved!!");
},
error: function(action, error) {
// Execute any logic that should take place if the save fails.
// error is a Parse.Error with an error code and message.
response.error(error.message);
}
});
});
I hope it's help you.
This answer works if you do not wish to use Base64 that requires API 26 and above for android.
I know João Armando has answered this question, but this is for the benefit of others who, like me, are supporting versions before API 26 for Android.
P.S. The Base64.encodeBase64(...) is deprecated and Base64.getEncoder()... is used now, which requires API 26.
There are 3 key parts to the solution:
Convert your bitmap to byteArray
Send this byteArray directly as params when calling your cloud function
Format this byteArray in cloud code itself
In Android:
Convert bitmap to byte[]
Bitmap bitmap = <Your source>;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
Send as params when calling cloud function
HashMap<String, Object> params = new HashMap<>();
params.put("imageInByteArray", byteArray);
ParseCloud.callFunctionInBackground("yourCloudFunction", params, new FunctionCallback<Map>() {
#Override
public void done(Map object, ParseException e) {
if(e == null){
// Success
} else {
// Failed
}
}
});
In cloud function/code
Depends on the version of javascript you use, the codes may differ. I am using a backend-as-a-service provider, which has improved from promises-related codes. The logic should still be applicable regardless.
Parse.Cloud.define("reportId", async request => {
// Retrieve and set values from client app
const imageInByteArray = request.params.imageInByteArray;
// Format as ParseFile
var file = new Parse.File("image.png", imageInByteArray);
// Initialize your class, etc.
....
// Save your object
await yourImageObject.save(null, {useMasterKey:true});
});

How can I send the POST request to the other server binding file into formdata

I have a pdf file which is generated into my local server with my server side code. I want to send a request to the another server requesting POST. The post method take parameter as FormData where formdata types
one is string and another is file type.
content-type
form-data
Body
 PDF file (file type)
string value
 
Is it possible to make the POST request without browsing the file location?
Doing some R&D I have overcome this problem with following some steps, as there is no way to get the file object from the physical location automatically in client side (basically in js) except browsing for security reason.
In my local server I have created a REST service. which response base64 string of the desired file.
Than I call the REST api from my javaScript and as a response I receive the base64 string. And than I convert it into bytes array and than Blob object and than File object.
base64 string==>bytes array==>Blob object==>File object
var base64 = this.getpdfFromLocal() //get the base64 string
var byteArray= this.base64ToByte(base64 );
var file = this.getFileFromByteArray(byteArray);
//return the byte array form the base64 string
MyApi.prototype.base64ToByte= function(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
};
MyApi.prototype.getFileFromByteArray=function(byteArray) {
var blob = new Blob([byteArray]);
var file = new File([blob], "resource.pdf");
return file;
};
Lastly I make from data using file object and send request the another server REST web services.
var formdata = new FormData();
formdata.append("some_value", "Some String");
formdata.append("file", file);
var url = "http://yoururl.com";
var result =$.ajax({
url : url ,
type : 'POST',
data : formdata,
contentType : false,
cache : false,
processData : false,
scriptCharset : 'utf-8',
async : false
}).responseText;

Sending Large file after converting to json to Action in MVC controller

I am reading a file in bytes using ReadAllBytes() method. Then convert these bytes to base64. Then send this base64 string as a part of JSON. On server side (which is MVC action) I receive the JSON and convert the base64 into bytes and then save it.
Small files in KB are transferring very fast and saving in temp folder. But the files in MB is not transferring at all.
I have set maxrequestlength, execution timeout done every thing with web config and even test by using httpwebrequest.keepalive = true and false.... but still all in vain. I can't send file chunks because its not the requirement. Want to send a Complete file at once...
byte[] b = File.ReadAllBytes("D://test.pdf");
string convert = Convert.ToBase64String(b);
Customer cs = new Customer();
cs.JsonString = convert;
JavaScriptSerializer js = new JavaScriptSerializer();
js.MaxJsonLength = Int32.MaxValue;
string json = js.Serialize(cs);
var request (HttpWebRequest)WebRequest.Create("http://localhost:46360/Home/Tester");
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
request.ContentLength = (json.Length);
request.KeepAlive = false;
request.Timeout = System.Threading.Timeout.Infinite;
request.Accept = "Accept=application/json";
request.SendChunked = false;
request.AllowWriteStreamBuffering = false;
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(json);
streamWriter.Close();
}
var response = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}

Categories

Resources