Dynamic saving of files .net core - c#

I have this function that save the file in the wwwroot foldar:
[HttpPost]
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return Content("file not selected");
var path = Path.Combine( Directory.GetCurrentDirectory(), "wwwroot", file.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return RedirectToAction("Files");
}
I'm trying to save the files dynamically so that every time a file is uploaded the function will check whether there is a folder with the user ID (from the session). If the folder exists, it is saved there, otherwise, it will open a new folder with his ID.
I want to create a sub folder for each userId and save user specific file to that folder

I added this and now it'ts working
var userId = HttpContext.Session.GetString("UserId");
if (!Directory.Exists(Path.Combine(
Directory.GetCurrentDirectory(), $"wwwroot/{userId}")))
{
Directory.CreateDirectory(Path.Combine(
Directory.GetCurrentDirectory(), $"wwwroot/{userId}"));
}

The easiest way to get the current user's id is:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
Then, you just work this into your path:
var path = Path.Combine( Directory.GetCurrentDirectory(), $"wwwroot/users/{userId}", file.FileName);

Related

Is there any way to upload a folder(have subfolders also) instead of file using .netcore web api

Is it possible to upload the entire folder with same folder structure using .netcore webapi. I know we can zip and upload, then unzip, just wondering for alternative way to upload as it is instead of zipping
Currently i have updated the controller as below, this logic doesnot consider folders for upload
private async Task<bool> Import()
{
bool isSaveSuccess = false;
string fileName;
var files = Request.Form.Files;
foreach (var file in files)
{
try
{
var extn = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
fileName = file.FileName + DateTime.Now.Ticks + extn;
//fileName = Path.Combine(file.FileName, DateTime.Now.Ticks + extn);
var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files1");
if (!Directory.Exists(pathBuilt))
{
Directory.CreateDirectory(pathBuilt);
}
var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files1", fileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
isSaveSuccess = true;
}
catch
{
_logger.LogError("Request failed: " + "err");
}
}
return isSaveSuccess;
}
Yes, it should be possible. Have a look at webkitdirectory property: https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
This will let you select one or more folders, so you can iterate over the inner contents of the users selection. As you can see, this is a "native" feature and not limited to asp.net or c# in any way.
<input type="file" id="files" name="files" webkitdirectory multiple />
<ul id="listing"></ul>
document.getElementById("filepicker").addEventListener("change", function(event) {
let output = document.getElementById("listing");
let files = event.target.files;
for (let i=0; i<files.length; i++) {
let item = document.createElement("li");
item.innerHTML = files[i].webkitRelativePath;
output.appendChild(item);
};
}, false);
As you can see from the demo code, you can get the relative path by calling .webkitRelativePath, so yit should be no problem to recreate the folder structure on your target.
Be aware, that this feature is not natively available on Internet Explorer and Firefox versions on android below 50.
Update for saving files with subfolders
Here is a bare minimum example of uploading taking any number of uploaded form files and saving them to disk, while keeping the relative folder structure.
First change your controllers constructor to get a your current environment in:
private readonly IWebHostEnvironment _env;
public HomeController(IWebHostEnvironment environment)
{
_env = environment;
}
Then amend your upload logic accordingly:
[HttpPost]
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files)
{
//target in wwwroot for static files
string targetFolder = Path.Combine(_env.WebRootPath, "uploads");
foreach (IFormFile file in files)
{
if (file.Length <= 0) continue;
//fileName is the the fileName including the relative path
string path = Path.Combine(targetFolder, file.FileName);
//check if folder exists, create if not
var fi = new FileInfo(path);
fi.Directory?.Create();
//copy to target
using var fileStream = new FileStream(path, FileMode.Create);
await file.CopyToAsync(fileStream);
}
return View("Index");
}
This will result to your folder and all its subfolders being saved into your wwwroot/uploads/ folder inside your application:
wwwroot/
|
|-uploads
|-MyFolder
|- File1.jpg
|- File2.jpg
|- MyFolder2
|- File3.jpg
I'd recommend saving them somewhere different though, because redeploying might clean out the application folder first, depending on your deployment strategy (xcopy, github actions, azure pipelines, containers ...)

Issue of Set Download File name from controller MVC Core

I tried to get and download file from server local folder to client side. But When I tried to download(also download operation is successfully), Downloaded file's name is set automatically Action Name. How can I change downloaded file name ?
MyController:
[HttpGet]
public PhysicalFileResult MYACTIONDOWNLOAD(string filePathAndName)
{
string downloadPath = Path.Combine(Directory.GetCurrentDirectory(), #"C:\", filePathAndName);
string filesMimeType = MimeTypesMap.GetMimeType(filePathAndName);
return new PhysicalFileResult(downloadPath, filesMimeType);
}
client side(view script):
<a target="_blank" href="(my website www root url)/MYPROJECTNAME/MYCONTROLLERNAME/MYACTIONDOWNLOAD?filePathAndName=\\192.168.X.X\MYREMOTEDISC-1\MYDOCUMENTS\SCHOOL\10012021_1023350.docx"></a>
when I clicked download operation is okey but downloaded file name is MYACTIONDOWNLOAD.docx
I want to change file name.
You can just change your code like following:
return new PhysicalFileResult(downloadPath, filesMimeType) { FileDownloadName = "Test.doc"};
You can try this method instead.
[HttpGet]
public async Task<IActionResult> Download(string path)
{
var memory = new MemoryStream();
string webRootPath = _HostEnvironment.WebRootPath;
var uploads = Path.Combine(webRootPath + path);
using (var stream = new FileStream(uploads, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var ext = Path.GetExtension(uploads).ToLowerInvariant();
return File(memory, "application/octet-stream", "any file name");
}
_HostEnvironment.WebRootPath will be give the absolute path of the server. If that comes from your URL then you can avoid this.
Instaed of path use
var bytes = File.ReadAllBytes("your path" )
and then
return File(bytes, mimetype,"name you want" );

Download and upload DriveItem from shared OneDrive Folder with MS Graph SDK

I'm currently trying to implement several tasks that involve listing, uploading and downloading files from a shared OneDrive folder. This folder is accesible via the logged in users OneDrive (visible in his root folder). The listing part works pretty well so far, using this code:
string remoteDriveId = string.Empty;
private GraphServiceClient graphClient { get; set; }
// Get the root of the owners OneDrive
DriveItem ownerRoot = await this.graphClient.Drive.Root.Request().Expand("thumbnails,children($expand=thumbnails)").GetAsync();
// Select the shared folders general information
DriveItem sharedFolder = ownerRoot.Children.Where(c => c.Name == "sharedFolder").FirstOrDefault();
// Check if it is a remote folder
if(sharedFolder.Remote != null)
{
remoteDriveId = item.RemoteItem.ParentReference.DriveId;
// Get complete Information of the shared folder
sharedFolder = await graphClient.Drives[remoteDriveId].Items[sharedFolder.RemoteItem.Id].Request().Expand("thumbnails,children").GetAsync();
}
So obviously I need to retrieve the shared folders information from the OneDrive that shared it with the other OneDrive.
Next part for me is to list the contents of this shared folder, which also works pretty well like this:
foreach (DriveItem child in sharedFolder.Children)
{
DriveItem childItem = await graphClient.Drives[remoteDriveId].Items[child.Id].Request().Expand("thumbnails,children").GetAsync();
if(childItem.Folder == null)
{
string path = Path.GetTempPath() + Guid.NewGuid();
// Download child item to path
}
}
My problem starts with the "Download child item to path" part. There I want to download everything, that is not a folder to a temporary file. The problem is that OneDrive always answers my request with an error message, that the file was not found. What I tried so far is:
using (var stream = await graphClient.Drives[remoteDriveId].Items[childItem.Id].Content.Request().GetAsync())
using (var outputStream = new System.IO.FileStream(path, System.IO.FileMode.Create))
{
await stream.CopyToAsync(outputStream);
}
In another variant I tried to use the ID of the childItem ParentReference (but I think this will only lead me to the remote OneDrives ID of sharedFolder):
using (var stream = await graphClient.Drives[remoteDriveId].Items[childItem.ParentReference.Id].Content.Request().GetAsync())
using (var outputStream = new System.IO.FileStream(path, System.IO.FileMode.Create))
{
await stream.CopyToAsync(outputStream);
}
After Downloading the files I want to edit them and reupload them to a different path in the shared folder. That path is created by me (which allready works) like this:
DriveItem folderToCreate = new DriveItem { Name = "folderName", Folder = new Folder() };
await graphClient.Drives[remoteDriveId].Items[sharedFolder.Id].Children.Request().AddAsync(folderToCreate);
The upload then fails. I've tried it like this:
using (var stream = new System.IO.FileStream(#"C:\temp\testfile.txt", System.IO.FileMode.Open))
{
await graphClient.Drives[remoteDriveId].Items[sharedFolder.Id].Content.Request().PutAsync<DriveItem>(stream);
}
And also like this (which works if it is not a shared folder and I therefore use Drive instead of Drives):
using (var stream = new System.IO.FileStream(#"C:\temp\testfile.txt", System.IO.FileMode.Open))
{
string folderPath = sharedFolder.ParentReference == null ? "" : sharedFolder.ParentReference.Path.Remove(0, 12) + "/" + Uri.EscapeUriString(sharedFolder.Name);
var uploadPath = folderPath + "/" + uploadFileName;
await graphClient.Drives[remoteDriveId].Root.ItemWithPath(uploadPath).Content.Request().PutAsync<DriveItem>(stream);
}
I couldn't get the AddAsync method (like in the folder creation) to work because I don't know how to create a DriveItem from a Stream.
If somebody could point me in the right direction I would highly appreciate that! Thank you!
The request:
graphClient.Drives[remoteDriveId].Items[childItem.ParentReference.Id].Content.Request().GetAsync()
corresponds to Download the contents of a DriveItem endpoint and is only valid if childItem.ParentReference.Id refers to a File resource, in another cases it fails with expected exception:
Microsoft.Graph.ServiceException: Code: itemNotFound Message: You
cannot get content for a folder
So, to download content from a folder the solution would be to:
enumerate items under folder: GET /drives/{drive-id}/items/{folderItem-id}/children
per every item explicitly download its content if driveItem corresponds to a File facet: GET /drives/{drive-id}/items/{fileItem-id}/content
Example
var sharedItem = await graphClient.Drives[driveId].Items[folderItemId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sharedItem.Children)
{
if (item.File != null)
{
var fileContent = await graphClient.Drives[item.ParentReference.DriveId].Items[item.Id].Content.Request()
.GetAsync();
using (var fileStream = new FileStream(item.Name, FileMode.Create, System.IO.FileAccess.Write))
fileContent.CopyTo(fileStream);
}
}
Example 2
The example demonstrates how to download file from a source folder and upload it into a target folder:
var sourceDriveId = "--source drive id goes here--";
var sourceItemFolderId = "--source folder id goes here--";
var targetDriveId = "--target drive id goes here--";
var targetItemFolderId = "--target folder id goes here--";
var sourceFolder = await graphClient.Drives[sourceDriveId].Items[sourceItemFolderId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sourceFolder.Children)
{
if (item.File != null)
{
//1. download a file as a stream
var fileContent = await graphClient.Drives[item.ParentReference.DriveId].Items[item.Id].Content.Request()
.GetAsync();
//save it into file
//using (var fileStream = new FileStream(item.Name, FileMode.Create, System.IO.FileAccess.Write))
// fileContent.CopyTo(fileStream);
//2.Upload file into target folder
await graphClient.Drives[targetDriveId]
.Items[targetItemFolderId]
.ItemWithPath(item.Name)
.Content
.Request()
.PutAsync<DriveItem>(fileContent);
}
}
Instead of downloading/uploading file content, i think what you are actually after is DriveItem copy or move operations. Lets say there are files that needs to be copied from one (source) folder into another (target), then the following example demonstrates how to accomplish it:
var sourceDriveId = "--source drive id goes here--";
var sourceItemFolderId = "--source folder id goes here--";
var targetDriveId = "--target drive id goes here--";
var targetItemFolderId = "--target folder id goes here--";
var sourceFolder = await graphClient.Drives[sourceDriveId].Items[sourceItemFolderId].Request().Expand(i => i.Children).GetAsync();
foreach (var item in sourceFolder.Children)
{
if (item.File != null)
{
var parentReference = new ItemReference
{
DriveId = targetDriveId,
Id = targetItemFolderId
};
await graphClient.Drives[sourceDriveId].Items[item.Id]
.Copy(item.Name, parentReference)
.Request()
.PostAsync();
}
}
}

Asp.net Core File Upload

when i save image, image save successfully in database, but it takes full image path like this C:\Users....\Uploads\employee.jpg i dont want like this, i need to save image path somehting like this ~Uploads\employee.jpg and in specific folder and same path should save in database, also if someone show me after saving correct path how i can view that image. there is error i get because of this:
"Not allowed to load local resource :file:///C:/......"
thank you!
my code:
[HttpPost]
public async Task<IActionResult> Create(Photos photos)
{
if (ModelState.IsValid)
{
var filePath =
Path.Combine(_appEnvironment.ContentRootPath,
"Uploads", photos.FormFile.FileName);
photos.PhotoPath = filePath;
using (var stream = new FileStream(filePath, FileMode.Create))
{
await photos.FormFile.CopyToAsync(stream);
}
_context.Add(photos);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["NewsId"] = new SelectList(_context.News, "NewsId",
"NewsBigTitle", photos.NewsId);
return View(photos);
}
Break the code down:
var localPath = Path.Combine("Upload", "file.jpg");
var fullPath = Path.Combine(_appEnvironment.ContentRootPath, localPath);
Save the localPath to PhotoPath
Edit
Okey so now bring up your PhotoPath in a View, and make it target a file stream action.
[HttpGet("path/{image}")]
public FileStreamResult Image(string image)
{
var result = new FileStreamResult(new FileStream(
Path.Combine(_appEnvironment.ContentRootPath, image),
FileMode.Open,
FileAccess.Read), "image/<your_mime>");
return result;
}
The best way I think is to create a new string in the following format http://example.com/path/image.jpg and bind it to src.
You can't target dynamically added files by using the following: ~/path/image.jpg for your source.
Make sure you have configured IFileProvider pointing to your Uploads folder in Configure method of Startup class:
app.UseStaticFiles(); // Default one
// Adding one more location - folder named `Uploads` to be a custom static files folder.
// The `Uploads` folder is available in the content root directory of the application (where your `Controllers` folder.
// You can point it of course inside `wwwroot` - use `env.WebRootPath` instead)
app.UseStaticFiles(new StaticFileOptions {
FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "Uploads")),
RequestPath = new PathString("/Uploads")
});
Once you do this you should be able to upload the file this way in your controller action:
var filePath = Path.Combine(_environment.ContentRootPath, "Uploads", photos.FormFile.FileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await photos.FormFile.CopyToAsync(stream);
}

Removing the file with same name, extension doesn't matter

I have some files in "~Content/Documents" folder which holds every uploaded file. In my case the user can only upload one file.
I have done the uploading part where the user can upload his file.
if (file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var fullpath = System.Web.HttpContext.Current.Server.MapPath("~/Content/Documents");
file.SaveAs(Path.Combine(fullpath,"document"+Path.GetExtension(fileName)));
}
My problem is:
User can upload either ".doc", ".docx", ".xls", ".xlsx", or ".pdf" format files.
Now when the user upload the file of ".doc" format it is uploaded to the folder. Later the same user can upload the file of ".pdf" format which is also uploaded to the folder. That means the user can upload two files.
Now what I want to do is:
When a specific user uploads his document:
->search whether the document uploaded by the user is in that folder or not. i.e. the specific filename with different extension exists or not.
->if the filename already exists with different extension then remove that file and upload the new file.
Try this, Just another way; If your filename is "document"
string[] files = System.IO.Directory.GetFiles(fullpath,"document.*");
foreach (string f in files)
{
System.IO.File.Delete(f);
}
So your code would be;
if (file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var fullpath = System.Web.HttpContext.Current.Server.MapPath("~/Content/Documents");
//deleting code starts here
string[] files = System.IO.Directory.GetFiles(fullpath,"document.*");
foreach (string f in files)
{
System.IO.File.Delete(f);
}
//deleting code ends here
file.SaveAs(Path.Combine(fullpath,"document"+Path.GetExtension(fileName)));
}
Something like this should do the trick
var files = new DirectoryInfo(fullpath).GetFiles();
var filesNoExtensions = files.Select(a => a.Name.Split('.')[0]).ToList();
//for below: or 'document' if that's what you rename it to be on disk
var fileNameNoExtension = fileName.Split('.')[0];
if (filesNoExtensions.Contains(fileNameNoExtension))
{
var deleteMe = files.First(f => f.Name.Split('.')[0] == fileNameNoExtension);
deleteMe.Delete();
}
file.SaveAs(Path.Combine(fullpath,"document"+Path.GetExtension(fileName)));
Get the filename of the new file without extension, then loop through all the filenames in the folder where it will be uploaded to and check if the name already exists. If so, delete the old an upload, else upload.
var info = new FileInfo("C:\\MyDoc.docx");
var filename = info.Name.Replace(info.Extension, "");
var files = Directory.GetFiles("YOUR_DIRECTORY").Select(f => new FileInfo(f).Name);
if (files.Any(file => file.Contains(filename)))
{
//Delete old file
}
//Upload new file

Categories