I am trying to upload file from desktop client to server using api. In our project, we are using Refit to create api.
I am trying to implement it according to Refit manual.
https://github.com/reactiveui/refit#multipart-uploads
This is Api interface method:
[Multipart]
[Post("/Projects/SaveProjectFile")]
Task<IApiResponse> SaveFileAsync([AliasAs("projectFile")] StreamPart stream, [Query] string projectId);
This is how I use it in code:
public async Task SaveProjectToCloudAsync()
{
using (var fileStream = new FileStream(_pdProject.currentFilePath, FileMode.Open))
{
var streamPart = new StreamPart(fileStream, "project.pd");
var response = await _projectApi.SaveFileAsync(streamPart, _projectToSave.Id);
}
}
And this is my api controller (It is not fished. I am just testing getting a file):
[HttpPost]
public async Task<IActionResult> SaveProjectFileAsync(IFormFile projectFile, [FromQuery] string projectId)
{
await using var saveFileStream = System.IO.File.OpenWrite($"{projectId}_{DateTime.Now.ToString().Replace(":",".")}.pd");
await projectFile.CopyToAsync(saveFileStream);
return Ok();
}
And my data gets to the controller. The problem is: during debug I can see that the server receives IFormFile with correct name of the file (project.pd), but there is no data. Length is zero. As a result it saves an empty file.
I cannot understand what am I doing wrong here, and googling did not help. I would highly appreciate any help you can give me.
There is no problem with the code you provided. The reason why you did not receive the file may be because of your path problem. You did not obtain the file through the corresponding path.
Please check if the length of fileStream is 0:
If the length of the fileStream is not 0, you should receive the file correctly:
Please double check whether you can get the corresponding file correctly through _pdProject.currentFilePath.
I used MVC to call the Api and kept the file in wwwroot:
public class HomeController : Controller
{
private readonly ISaveFile _projectApi;
private readonly IWebHostEnvironment _webHostEnvironment;
public HomeController(ISaveFile saveFile,IWebHostEnvironment webHostEnvironment)
{
_projectApi = saveFile;
_webHostEnvironment = webHostEnvironment;
}
public async Task<IActionResult> Test()
{
var path = Path.Combine(_webHostEnvironment.WebRootPath, "img1.jpg");
using (var fileStream = new FileStream(path, FileMode.Open))
{
var streamPart = new StreamPart(fileStream, "img1.jpg");
var response = await _projectApi.SaveFileAsync(streamPart, "project.pd");
}
return View();
}
}
As you can see, the file path is correct and the parameters of the Api are correct.
Related
I am trying to implement a code to read and write blob file using Azure Storage Blobs. I am referencing this article:
https://briancaos.wordpress.com/2021/06/29/read-and-write-blob-file-from-microsoft-azure-storage-with-net-core/
I am using .Net 6.0 and when the code executes _client.CreateIfNotExists throws an error saying The value for one of the HTTP headers is not in the correct format. I searched for the solution, but didn't see any. I see the old articles with old libraries which doesn't work for me. I am using the latest version of Azure.Storage.Blobs, that is 12.13.1.
private BlobContainerClient _client;
_client = new BlobContainerClient(connectionString, containerName);
// Only create the container if it does not exist
_client.CreateIfNotExists(PublicAccessType.BlobContainer);
Appreciate if someone can help me to solve this issue.
I tried in my environment with same code and got below results:
Code:
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Azurestorage
{
class program
{
public class BlobRepository
{
private BlobContainerClient _client;
public BlobRepository(string connectionString, string containerName)
{
_client = new BlobContainerClient(connectionString, containerName);
// Only create the container if it does not exist
_client.CreateIfNotExists(PublicAccessType.BlobContainer);
}
public async Task Upload(string localFilePath, string pathAndFileName, string contentType)
{
BlobClient blobClient = _client.GetBlobClient(pathAndFileName);
using FileStream uploadFileStream = File.OpenRead(localFilePath);
await blobClient.UploadAsync(uploadFileStream, new BlobHttpHeaders { ContentType = contentType });
uploadFileStream.Close();
}
public async Task<string> Download(string pathAndFileName)
{
BlobClient blobClient = _client.GetBlobClient(pathAndFileName);
if (await blobClient.ExistsAsync())
{
BlobDownloadInfo download = await blobClient.DownloadAsync();
byte[] result = new byte[download.ContentLength];
await download.Content.ReadAsync(result, 0, (int)download.ContentLength);
return Encoding.UTF8.GetString(result);
}
return string.Empty;
}
}
static async Task Main()
{
BlobRepository rep = new BlobRepository("<connection string>", "test");
await rep.Upload("C:\\Users\\v-vsettu\\Documents\\storage\\student1.json", "blobtest/student1.json", "application/json");
string jsonFile = await rep.Download("blobtest/student1.json");
}
}
}
Console:
Output:
The above code runned successfully and reflected in portal:
_client.CreateIfNotExists throws an error saying The value for one of the HTTP headers is not in the correct format
The above error occurs :
Ensure you have given access to the Allowed permissions of Shared Access Signature like below:
Make sure you have provided valid blob name and check whether you are using supported version of the blob Service.
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 tried to upload a photo using IFormFile using a postman plugin. But the API didn't get the file object from the body of the request. I tried with and without [FromBody].
[HttpPost]
public async Task<IActionResult> Upload(int vId, IFormFile fileStream)
{
var vehicle = await this.repository.GetVehicle(vId, hasAdditional: false);
if (vehicle == null)
return NotFound();
var uploadsFolderPath = Path.Combine(host.WebRootPath, "uploads");
if (!Directory.Exists(uploadsFolderPath))
Directory.CreateDirectory(uploadsFolderPath);
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(fileStream.FileName);
var filePath = Path.Combine(uploadsFolderPath, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await fileStream.CopyToAsync(stream);
}
The error shows on this line :
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(fileStream.FileName);
I figured out that it is not getting the file, while I am sending an image.jpg with the same key "fileStream". By the way everything else works fine. I found no solution to fix this issue. If anybody can help me with this please let me know.
The FromBody attribute can only be used on one parameter in the signature.
One option to send the int vId is by a query string and read it with the FromQuery attribute.
Try it like this
[HttpPost]
public async Task<IActionResult> Upload([FromQuery]int vId, [FromBody]IFormFile fileStream)
Then make the POST to url api/yourController?vId=123456789 where the body contains the IFromFile
Update
As the form-data will be sent as key-value try and create a model containing the keys and read it from the body
public class RequestModel
{
public IFormFile fileStream { get; set; }
}
Then read the model from the body
[HttpPost]
public async Task<IActionResult> Upload([FromBody]RequestModel model)
Finally got the solution. Actually the problem was with the old version of postman Tabbed Postman - REST Client chrome extension. After trying with new postman app it worked perfectly fine. Thanks all of you who tried to solve this problem. Here is the result:enter image description here
I want to upload file and some FormData to server. I'm did it like that and it is works:
On client side i use Angular 2 and logic looks follows:
1.In component
onLoadForeignLightCompanies(event: any) {
let fileList: FileList = event.target.files;
if (fileList.length > 0) {
let file: File = fileList[0];
let formData: FormData = new FormData();
formData.append('projectId', this.projectId);
formData.append('file', file);
this.companyService.uploadForeginLightCompany(formData).then(result => {});
this.isLoading = false;
window.location.reload();
}
}
2.In service
uploadForeginLightCompany(formData: FormData): Promise<any> {
return this.http.post(this.baseUrl + 'foreignLightCompanyImport', formData).toPromise();
}
On server side
public byte[] LoadUploadedFile(HttpPostedFile uploadedFile)
{
var buf = new byte[uploadedFile.InputStream.Length];
uploadedFile.InputStream.Read(buf, 0, (int)uploadedFile.InputStream.Length);
return buf;
}
[HttpPost, Route("foreignLightCompanyImport")]
public async void UploadForeignLigtCompanyCompanyFile()
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var file = LoadUploadedFile(HttpContext.Current.Request.Files[0]);
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
var projectId = provider.FormData.GetValues("projectId")[0];
((IForeginLightCompanyService) Service).UploadDataFromExcel(file, Convert.ToInt32(projectId));
}
The following construction is used on the server side. HttpContext.Current.Server.MapPath("~/App_Data");
But in this case, the files are stored on disk. I know how to upload a file without saving it to disk. But in this case I can not get other parameters. Like projectId e.t.c.
How this be made without saving file on disk?
May be i must using model on client side and send file like binary string in json with other parameters?
Is anybody knows a solution of this issue or maybe have thoughts on it?
A clean way would be to use a model:
public class UploadForeignLigtCompanyCompanyFile
{
public string ProjectId { get; set; }
public HttpPostedFileBase Attachment { get; set; }
}
And change your post action method to have a parameter of the model type:
public async void UploadForeignLigtCompanyCompanyFile(
UploadForeignLigtCompanyCompanyFile companyFile)
{
// code...
}
Of course you will need to change your view code to submit that info to the action method.
i try save file to server, i'm using the ng-file-upload directive, i added the next html - code:
<button class="button" ngf-select ng-model="fileCover" name="fileCover" ngf-pattern="'image/*'"
ngf-accept="'image/*'" ngf-max-size="20MB" ngf-min-height="100">Select</button>
<button type="submit" ng-click="submitUpload(fileCover)">submit</button>
and my angularjs-code:
$scope.submitUpload = function (fileCover) {
console.log(fileCover);
Upload.upload({
url: '/api/upload',
data: { file: fileCover }
});
};
And i have a empty controller:
[Route("upload")]
[HttpPost]
public void Upload ( )
{
}
Tell me please, how i can save files at server side ?Thanks for your answers!
I have a solution using an older version of ng-file-upload (angular-file-upload), and my WebApi method that received the file looks like this:
[HttpPost]
public async Task<HttpResponseMessage> Upload() {
try {
if (!Request.Content.IsMimeMultipartContent()) {
Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
var provider = GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
// On upload, files are given a generic name like "BodyPart_26d6abe1-3ae1-416a-9429-b35f15e6e5d5"
// so this is how you can get the original file name
var originalFileName = GetDeserializedFileName(result.FileData.First());
// uploadedFileInfo object will give you some additional stuff like file length,
// creation time, directory name, a few filesystem methods etc..
var uploadedFileInfo = new FileInfo(result.FileData.First().LocalFileName);
// Create full path for where to move the uploaded file
string targetFile = Path.Combine(uploadedFileInfo.DirectoryName, originalFileName);
// If the file in the full path exists, delete it first otherwise FileInfo.MoveTo() will throw exception
if (File.Exists(targetFile))
File.Delete(targetFile);
}
// Move the uploaded file to the target folder
uploadedFileInfo.MoveTo(targetFile);
// targetFile now contains the uploaded file
// Through the request response you can return an object to the Angular controller
// You will be able to access this in the .success callback through its data attribute
// If you want to send something to the .error callback, use the HttpStatusCode.BadRequest instead
return new HttpResponseMessage(HttpStatusCode.OK);
} catch (Exception ex) {
return new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new ObjectContent(ex.GetType(), ex, new JsonMediaTypeFormatter()) };
}
}
private MultipartFormDataStreamProvider GetMultipartProvider() {
var uploadFolder = #"C:\Temp"
if (Directory.Exists(uploadFolder) == false) Directory.CreateDirectory(uploadFolder);
return new MultipartFormDataStreamProvider(uploadFolder);
}
private string GetDeserializedFileName(MultipartFileData fileData) {
var fileName = GetFileName(fileData);
return JsonConvert.DeserializeObject(fileName).ToString();
}
private string GetFileName(MultipartFileData fileData) {
return fileData.Headers.ContentDisposition.FileName;
}
Hopefully the server side functionality hasn't changed much in the versions between what I'm using and the one you are using.