Jun 29

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

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

The application allows you to create folders.

image

Folders can be nested.

image

Multiple files can be selected and uploaded.

image

Files can be downloaded and viewed by clicking on them.

image

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

The Code

image

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

The Tree

image

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

image

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

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

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

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

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

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

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

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.


Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation