We have a front-end flutter application, which should send file to our backend (ASP.NET Core Web API).
The question is: how should controller be constructed? I believe that it should be a POST-method, but how to get this file on the backend.
P.S. All requests come to our API in JSON format.
In dotnet core controller you can use IFormFile Interface to get files,
[HttpPost("upload-file")]
public async Task<IActionResult> UploadFile([FromQuery] IFormFile file){
if(file.Length > 0){
// Do whatever you want with your file here
// e.g.: upload it to somewhere like Azure blob or AWS S3
}
//TODO: Save file description and image URL etc to database.
}
In Flutter you need to send a Multipart POST request to include files with binary content (images, various documents, etc.), in addition to the regular text values.
import 'package:http/http.dart' as http;
Future<String> uploadImage(filename, url) async {
var request = http.MultipartRequest('POST', Uri.parse(url));
request.files.add(
http.MultipartFile.fromBytes(
'file',
File(filename).readAsBytesSync(),
filename: filename.split("/").last
)
);
var res = await request.send();
return res;
}
F# file upload took few hours to figure out giraffe and HTML when some other data need to be added + drag-drop
Here is code:
script [ _type "text/javascript"; _src "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js" ] [];
script [ _type "text/javascript"] [ rawText "
$(function() { $('#uploadForm').submit(function() {
if(!$('form input[type=file]').val()) {
alert('You must select a file!');
return false;
};});}); "];
form [_method "POST"; _action "/upload";
_enctype "multipart/form-data"; _id "uploadForm"; _name "uploadForm"]
h2 [] [ Text "Drop or select file to upload" ];
[ input [ _type "file"; _name "fileName"; _id "file"; ];
input [ _type "text"; _name "Title";];
button [ _type "submit"] [ str "Uppload" ];
];
and
let fileUploadHandler = fun (next : HttpFunc) (ctx : HttpContext) -> task {
return!
(match ctx.Request.HasFormContentType with
| false -> RequestErrors.BAD_REQUEST ("Bad file uppload request")
| true ->
let title = (ctx.Request.Form.Item("Title").ToString()) in
let file = ctx.Request.Form.Files |> Seq.head in
let fileName = file.FileName in
let stream = new MemoryStream() in
file.CopyTo( stream);
let content = Encoding.UTF8.GetString(stream.ToArray()) in
let db_ctx = mssql.GetDataContext() in
let row = db_ctx.Dbo.File.Create(content, fileName, title) in
db_ctx.SubmitUpdates();
htmlView indexView
)
next ctx
}
POST >=> route "/upload" >=> fileUploadHandler;
Related
This is my bucket policy
{
"Version" : "2012-10-17",
"ID" : "************",
"Statement" : [
{
"Sid" : "************",
"Effect" : "Allow",
"Principar" : "*",
"Action" : [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:GetObjectAcl"
],
"Resource" : "************************"
}
]
}
{
"Version" : "2012-10-17",
"ID" : "",
"Statement" : [
{
"Sid" : "",
"Effect" : "Allow",
"Principar" : "",
"Action" : [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:GetObjectAcl"
],
"Resource" : "***********************"
}
]
}
and here's the code I used to upload image:
[HttpPost]
public bool UploadFile(string file)
{
var s3Client = new AmazonS3Client(accesskey, secretkey, RegionEndpoint.APSoutheast1);
var fileTransferUtility = new TransferUtility(s3Client);
if (file.Length > 0)
{
var filePath = file;
var fileTransferUtilityRequest = new TransferUtilityUploadRequest
{
BucketName = bucketName,
FilePath = filePath,
StorageClass = S3StorageClass.StandardInfrequentAccess,
PartSize = 6291456, // 6 MB.
Key = keyName,
CannedACL = S3CannedACL.PublicRead
};
fileTransferUtilityRequest.Metadata.Add("param1", "Value1");
fileTransferUtilityRequest.Metadata.Add("param2", "Value2");
fileTransferUtility.Upload(fileTransferUtilityRequest);
fileTransferUtility.Dispose();
}
return true;
}
and getting "The bucket does not allow ACLs" even setting it to "ACLs enabled" in object ownership
#Rutger 's answer is correct, and now it's 2022, aws console has changed ( not a lot ,but some what ), so let me show the images:
1.assume you have created the s3 bucket, in the list page,
2.don't toggle the "block" options
3.find the ownership, then click edit.
4.edit the object owner ship (ACLs enabled)
5.now the edit button for ACL is clickable.
6.toggle the permissions you want and save changes.
it's done, now you can upload images to s3 via commandline and then visit them in your browser:
You should be able to go to the AWS S3 console and navigate to the bucket details for the bucket you try to write objects to. You'll see a tab called 'Permissions'. There you have the option to change the "Object Ownership" at a block with te same title.
Once there, you can choose the option "ACLs enabled".
After applying those changes, you should be able to write objects with ACL options.
We have two applications: A C# REST-API, and a Kotlin Android application, we are using Google Platform Cloud Bucket to host the images.
A picture will be uploaded on the Android application, but the C# REST-API needs to upload it to the Google Cloud Platform.
This is the working C# code to upload a file to the Google Cloud Buckets:
[HttpPost]
[Route("upload")]
public IActionResult Upload()
{
var storageClient = StorageClient.Create(google_credentials);
string fileToUpload ="/Users/niel/Downloads/new_cat.jpg";
using (var fileStream = new FileStream(fileToUpload, FileMode.Open,
FileAccess.Read, FileShare.Read))
{
storageClient.UploadObject("test_storage_fotos", "new_cat", "image/jpeg", fileStream);
}
Console.WriteLine("uploaded the file successfully");
return Ok();
}
Now I need to replace fileToUpload with the content from a POST-request. Is there a way to do this? Picture from Android app > C# API > Google Buckets? The link from the C# API to Google Buckets is already working.
Is there a way in Kotlin to somehow get the byte-string of an image, post it to my C# API who takes the content and puts it in a FileStream? I than can upload the FileStream using storageClient.UploadObject? Is this a possibility?
Thanks!
Yes, you can definitely do this. Just send the file over to the server via http protocol with multipart/form-data content type.
In kotlin you can use ktor or any other http library to do that.
For ktor you'll need to add an implementation dependency
implementation "io.ktor:ktor-client-android:1.5.4"
And you might also need to add additional permission in AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
Then you can send a file with this snippet. Notice that imageUri is a content uri, for file uri the code would be a bit different
private fun getFileName(resolver: ContentResolver, uri: Uri): String {
val returnCursor: Cursor = resolver.query(uri, null, null, null, null)!!
val nameIndex: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
val name: String = returnCursor.getString(nameIndex)
returnCursor.close()
return name
}
suspend fun postAndImage(imageUri: Uri, uploadEndPoint: String) {
val client = HttpClient(Android)
val cr = applicationContext.contentResolver
if(cr.getType(imageUri) == null) {
//process error
return
}
val stream = cr.openInputStream(imageUri)
if(stream == null) {
//process error
return
}
val response: HttpResponse = client.submitFormWithBinaryData(
url = uploadEndPoint,
formData = formData {
append("image", InputProvider { stream.asInput() }, Headers.build {
append(HttpHeaders.ContentType, cr.getType(imageUri)!!)
append(HttpHeaders.ContentDisposition, "filename=${getFileName(cr, imageUri)}")
})
}
)
stream.close()
//process response
}
And you'll need to modify you upload function slightly
[HttpPost]
[Route("upload")]
//the name of the argument must match the key that you pass in "append" function
public async Task<IActionResult> Post(IFormFile image)
{
var storageClient = StorageClient.Create(google_credentials);
using (var stream = image.OpenReadStream())
{
//it's also possible to get original file name from file name property
var fileName = Guid.NewGuid() + "." + Path.GetExtension(image.FileName);
//assuming bucket is already created
var storageObject = await storageClient
.UploadObjectAsync("test_storage_fotos", fileName, "image/jpeg", stream);
//save information about a storage object in database
}
return Ok();
}
I am making a flutter app and using the VideoPlayerController library package and requesting video content via network:
VideoPlayerController newController = VideoPlayerController.network(
"http://192.168.1.1:9999/S3/get-object/name-of-video.mp4");
My Web API Backend is .NET Core 3 and the controller endpoint is this:
[AllowAnonymous]
[HttpGet("get-object/{url}")]
public async Task<FileStreamResult> GetObject(string url)
{
// Seperate out only the filename
string[] res = url.Split(new string[] { "%2F" }, StringSplitOptions.None);
string fileName = res.LastOrDefault();
Stream imageStream = await S3Helper.ReadObjectData(_appSettings, fileName);
Response.Headers.Add("Content-Disposition", new ContentDisposition
{
FileName = fileName,
Inline = true // false = prompt the user for downloading; true = browser to try to show the file inline
}.ToString());
if (fileName.Contains(".jpg") || fileName.Contains(".jpeg"))
{
return File(imageStream, "image/jpeg");
}
else if (fileName.Contains(".png"))
{
return File(imageStream, "image/png");
}
else if (fileName.Contains(".mp4"))
{
return File(imageStream, new MediaTypeHeaderValue("video/mp4").MediaType, true);
}
else
{
return null;
}
}
However, when I create a widget that uses a Network image, it actually works. I'm not sure what the difference is.
CachedNetworkImage(
imageUrl: "http://192.168.1.1:9999/S3/get-object/name-of-image.jpg",
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) =>
Icon(Icons.error),
fit: BoxFit.contain,
),
The .Net Core Backend has the video coming via an http get request as an inline video, similar to this one:
https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4
The error I get from my flutter app shows up like this:
Source error. E/ExoPlayerImplInternal(24687): com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: Unable to connect to http://192.168.1.1:9999/S3/get-object/name-of-video.mp4
I don't know about ios. But android doesn't allow http://. You need to provide a link starting https://.
To allow the http:// or others
add this line on androids AndroidManifest.xml.
Location android\app\src\main\AndroidManifest.xml
android:usesCleartextTraffic="true"
This should look like this:
Previously I was sending file as Byte array from ASP.net core 2.0 and in Angular 4 application I am calling below function to download the file
function (response) { // Here response is byte array
var url= window.URL.createObjectURL(res);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", this.zipLocation + ".zip");
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
But now I want to send the file path from the server like below
https://websiteaddress/file/path/to/download.ext
So in Angular 5, I can directly attach link to href attribute of anchor tag and will make automatic click on that. So I don't need to Convert byte array to url
Here the issue is I don't know how to create that downloadable file path using ASP.net core and send it to frontend
And also I want to know, which approach is better, whether sending Byte array or Sending the direct link? Is there any performance issue with any of the two?
If you are using api response as file data
add responseType: 'arraybuffer' in request header.
Try something like this:
HTML:
<a (click)="downLoad()">Click To Download</a>
TS:
downLoad(){
this.fileService.getFileFromServer(fileId.toString()).subscribe(respData => {
this.downLoadFile(respData, this.type);
}, error => {
});
}
/**
* Method is use to download file.
* #param data - Array Buffer data
* #param type - type of the document.
*/
downLoadFile(data: any, type: string) {
var blob = new Blob([data], { type: type.toString() });
var url = window.URL.createObjectURL(blob);
var pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
console.log('Please disable your Pop-up blocker and try again');
}
}
file-service.ts:
getFileFromServer(id){
return this.http.get(url, {responseType: 'arraybuffer',headers:headers});
}
your question make confuse about angular frontend and backend
frontend you can use mvc
<a asp-controller="Controller"
asp-action="Download"
asp-route-id="#Model.FileName">Download #Model.FileName</a>
or using angular
Download
<a [href]="ControllerRoute+'/Download?name='+fileName" download>Download {{fileName}}</a>
Ok maybe your problem is your action (in controller) doesnt server a file
you need return a HttpResponse with a MediaType, this is just a example, dont forget best practices on your code
[HttpGet]
public HttpResponseMessage GetDownloadableFIle(string name)
{
try
{
var result = new HttpResponseMessage(HttpStatusCode.OK);
var filePath = $"{MyRootPath}/{name}";
var bytes = File.ReadAllBytes(filePath );
result.Content = new ByteArrayContent(bytes);
var mediaType = "application/octet-stream";
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mediaType);
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.ToString()));
}
}
So I have a pdf that I generated using PDF Sharp and saved within my App_Data folder. When the user clicks a button on my controller, I want the user be able to download that file.
I've managed to get it to work (sort of) in the sense that the user can download the file, however when they open up this pdf file the document is blank and appears to be nearly twice the size of the original file saved in my App_Data folder. I'm not sure what's going wrong, but can only guess it's something to do with how I'm reading the file and streaming that data.
Anyway this is the web api controller function:
[HttpGet]
public HttpResponseMessage DownloadPdf(int itemId)
{
var item = _myService.GetItem(itemId);
var result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(File.ReadAllBytes(HostingEnvironment.MapPath(item.ReportFilePath)));
result.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(HostingEnvironment.MapPath(item.ReportFilePath));
return result;
}
And this is the relevant function on my angular controller (note the businessResource call just routes the data to my web api function via an $http call):
$scope.downloadPdf = function() {
businessResource.downloadPdf($scope.itemId).then(function (response) {
var headers = response.headers;
var filename;
var disposition = headers('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
var link = document.createElement('a');
if ('download' in link) {
// Prepare a blob URL
var blob = new Blob([response.data], { type: headers('content-type') });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
}
}
}, function (response) {
notificationsService.error("Error", "Could not generate report");
});
}
So just to clarify: The call is working in the sense I'm getting a pdf file downloaded, however this file seems to contain no content when I open it in the browser/pdf reader and is nearly twice the size of the original file I'm storing in the App_Data folder.
I don't know where the data is getting corrupted.. any ideas?
Figured it out:
The $http.get that was making the call to the web api needed to be changed from:
return $http.get('<end point url>);
to:
return $http.get('<end point url>', { responseType: 'arraybuffer' });
ie. the responseType needs to be set to 'arraybuffer'. Hope this helps someone!