Jun
29
Written by:
Michael Washington
6/29/2017 6:31 AM

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.

The application allows you to create folders.

Folders can be nested.

Multiple files can be selected and uploaded.

Files can be downloaded and viewed by clicking on them.

Files and folders can be deleted by selecting their check box and then clicking the Delete button.
The Code

We start with the code from the article: Upgrading JavaScriptServices and PrimeNG From Angular 2 to Angular 4+
The Tree

The files and folders are displayed in the PrimeNG Tree control.

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

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);
}

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);

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

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

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

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

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
|