A File System Manager from Scratch in .NET CORE and VueJS

Also available in portuguese

Learn how to manipulate files and folders in a custom file system in C# with .NET CORE and a VueJS frontend.

Create a File System Manager from Scratch in .NET CORE and VueJS

Imagine a file system manager like Windows Explorer that shows files and folder from a computer in a treeview frontend.

This article is a partnership with Lucas Juliano, friend, workmate, and my Blog’s revisor.

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?

1 – 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.

2 – 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.

3 – jQuery frontend
Create a simple jQuery Frontend to interact with the DOM and consume the file system manager API.

4 – VueJS frontend
Create a new frontend in VueJS, consuming the same API above. Lucas Juliano developed that frontend.

5 – Design Patterns
For the API, we are going to use some Design Patterns like Repository, Dependency Injection, Strategy, and Factory.

I had written an eBook about Design Patterns, but I haven’t translated to 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 1

VueJS frontend:

A File System Manager from Scratch in .NET CORE and VueJS 2

The file system manager is not a big deal, so let’s check in more detail how we did it.


#1.1 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 3

The FileSystemController knows about an IFileSystemService interface that contains all methods to return files and folders.

    public interface IFileSystemService
    {
        IEnumerable<FileSystemObject> GetAllFileSystemObject(
            string fullName);
 
        object ToJson(
            IEnumerable<FileSystemObject> objs,
            IFileSystemFormatter fileSystemFormatter = null);
 
        bool DirectoryExists(
            string fullName);
    }

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.

   public interface IFileSystemRepository :
        ISelectRepository<Models.FileSystemObject>,
        IDeleteRepository<Models.FileSystemObject>
    {
        bool Exists(
            string fullName);
    }

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 4

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.

    public interface IFileSystemFormatter
    {
        object ToJson(
            IEnumerable<FileSystemObject> fileSystemObjects);
    }

That Formatter solution uses the Strategy Design Pattern.

Onde of my Blog’s contributors, Kleber Silva, had written an article about the Humanizer library. Check this out clicking here.


#1.2 ASP.NET CORE – Configuration

The .NET CORE dependency injection settings code:

services.AddSingleton<IFileSystemRepository, FileSystemRepository>();
services.AddSingleton<IFileSystemService, DefaultFileSystemService>();

There are other settings in the Startup.cs file and, for performance, let’s remove NULL properties from JSON serialization.

.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
       opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
       opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});


#1.3 API .NET CORE – Controller

The file system manager controller:

    public sealed class FileSystemController :
       Controller
    {
        private readonly IFileSystemService _fileSystemService;
 
        public FileSystemController(
            IFileSystemService fileSystemService)
        {
            _fileSystemService = fileSystemService;
        }
 
        public IActionResult Index(
            string fullName = null,
            string formatterName = null)
        {
            var data = _fileSystemService.GetAllFileSystemObject(
                fullName);
 
            var json = _fileSystemService.ToJson(
                data,
                FormatterFactory.CreateInstance(formatterName));
 
            return Json(
                json);
        }
 
    }

The Index action receives two parameters, a folder path and a type of format, default ou humanizer format.


#1. API .NET CORE – Formatter classes, Services, and Repository

