A File System Manager From Scratch In .NET Core And VueJS

Imagine a file system manager like Windows Explorer that shows files and folders from a computer in a treeview frontend.
 
This article is a partnership with Lucas Juliano, friend, workmate, and my blog’s editor.
 
There is no fun --  just writing methods and functions to interact with the system operational file system, so let’s use some Design Patterns to organize our C# code.
 
Before creating all frontend in VueJS, let’s create an API to access the file system and then use jQuery to show them.
 
At least, our File System Manager will earn a new clean frontend in VueJS, more friendly and, with more performance.
 

File System – What are we going to do?

 
API .NET CORE C#
 
We are going to create a new .NET CORE API to interact and read all the files and folders from the file system.
 
Tag Helpers MVC .NET CORE
 
We are going to use the Tag Helper concept to create a file system manager component to use more than one time or more than one page.
 
jQuery frontend
 
Create a simple jQuery Frontend to interact with the DOM and consume the file system manager API.
 
VueJS frontend
 
Create a new frontend in VueJS, consuming the same API above. Lucas Juliano developed that frontend.
 
Design Patterns
 
For the API, we are going to use some Design Patterns like Repository, Dependency Injection, Strategy, and Factory.
 
I have written an eBook about Design Patterns, but I haven’t translated  it into English yet.
 

jQuery and VueJS frontend preview

 
One of my students gave me a tip about showing in an article what to expect before showing any code.
 
His concern was the benefit of showing the final project before all explanations and coding to get there.
 
So Mario, for you, this is the final project:
 
jQuery frontend
 
A File System Manager From Scratch In .NET Core And VueJS
VueJS frontend
 
A File System Manager From Scratch In .NET Core And VueJS
 
The file system manager is not a big deal, so let’s check in more detail how we did it.
 

ASP.NET CORE API – Software Architecture

 
For understanding purposes, I had made a class diagram separating all responsibilities in the solution. 
A File System Manager From Scratch In .NET Core And VueJS 
 
The FileSystemController knows about an IFileSystemService interface that contains all methods to return files and folders.  
  1. public interface IFileSystemService  
  2. {  
  3.     IEnumerable<FileSystemObject> GetAllFileSystemObject(  
  4.         string fullName);  
  5.    
  6.     object ToJson(  
  7.         IEnumerable<FileSystemObject> objs,  
  8.         IFileSystemFormatter fileSystemFormatter = null);  
  9.    
  10.     bool DirectoryExists(  
  11.         string fullName);  
  12. }  
The FileSystemService that implements IFileSystemService knows another interface called IFileSystemRepository to interact with files doesn’t matter if those files are in a database or in the file system itself.
  1. public interface IFileSystemRepository :  
  2.      ISelectRepository<Models.FileSystemObject>,  
  3.      IDeleteRepository<Models.FileSystemObject>  
  4.  {  
  5.      bool Exists(  
  6.          string fullName);  
  7.  }  
There is just one implementation of that repository interface that reads all stuff from the System.IO namespace.
 
In the future, if you want to create a logical file system using a database, you will only need to implement the IFileSystemRepository interface.
 
That is the Inversion of Control concept that we program using interfaces without knowing about their implementations.
 
The FileSystemService class is responsible for bringing all files and folders with a specific format. That is the goal of the IFileSystemFormatter interface.
 
A File System Manager From Scratch In .NET Core And VueJS
 
We have two IFileSystemFormatter implementations, one for a Default format and others using the Humanizer library to format all dates and sizes with a more readable text. 
  1. public interface IFileSystemFormatter  
  2. {  
  3.     object ToJson(  
  4.         IEnumerable<FileSystemObject> fileSystemObjects);  
  5. }  
That Formatter solution uses the Strategy Design Pattern.
 
One of my Blog’s contributors, Kleber Silva, had written an article about the Humanizer library.
 
ASP.NET CORE – Configuration
 
The .NET CORE dependency injection settings code, 
  1. services.AddSingleton<IFileSystemRepository, FileSystemRepository>();  
  2. services.AddSingleton<IFileSystemService, DefaultFileSystemService>();  
There are other settings in the Startup.cs file and, for performance, let’s remove NULL properties from JSON serialization.
  1. .AddMvc()  
  2. .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)  
  3. .AddJsonOptions(opt =>  
  4. {  
  5.        opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();  
  6.        opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;  
  7. });  
API .NET CORE – Controller
 
The file system manager controller,
  1. public sealed class FileSystemController :  
  2.    Controller  
  3. {  
  4.     private readonly IFileSystemService _fileSystemService;  
  5.    
  6.     public FileSystemController(  
  7.         IFileSystemService fileSystemService)  
  8.     {  
  9.         _fileSystemService = fileSystemService;  
  10.     }  
  11.    
  12.     public IActionResult Index(  
  13.         string fullName = null,  
  14.         string formatterName = null)  
  15.     {  
  16.         var data = _fileSystemService.GetAllFileSystemObject(  
  17.             fullName);  
  18.    
  19.         var json = _fileSystemService.ToJson(  
  20.             data,  
  21.             FormatterFactory.CreateInstance(formatterName));  
  22.    
  23.         return Json(  
  24.             json);  
  25.     }  
  26.    
  27. }  
The Index action receives two parameters, a folder path and a type of format, default or humanizer format.
 
API .NET CORE – Formatter classes, Services, and Repository
 
The DefaultFileSystemFormatter and HumanizerFileSystemFormatter, 
  1. public sealed class DefaultFileSystemFormatter :  
  2.      IFileSystemFormatter  
  3.  {  
  4.      public object ToJson(  
  5.          IEnumerable<FileSystemObject> objs)  
  6.      {  
  7.          var dateTimeFormat = "MM/dd/yyyy HH:mm";  
  8.    
  9.          return objs?.Select(obj => new  
  10.          {  
  11.              obj.Name,  
  12.              Id = obj.Id.Replace(@"\", @"\\"),  
  13.              obj.IsFile,  
  14.              obj.HasChilds,  
  15.              LastWriteTime = obj.LastWriteTime?.ToString(dateTimeFormat),  
  16.              CreationTime = obj.CreationTime?.ToString(dateTimeFormat),  
  17.              obj.Size,  
  18.              obj.Extension  
  19.          });  
  20.      }  
  21.  }  
  1. public sealed class HumanizerFileSystemFormatter :  
  2.     IFileSystemFormatter  
  3. {  
  4.     public object ToJson(  
  5.         IEnumerable<FileSystemObject> objs)  
  6.     {  
  7.         return objs?.Select(obj => new  
  8.         {  
  9.             obj.Name,  
  10.             Id = obj.Id.Replace(@"\", @"\\"),  
  11.             obj.IsFile,  
  12.             obj.HasChilds,  
  13.             LastWriteTime = obj.LastWriteTime.Humanize(),  
  14.             CreationTime = obj.CreationTime.Humanize(),  
  15.             Size = obj.Size.HasValue ?  
  16.                 obj.Size.Value.Bytes().Humanize() :  
  17.                 null,  
  18.             obj.Extension  
  19.         });  
  20.     }  
  21. }  
  1. public sealed class DefaultFileSystemService :  
  2.     IFileSystemService  
  3. {  
  4.     private readonly IFileSystemRepository _fileSystemRepository;  
  5.    
  6.     public DefaultFileSystemService(  
  7.         IFileSystemRepository fileSystemRepository)  
  8.     {  
  9.         _fileSystemRepository = fileSystemRepository;  
  10.     }  
  11.    
  12.     public IEnumerable<FileSystemObject> GetAllFileSystemObject(  
  13.         string fullName)  
  14.     {  
  15.         var objs = _fileSystemRepository.SelectMany(  
  16.             fullName)  
  17.             .ToList();  
  18.    
  19.         if (objs.Any())  
  20.             objs.OrderBy(obj => obj.IsFile.ToString());  
  21.    
  22.         return objs;  
  23.     }  
  24.    
  25.     public object ToJson(  
  26.         IEnumerable<FileSystemObject> objs,  
  27.         IFileSystemFormatter fileSystemFormatter = null)  
  28.     {  
  29.         fileSystemFormatter = fileSystemFormatter ?? FormatterFactory.CreateInstance();  
  30.    
  31.         return fileSystemFormatter.ToJson(  
  32.             objs);  
  33.     }  
  34.    
  35.     public bool DirectoryExists(  
  36.         string fullName)  
  37.     {  
  38.         var result = _fileSystemRepository.Exists(  
  39.             fullName);  
  40.    
  41.         return result;  
  42.     }  
  43. }  
The class mentioned above doesn’t know about System.IO; in other words, it doesn’t matter if the data came from the database, filesystem, or other data sources.
 
The more complex class is the FileSystemRepository that interacts with the system operational file system. You can download all source code from my Github.
  1. public IEnumerable<FileSystemObject> SelectMany(  
  2.         string id)  
  3.     {  
  4.         id = id ?? Environment.CurrentDirectory;  
  5.    
  6.         var objs = new List<FileSystemObject>();  
  7.    
  8.         if (Directory.Exists(id))  
  9.         {  
  10.             foreach (DirectoryInfo directoryInfo in new DirectoryInfo(id).GetDirectories().AsParallel())  
  11.             {  
  12.                 var obj = SelectOneDirectoryInfo(  
  13.                     directoryInfo);  
  14.    
  15.                 objs.AddIfNotNull(obj);  
  16.             }  
  17.    
  18.             foreach (FileInfo fileInfo in new DirectoryInfo(id).GetFiles().AsParallel())  
  19.             {  
  20.                 var obj = SelectOneFileInfo(  
  21.                     fileInfo);  
  22.    
  23.                 objs.AddIfNotNull(obj);  
  24.             }  
  25.         }  
  26.    
  27.         return objs;  
  28.     }  
  29.    
  30.     public FileSystemObject SelectOne(  
  31.         string id)  
  32.     {  
  33.         var obj = SelectOneFileInfo(  
  34.             new FileInfo(id));  
  35.    
  36.         if (obj == null)  
  37.         {  
  38.             obj = SelectOneDirectoryInfo(  
  39.                 new DirectoryInfo(id));  
  40.         }  
  41.    
  42.         return obj;  
  43.     }  
  44.    
  45.     public bool Delete(  
  46.         string id)  
  47.     {  
  48.         var obj = SelectOneFileInfo(  
  49.             new FileInfo(id));  
  50.    
  51.         if (obj == null)  
  52.         {  
  53.             obj = SelectOneDirectoryInfo(  
  54.                 new DirectoryInfo(id));  
  55.    
  56.             if (obj != null)  
  57.             {  
  58.                 Directory.Delete(id);  
  59.             }  
  60.         }  
  61.         else  
  62.         {  
  63.             File.Delete(id);  
  64.    
  65.             return true;  
  66.         }  
  67.    
  68.         return false;  
  69.     }  
  70.    
  71.     public bool Exists(  
  72.       string fullName)  
  73.     {  
  74.         if (!Directory.Exists(fullName))  
  75.             return false;  
  76.    
  77.         return true;  
  78.     }  
  79. }  
API .NET CORE – Running the API
 
To run the API just press F5, change the URL and set the formatter parameter like that:
 
http://localhost:59615/FileSystem?formatterName=Default
 
A File System Manager From Scratch In .NET Core And VueJS 
A File System Manager From Scratch In .NET Core And VueJS
 
My Chrome web browser is formating and indenting the JSON data. That is a Google Chrome Extension that you can download and install in your browser.
 
jQuery frontend
 
I had used jQuery only for testing the file system manager API and, to show the Tag Helpers features. 
  1. <div class="col-md-6"  
  2.         fsl-filesystem="dir1"  
  3.         fsl-filesystem-full-name="c:\dev"></div>  
  4.     
  5.     <div class="col-md-6"  
  6.         fsl-filesystem="dir2"  
  7.         fsl-filesystem-full-name="c:\dev"></div>  
Like you can see, there are two DIV with some fsl-filesystem attributes that each of one of them is pointing to a different folder.
 
To create a Tag Helper it is necessary to create a C# Class and put it to the TagHelpers folders in a .NET CORE project. Don’t forget to add this TagHelpers namespace in the _ViewImports.cshtml file.
  1. @using FSL.FileSystem.Core  
  2. @using FSL.FileSystem.Core.Models  
  3. @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers  
  4. @addTagHelper *, FSL.FileSystem.Core  
You can create how many properties you want. Each property must have an attribute like this,
  1. [HtmlTargetElement("div", Attributes = "fsl-filesystem")]  
  2.     public class FileSystemTagHelper :   
  3.         TagHelper  
  4.     {  
  5.         [HtmlAttributeName("fsl-filesystem")]  
  6.         public string Id { getset; }  
  7.    
  8.         [HtmlAttributeName("fsl-filesystem-full-name")]  
  9.         public string FullName { getset; }  
  10.    
  11.         [HtmlAttributeName("fsl-filesystem-full-height")]  
  12.         public int? Height { getset; }  
  13.    
  14.         public override void Process(  
  15.             TagHelperContext context,   
  16.             TagHelperOutput output)  
  17.         {  
  18.             var height = Height ?? 400;  
  19.             output.Attributes.Add("style", $"overflow-y:auto;overflow-x:hidden;height:{height}px");  
  20.    
  21.             var fullName = FullName ?? "";  
  22.             fullName = fullName.Replace(@"\", @"\\");  
  23.    
  24.             var sb = new StringBuilder();  
  25.             sb.Append($"<div id=\"{Id}0\"></div>");  
  26.             sb.Append("<script type=\"text/javascript\">");  
  27.             sb.Append("$(document).ready(function () {");  
  28.             sb.Append($"fileSystem.build('{fullName}', '{Id}', 0, 0);");  
  29.             sb.Append("});");  
  30.             sb.Append("</script>");  
  31.    
  32.             output.Content.SetHtmlContent(sb.ToString());  
  33.         }  
  34.     }  
In the previous code, I had created three properties: Height, for the vertical size of treeview; Id, for the component’s distinct ID to work in the DOM; and FullName, a source path to the file system.
 
It is in the Process method that we need to write code to render HTML code.
 
The Tag Helper will render scripts that will call a method called build from the javascript filesystem file.
  1. var fileSystem = function () {  
  2.    
  3.     var index = 0,  
  4.         build = function (dir, id, objIndex, tab) {  
  5.    
  6.             $.getJSON(  
  7.                 'filesystem?fullName=' + dir + '&formatterName=Humanizer',  
  8.                 (data, status) => {  
  9.                     // full code in my github  
  10.                 });  
  11.         }  
  12.    
  13.     return {  
  14.         build: build  
  15.     };  
  16.    
  17. }();  
To create the filesystem.js file, I had used the Revealing Module Pattern to organize all javascript code.
 
All the [treeview] is built by hand using jQuery with the data returned by the API.
 
Obviously, I had reinvented the wheel to write this javascript code because there is a lot of treeview components out there. But I don’t care. It was such fun playing with jQuery again.
 

VueJS Frontend

 
VueJS is a Single Page Application framework that uses the MVVM Design Pattern to interact with the javascript and DOM.
 
You can download the File System Manager VueJS source code from Lucas Juliano’s Github.
 
Thank you for reading it.
 
https://github.com/fabiosilvalima
https://github.com/julianodev