Jun
29
Written by:
Michael Washington
6/29/2017 6:31 AM
![](/images/Angular4FuileUploader.gif)
You can easily implement full multi-file upload and management in a .Net Core C# Angular 4+ application using PrimeNG FileUpload.
This example is more advanced than the previous example: Implementing PrimeNG FileUpload in a .Net Core Application as this one demonstrates full folder management.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_c36a56d8-95d3-4cd7-a90e-5b7f2fc379b3.png)
The application allows you to create folders.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_d191f740-268c-4dd9-9207-c7c67cf82040.png)
Folders can be nested.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_d26e2054-0f9e-4a91-831b-4885545bc036.png)
Multiple files can be selected and uploaded.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_2ed1500e-37c0-419f-ba0b-f1c9a3cf52fa.png)
Files can be downloaded and viewed by clicking on them.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_12e86a0f-07ef-4c30-a489-bbe4537f0dae.png)
Files and folders can be deleted by selecting their check box and then clicking the Delete button.
The Code
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_084c04e2-c5eb-4675-a96a-1a819bbeead2.png)
We start with the code from the article: Upgrading JavaScriptServices and PrimeNG From Angular 2 to Angular 4+
The Tree
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_086b1ef4-6e8d-477b-b68d-09d61d9edf87.png)
The files and folders are displayed in the PrimeNG Tree control.
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_e2dff56e-d089-4e24-a503-5b1a2646520b.png)
The code is contained in the FilesController.cs file.
The constructor contains code to create the needed root directory if it does not exist:
private readonly IHostingEnvironment _hostEnvironment;
public FilesController(IHostingEnvironment hostEnvironment)
{
_hostEnvironment = hostEnvironment;
// Set WebRootPath to wwwroot\Files directory
_hostEnvironment.WebRootPath =
System.IO.Path.Combine(
Directory.GetCurrentDirectory(),
@"wwwroot\Files");
// Create wwwroot\Files directory if needed
if (!Directory.Exists(_hostEnvironment.WebRootPath))
{
DirectoryInfo di =
Directory.CreateDirectory(_hostEnvironment.WebRootPath);
}
}
The SystemFiles method returns the data for the Tree control:
// api/Files/SystemFiles
#region public DTONode SystemFiles()
[HttpGet("[action]")]
public DTONode SystemFiles()
{
// Create Root Node
DTONode objDTONode = new DTONode();
if (Directory.Exists(_hostEnvironment.WebRootPath))
{
objDTONode.label = "Root";
objDTONode.data = "Root";
objDTONode.expandedIcon = "fa-folder-open";
objDTONode.collapsedIcon = "fa-folder";
objDTONode.children = new List<DTONode>();
// Get Files
ProcessDirectory(_hostEnvironment.WebRootPath, ref objDTONode);
}
return objDTONode;
}
#endregion
This method calls the recursive ProcessDirectory method that adds directories to the data returned:
#region public void ProcessDirectory(string targetDirectory, DTONode paramDTONode)
// Process all files in the directory passed in,
// and process the files they contain.
public void ProcessDirectory(string targetDirectory, ref DTONode paramDTONode)
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries)
{
ProcessFile(fileName, ref paramDTONode);
}
// subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
{
string WebRootPath = _hostEnvironment.WebRootPath + @"\";
// The directory label should only contain the name of the directory
string subdirectoryLabel = FixDirectoryName(subdirectory);
DTONode objDTONode = new DTONode();
objDTONode.label = subdirectoryLabel;
objDTONode.data = subdirectory.Replace(WebRootPath, "");
objDTONode.expandedIcon = "fa-folder-open";
objDTONode.collapsedIcon = "fa-folder";
objDTONode.children = new List<DTONode>();
objDTONode.type = "folder";
paramDTONode.children.Add(objDTONode);
ProcessDirectory(subdirectory, ref objDTONode);
}
}
#endregion
That method calls the ProcessFile method that adds files to the data returned:
#region public void ProcessFile(string path, DTONode paramDTONode)
// Insert logic for processing found files here.
public void ProcessFile(string path, ref DTONode paramDTONode)
{
string WebRootPath = _hostEnvironment.WebRootPath + @"\";
string FileName = Path.GetFileName(path);
string FilePath = path;
DTONode objDTONode = new DTONode();
objDTONode.label = FileName;
objDTONode.data = FilePath.Replace(WebRootPath, "");
objDTONode.expandedIcon = "fa-file";
objDTONode.collapsedIcon = "fa-file";
objDTONode.type = "file";
paramDTONode.children.Add(objDTONode);
}
#endregion
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_2956b7fa-a30c-46e3-84ff-a993f6ce22c8.png)
The Angular 4 code to call the server side is contained in the files.service.ts file:
import { Injectable } from '@angular/core';
import {
Http, Response, RequestOptions,
Request, RequestMethod, Headers
} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { IDTONode } from './DTONode';
@Injectable()
export class FilesService {
constructor(private _http: Http) { }
// ** Get files **
getFiles(): Observable<IDTONode> {
var _Url = 'api/Files/SystemFiles';
// Call the client side code
return this._http.get(_Url)
.map((response: Response) => <IDTONode>response.json())
.catch(this.handleError);
}
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_23d5ded1-b4d1-48af-858c-9d6e66757b05.png)
The service method is called by the getFilesAndFolders method in the files.component.ts file:
// Register the service
constructor(private _FilesService: FilesService) { }
ngOnInit(): void {
this.getFilesAndFolders();
}
public getFilesAndFolders() {
this.errorMessage = "";
//Clear Filelist
this.fileList = [];
// Call the service -- to get Files
this._FilesService.getFiles()
.subscribe((files) => {
// Show the Files in the Tree Control
this.fileList = files.children;
},
error => this.errorMessage = <any>error);
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_6c446352-5cf7-4132-9cb1-13d155d72188.png)
The following markup in the files.component.html file displays the tree:
<!-- Tree Node Control -->
<p-tree [value]="fileList"
selectionMode="checkbox"
[propagateSelectionUp]="false"
[(selection)]="selectedNodes"
[style]="{'width':'100%','max-height':'400px','overflow':'auto'}">
<ng-template let-node pTemplate="file">
<u>
<a href="Files/{{node.data}}"
target="_blank">{{node.label}}</a>
</u>
</ng-template>
<ng-template let-node pTemplate="folder">
<b>{{node.label}}</b>
</ng-template>
</p-tree>
Create Folder
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_11efd776-1cd8-4234-9e4c-fee4ae952516.png)
The Create Folder button opens a dialog that uses the following markup:
<p-dialog [(visible)]="showCreateFolderPopup"
modal="modal"
width="400"
height="230"
[responsive]="true"
[contentStyle]="{'overflow':'visible'}">
<p-header>
Create New Folder
</p-header>
<label for="FileFolderPopup">Parent Folder </label>
<p-dropdown id="FileFolderPopup"
[options]="foldersDropdown"
[(ngModel)]="selectedFolder"
[style]="{'width':'150px'}"></p-dropdown>
<br /><br />
<label for="FolderName">Folder Name </label>
<input id="FolderName" type="text" pInputText [(ngModel)]="NewFolderName" />
<p-footer>
<div class="ui-dialog-buttonpane ui-helper-clearfix">
<button pButton type="button"
(click)="CreateFolder()"
label="Create Folder"
icon="fa-plus-square"
class="ui-button-success"></button>
</div>
</p-footer>
</p-dialog>
This contains a dropdown that uses the following service method to get the folders that are displayed in the dropdown:
// ** Get folders **
getFolders(): Observable<IDTONode> {
var _Url = 'api/Folders/SystemFolders';
// Call the client side code
return this._http.get(_Url)
.map((response: Response) => <IDTONode>response.json())
.catch(this.handleError);
}
This calls the following server side code:
// api/Folders/SystemFolders
#region public DTONode SystemFolders()
[HttpGet("[action]")]
public DTONode SystemFolders()
{
objDTONode = new DTONode();
objDTONode.label = "TreeRoot";
objDTONode.data = "TreeRoot";
objDTONode.expandedIcon = "fa-folder-open";
objDTONode.collapsedIcon = "fa-folder";
objDTONode.children = new List<DTONode>();
if (Directory.Exists(_hostEnvironment.WebRootPath))
{
DTONode objNewDTONode = new DTONode();
objNewDTONode.label = "[Root]";
objNewDTONode.data = @"\";
objNewDTONode.expandedIcon = "fa-folder-open";
objNewDTONode.collapsedIcon = "fa-folder";
objNewDTONode.children = new List<DTONode>();
objDTONode.children.Add(objNewDTONode);
// Get Folders
ProcessDirectory(_hostEnvironment.WebRootPath, 0);
}
return objDTONode;
}
#endregion
This calls the following recursive method:
#region public void ProcessDirectory(string targetDirectory)
// Process the directory, on any directories
// that are found
public void ProcessDirectory(string targetDirectory, int paramNumberOfDots)
{
paramNumberOfDots++;
string WebRootPath = _hostEnvironment.WebRootPath + @"\";
// subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
{
// The directory label should only contain the name of the directory
string subdirectoryLabel = FixDirectoryName(subdirectory);
DTONode objNewDTONode = new DTONode();
objNewDTONode.data = subdirectory.Replace(WebRootPath, "");
objNewDTONode.expandedIcon = "fa-folder-open";
objNewDTONode.collapsedIcon = "fa-folder";
objNewDTONode.children = new List<DTONode>();
// Update the label to add dots in front of the name
objNewDTONode.label = $"{AddDots(paramNumberOfDots)}{subdirectoryLabel}";
objDTONode.children.Add(objNewDTONode);
ProcessDirectory(subdirectory, paramNumberOfDots);
}
}
#endregion
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_cf5ee1aa-73e1-4f20-af7e-aed7fb199572.png)
The Create Folder button, on the dialog, calls the following method:
public CreateFolder() {
// Construct the new folder
var NewFolder: string = this.selectedFolder + '\\' + this.NewFolderName;
// Call the service
this._FilesService.createFolder(NewFolder)
.subscribe(() => {
// Refresh the files and folders
this.getFilesAndFolders();
// Close popup
this.showCreateFolderPopup = false;
},
error => this.errorMessage = <any>error);
}
This calls the following service method:
// ** Create folder **
createFolder(paramFolder: string): Observable<void> {
var _Url = 'api/Folders';
// This is a Post so we have to pass Headers
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
// Call the client side code
return this._http.post(_Url,
JSON.stringify(paramFolder), options)
.catch(this.handleError);
}
This calls the following server side code:
// api/Folders/Post
[HttpPost]
#region public IActionResult Post([FromBody]string DirectoryPath)
public IActionResult Post([FromBody]string DirectoryPath)
{
// Fix DirectoryPath
if(DirectoryPath.IndexOf("\\\\") == 0)
{
// Four slashes indicates this is the root
// The slashes need to be removed to make the Path.Combine work
DirectoryPath = DirectoryPath.Replace("\\\\", "");
}
// Create path
string NewPath = Path.Combine(_hostEnvironment.WebRootPath, DirectoryPath);
if (!Directory.Exists(NewPath))
{
Directory.CreateDirectory(NewPath);
}
return Ok();
}
#endregion
Deleting Files And Folders
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_6bcbec78-53ef-4959-b2eb-c36341a156e1.png)
You can delete files and folders by selecting the check box next to each item and clicking the Delete button.
This calls the following method:
public deleteItems() {
// Create an Parent IDTONode
// and add the Children
let ParentDTONode: IDTONode = {
data: '',
label: '',
expandedIcon: '',
collapsedIcon: '',
children: [],
parentId: 0
}
this.selectedNodes.forEach((objTreeNode: TreeNode) => {
// Create a child IDTONode
// and Convert TreeNode to IDOTNode
let ChildDTONode: IDTONode = {
data: objTreeNode.data,
label: objTreeNode.label,
expandedIcon: objTreeNode.expandedIcon,
collapsedIcon: objTreeNode.collapsedIcon,
children: [],
parentId: 0
}
ParentDTONode.children.push(ChildDTONode);
});
// Call the service
this._FilesService.deleteFilesAndFolders(ParentDTONode)
.subscribe(() => {
// Refresh the files and folders
this.getFilesAndFolders();
},
error => this.errorMessage = <any>error);
}
This calls the following service code:
// ** Delete files and folders **
deleteFilesAndFolders(paramNode: IDTONode): Observable<void> {
var _Url = 'api/Files';
// This is a Post so we have to pass Headers
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
// Call the client side code
return this._http.post(_Url,
JSON.stringify(paramNode), options)
.catch(this.handleError);
}
This calls the following server side code:
// api/Files/DeleteFiles
[HttpPost]
#region public IActionResult Post([FromBody]DTONode paramDTONode)
public IActionResult Post([FromBody]DTONode paramDTONode)
{
// Loop through each node
foreach (DTONode objDTONode in paramDTONode.children)
{
if(objDTONode.expandedIcon == "fa-folder-open")
{
// This is a folder
DeleteFolder(objDTONode);
}
else
{
// This is a file
DeleteFile(objDTONode);
}
}
return Ok();
}
#endregion
This calls the following methods to delete the files and folders:
#region private void DeleteFile(DTONode objDTONode)
private void DeleteFile(DTONode objDTONode)
{
try
{
// Create path
string FullPath = Path.Combine(_hostEnvironment.WebRootPath, objDTONode.data);
if (System.IO.File.Exists(FullPath))
{
System.IO.File.Delete(FullPath);
}
}
catch
{
// Do nothing
}
}
#endregion
#region private void DeleteFolder(DTONode objDTONode)
private void DeleteFolder(DTONode objDTONode)
{
try
{
// Create path
string FullPath = Path.Combine(_hostEnvironment.WebRootPath, objDTONode.data);
if (Directory.Exists(FullPath))
{
Directory.Delete(FullPath, true);
}
}
catch
{
// Do nothing
}
}
#endregion
Uploading Files
![image image](/Portals/0/Blog/Files/1/4308/Windows-Live-Writer-fcade48fcdd9_12739-image_cbc2c13f-06c9-45f8-a9dd-2c888ad6c954.png)
You can upload one or more files to a directory.
The following is the markup for the file upload control and the folder dropdown:
<label for="FileFolder">File Folder </label>
<p-dropdown id="FileFolder"
[options]="foldersDropdown"
[(ngModel)]="selectedFolder"
[style]="{'width':'150px'}"></p-dropdown>
<br /><br />
<p-fileUpload name="myfile[]"
url="api/upload"
multiple="multiple"
(onBeforeUpload)="onBeforeUploadHandler($event)"
(onUpload)="onUploadHandler($event)">
</p-fileUpload>
This specifies that the following method is called right before the file(s) are uploaded, so that the selected folder can be specified:
public onBeforeUploadHandler(event)
{
// called before the file(s) are uploaded
// Send the currently selected folder in the Header
event.formData.append("selectedFolder", this.selectedFolder);
}
The PrimeNG File Upload control is configured to call the following server side code directly (however, this can be overridden by the Custom Upload method):
// api/Upload
[HttpPost]
#region public IActionResult Index(ICollection<IFormFile> files)
public IActionResult Index(ICollection<IFormFile> files)
{
if (!Request.HasFormContentType)
{
return BadRequest();
}
// Retrieve data from Form
var form = Request.Form;
// Retrieve SelectedFolder
string SelectedFolder = form["selectedFolder"].First();
// Process all Files
foreach (var file in form.Files)
{
// Process file
using (var readStream = file.OpenReadStream())
{
var filename = ContentDispositionHeaderValue
.Parse(file.ContentDisposition)
.FileName
.Trim('"');
filename = _hostEnvironment.WebRootPath + $@"\{SelectedFolder}\{filename}";
//Save file to harddrive
using (FileStream fs = System.IO.File.Create(filename))
{
file.CopyTo(fs);
fs.Flush();
}
}
}
return Ok();
}
#endregion
After the file(s) are uploaded, the following Angular method refreshes the Tree:
public onUploadHandler(event) {
// Called after the file(s) are upladed
// Refresh the files and folders
this.getFilesAndFolders();
}
Links
Implementing PrimeNG FileUpload in a .Net Core Application
PrimeNG FileUpload
Upgrading JavaScriptServices and PrimeNG From Angular 2 to Angular 4+
Download
The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx
You must have Visual Studio 2017 (or higher) installed to run the code.
2 comment(s) so far...
THANK YOU! I searched multiple places for the .NET Core endpoint code needed to understand the incoming form data correctly. This post got me moving forward again. Nicely done!
By Eric Barnes on
2/15/2018 6:43 AM
|
NICE POST
By William Astro on
11/15/2018 4:54 AM
|