The DefaultFileSystemFormatter and HumanizerFileSystemFormatter:

   public sealed class DefaultFileSystemFormatter :
        IFileSystemFormatter
    {
        public object ToJson(
            IEnumerable<FileSystemObject> objs)
        {
            var dateTimeFormat = "MM/dd/yyyy HH:mm";
 
            return objs?.Select(obj => new
            {
                obj.Name,
                Id = obj.Id.Replace(@"\", @"\\"),
                obj.IsFile,
                obj.HasChilds,
                LastWriteTime = obj.LastWriteTime?.ToString(dateTimeFormat),
                CreationTime = obj.CreationTime?.ToString(dateTimeFormat),
                obj.Size,
                obj.Extension
            });
        }
    }
    public sealed class HumanizerFileSystemFormatter :
        IFileSystemFormatter
    {
        public object ToJson(
            IEnumerable<FileSystemObject> objs)
        {
            return objs?.Select(obj => new
            {
                obj.Name,
                Id = obj.Id.Replace(@"\", @"\\"),
                obj.IsFile,
                obj.HasChilds,
                LastWriteTime = obj.LastWriteTime.Humanize(),
                CreationTime = obj.CreationTime.Humanize(),
                Size = obj.Size.HasValue ?
                    obj.Size.Value.Bytes().Humanize() :
                    null,
                obj.Extension
            });
        }
    }

The DefaultFileSystemService implementation:

    public sealed class DefaultFileSystemService :
        IFileSystemService
    {
        private readonly IFileSystemRepository _fileSystemRepository;
 
        public DefaultFileSystemService(
            IFileSystemRepository fileSystemRepository)
        {
            _fileSystemRepository = fileSystemRepository;
        }
 
        public IEnumerable<FileSystemObject> GetAllFileSystemObject(
            string fullName)
        {
            var objs = _fileSystemRepository.SelectMany(
                fullName)
                .ToList();
 
            if (objs.Any())
                objs.OrderBy(obj => obj.IsFile.ToString());
 
            return objs;
        }
 
        public object ToJson(
            IEnumerable<FileSystemObject> objs,
            IFileSystemFormatter fileSystemFormatter = null)
        {
            fileSystemFormatter = fileSystemFormatter ?? FormatterFactory.CreateInstance();
 
            return fileSystemFormatter.ToJson(
                objs);
        }
 
        public bool DirectoryExists(
            string fullName)
        {
            var result = _fileSystemRepository.Exists(
                fullName);
 
            return result;
        }
    }

The class mentioned above doesn’t know about System.IO; in other meanings, 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.

    public IEnumerable<FileSystemObject> SelectMany(
            string id)
        {
            id = id ?? Environment.CurrentDirectory;
 
            var objs = new List<FileSystemObject>();
 
            if (Directory.Exists(id))
            {
                foreach (DirectoryInfo directoryInfo in new DirectoryInfo(id).GetDirectories().AsParallel())
                {
                    var obj = SelectOneDirectoryInfo(
                        directoryInfo);
 
                    objs.AddIfNotNull(obj);
                }
 
                foreach (FileInfo fileInfo in new DirectoryInfo(id).GetFiles().AsParallel())
                {
                    var obj = SelectOneFileInfo(
                        fileInfo);
 
                    objs.AddIfNotNull(obj);
                }
            }
 
            return objs;
        }
 
        public FileSystemObject SelectOne(
            string id)
        {
            var obj = SelectOneFileInfo(
                new FileInfo(id));
 
            if (obj == null)
            {
                obj = SelectOneDirectoryInfo(
                    new DirectoryInfo(id));
            }
 
            return obj;
        }
 
        public bool Delete(
            string id)
        {
            var obj = SelectOneFileInfo(
                new FileInfo(id));
 
            if (obj == null)
            {
                obj = SelectOneDirectoryInfo(
                    new DirectoryInfo(id));
 
                if (obj != null)
                {
                    Directory.Delete(id);
                }
            }
            else
            {
                File.Delete(id);
 
                return true;
            }
 
            return false;
        }
 
        public bool Exists(
          string fullName)
        {
            if (!Directory.Exists(fullName))
                return false;
 
            return true;
        }
    }


#1.5 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 5
A File System Manager from Scratch in .NET CORE and VueJS 6

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.


#2.1 – jQuery frontend

I had used jQuery only for testing the file system manager API and, to show the Tag Helpers features.

<div class="col-md-6"
        fsl-filesystem="dir1"
        fsl-filesystem-full-name="c:\dev"></div>
 
    <div class="col-md-6"
        fsl-filesystem="dir2"
        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 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.

@using FSL.FileSystem.Core
@using FSL.FileSystem.Core.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, FSL.FileSystem.Core

You can create how many properties you want. Each property must have an attribute like this:

[HtmlTargetElement("div", Attributes = "fsl-filesystem")]
    public class FileSystemTagHelper : 
        TagHelper
    {
        [HtmlAttributeName("fsl-filesystem")]
        public string Id { get; set; }
 
        [HtmlAttributeName("fsl-filesystem-full-name")]
        public string FullName { get; set; }
 
        [HtmlAttributeName("fsl-filesystem-full-height")]
        public int? Height { get; set; }
 
        public override void Process(
            TagHelperContext context, 
            TagHelperOutput output)
        {
            var height = Height ?? 400;
            output.Attributes.Add("style", $"overflow-y:auto;overflow-x:hidden;height:{height}px");
 
            var fullName = FullName ?? "";
            fullName = fullName.Replace(@"\", @"\\");
 
            var sb = new StringBuilder();
            sb.Append($"<div id=\"{Id}0\"></div>");
            sb.Append("<script type=\"text/javascript\">");
            sb.Append("$(document).ready(function () {");
            sb.Append($"fileSystem.build('{fullName}', '{Id}', 0, 0);");
            sb.Append("});");
            sb.Append("</script>");
 
            output.Content.SetHtmlContent(sb.ToString());
        }
    }

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.

var fileSystem = function () {
 
    var index = 0,
        build = function (dir, id, objIndex, tab) {
 
            $.getJSON(
                'filesystem?fullName=' + dir + '&formatterName=Humanizer',
                (data, status) => {
                    // full code in my github
                });
        }
 
    return {
        build: build
    };
 
}();

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.

Obviouslly, I had reinvented the wheel 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.


#3 – 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.

đŸ™‚

About the Author:
He works as a solution architect and developer, has more than 18 years of experience in software development on several platforms and more than 16 years only for the insurance market.