Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS

Disponível também em inglês

Saiba como manipular arquivos e pastas criando um gerenciador de arquivos em C# com .NET Core e Frontend em VueJS.

Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS

Imagine um gerenciador de arquivos como algo parecido com o Windows Explorer, que exibe pastas e arquivos do computador em forma de treeview.

Esse artigo é uma parceria realizada com Lucas Juliano, que além de trabalhar comigo, me ajuda nas revisões dos textos publicados neste Blog.

Só o fato de criar métodos e funções para interagir com FileSystem não tem graça, então vamos usar alguns Design Patterns para deixar o código C# mais organizado.

Antes de criarmos o FrontEnd para o gerenciador de arquivos em VueJS, vamos montar toda a API com as regras de negócio e usar jQuery para exibir os arquivos e pastas do computador.

E por fim, o gerenciador de arquivos ganhará um Frontend em VueJs, muito mais amigável, bonito e performático.

Algumas informações importantes para quem está chegando agora. Se você é um desenvolvedor .NET mas nunca viu nada de .NET Core, sugiro você ler antes o artigo .NET Core para DesenvolvedoreS .NET.

Dica: Não é necessário para esse artigo, mas se você não sabe como configurar o .NET Core no IIS, veja IIS: Como Hospedar Aplicação .NET Core em 10 Passos.

Se você preferir usar MAC ou Linux, também vai conseguir acompanhar, já que toda a solução é em .NET Core.


# Gerenciador de Arquivos – O que faremos?

1 – API .NET Core C#
Criaremos uma API em .NET Core na linguagem de programação C# com objetivo de interagir com regras de negócio e retornar informações de arquivos/pastas do filesystem.

2 – Tag Helpers MVC .NET Core
Usaremos o conceito de Tag Helper do .NET Core para criar um componente de gerenciador de arquivos para que possamos incluir mais de um na tela.

3 – Frontend em jQuery
Criaremos um Frontend simples com jQuery para interagir com o DOM e também consumir a API do gerenciador de arquivos.

4 – Frontend em VueJS
Faremos um novo FrontEnd usando VueJS consumindo a mesma API do gerenciador de arquivos. O desenvolvimento dessa parte será feito pelo Lucas Juliano.

5 – Design Patterns: Repository | Dependency Injection | Strategy | Factory
Para a API e regras de negócio usaremos alguns Design Patterns para organização do código C#.

Esses Design Patterns você encontrará no meu eBook Design Patterns Vol. 1 Programação no Mundo Real.


# Preview dos projetos em jQuery e VueJS

Um dos meus alunos me deu uma dica bem interessante em um dos treinamentos que ministrei.

O questionamento dele era que eu iniciava passo a passo a construção do código backend e frontend, e somente no final do curso mostrava o projeto pronto.

Ele achava melhor mostrar o bolo pronto, depois explicar como cheguei nele usando todos os ingredientes da receita do bolo.

Então Mario, em sua homenagem, tá aí o projeto final pronto:

Frontend Tag Helpers + jQuery:

Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS

Frontend VueJs:

Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS

Agora vamos pegar todas as tecnologias e funcionalidades já mencionadas e ver como chegaremos no projeto final.

Para o gerenciador de arquivos, eu dividi a receita de bolo em três etapas distintas: backend, onde criaremos toda a API em .NET Core; frontend simples usando jquery; e por fim frontend usando VueJS.

Para não cairmos em um dos 50 Erros Comprovados Cometidos por um Programador, vamos ver se estamos aptos para iniciar esse desenvolvimento.

Requisitos mínimos:
– .NET Core 2.2.
– Visual Studio 2017 ou Visual Studio Code.

Tudo pronto? Então vamos lá!


# 1.1 API .NET Core – Arquitetura

Para facilitar o entendimento da arquitetura e dos padrões usados nessa solução backend, fiz alguns diagramas de interfaces e classes, separando as responsabilidades.

Diagrama das interface e classes:

O Controller da API FileSystemController conhece uma interface IFileSystemService, que contém os métodos para retornar pastas e arquivos. Para essa interface há apenas uma classe, que é FileSystemService.

public interface IFileSystemService
    {
        IEnumerable<FileSystemObject> GetAllFileSystemObject(
            string fullName);

        object ToJson(
            IEnumerable<FileSystemObject> objs,
            IFileSystemFormatter fileSystemFormatter = null);

        bool DirectoryExists(
            string fullName);
    }

A classe FileSystemService conhece uma interface IFileSystemRepository, que contém os métodos para interagir com arquivos e pastas não importa se fisicamente ou logicamente (banco de dados por exemplo).

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

Há apenas uma implementação dessa interface que é o FileSystemRepository, que acessa a as funções de System.IO.

Se você quiser criar um gerenciador de arquivos lógico usando banco de dados SQL, só precisaria criar uma classe, por exemplo SqlFileSystemRepository e implementar a mesma interface IFileSystemRepository.

Percebe que o modelo de treeview usado no gerenciador de arquivos, também poderia ser usado para uma solução de menu com múltiplos submenus.

Com esse conceito de Inversion of Control e Dependency Injection, conseguimos organizar o código C# de forma mais clara com suas respectivas dependências. Além desses Design Patterns, usaremos também o de Repository, para acesso a dados.

A classe FileSystemService será responsável por trazer a lista de arquivos e pastas formatados. Para isso temos a interface IFileSystemFormatter.

Teremos duas implementações para IFileSystemFormatter, uma com formatação Default e outra usando a lib Humanizer, para trazer por exemplo a data de modificação de um arquivo/pasta em formato mais legível, exemplo: “modificado a X minutos”.

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

O Design Pattern usado nessa solução de formatação é o Strategy.

A escolha dessa formatação será uma das entradas da nossa API, onde será possível alternar entre formatação Default ou Humanizer.

Um dos contribuidores do meu blog, Kleber Silva, escreveu um artigo sobre o Humanizer, depois sugiro que você dê uma olhada, é muito legal.


# 1.2 – API .NET Core – Configuração

A configuração de Dependency Injection no .NET Core ficou assim:

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

Existem outras configurações no arquivo Startup.cs da API, e para performance iremos retirar campos que estiverem NULL, e como boas práticas, usaremos padrão de capitalização camelCase.

.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

O Controller da API do gerenciador de arquivos ficou assim:

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

    }

Uma Action Index que recebe dois parâmetros: um, o caminho de uma pasta no FileSystem; outro, o tipo de formatação Default ou Humanizer.


# 1.4 – API .NET Core – Classes Formatter, Service e Repository

As duas classes que implementam IFileSystemFormatter ficaram assim:

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

A classe FileSystemService, responsável por interagir com o repositório ficou assim:

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

Essa classe FileSystemService não conhece o System.IO e para ela isso é indiferente. Ou seja, para ela não importa a origem dos dados.

Por fim, a classe mais complexa de todas, a FileSystemRepository que faz toda a interação com o System.IO. Vou postar aqui somente o método principal. Você pode baixar o fonte direto do meu 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 – Rodando a API

Para rodar a aplicação, basta acessar o endereço da API passando os parâmetros necessários, veja abaixo o resultado usando formatação Default e Humanizer.

http://localhost:59615/FileSystem?formatterName=Default

A porta 59615 é apenas exemplo, talvez no seu computador o Visual Studio abra a aplicação web em outra porta.

Na imagem você está vendo o JSON formatado no navegador Google Chrome. Isso é uma Google Chrome Extension que pode ser baixada gratuitamente, veja mais detalhes aqui.


# 2.1 – Frontend jQuery

Essa parte de frontend usando jquery foi realizada para simular o acesso a API e demonstrar as funcionalidades dos Tag Helpers do .NET Core.

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

Como pode ver no HTML acima, existem dois DIV com atributos que iniciam com fsl-filesystem. Cada um deles está apontando para uma determinada pasta local no computador.

Para um Tag Helper funcionar é necessário criar uma classe C# na pasta TagHelpers do projeto .NET Core e configurar o namespace do projeto no arquivo _ViewImports.cshtml.

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

Na classe Tag Helper, crie quantas propriedades quiser e para cada uma delas configure seus atributos. Configure os atributos também para a classe Tag Helper.

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

No código acima, eu criei três propriedades: Height, para o tamanho do treeview do gerenciador de arquivos; Id, para o código do componente ser distinto, caso existam mais de um na mesma página; e FullName, que é o caminho inicial para a API trazer as pastas e arquivos.

Você pode deixar esse atributo full-name em branco, que automaticamente a API trará a pasta local da aplicação web.

É no método Process que a gente cria códigos scripts e HTML que serão renderizados pelo Tag Helper.

O Tag Helper renderizará scripts que chamarão o método build da classe javascript fileSystem.

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

}();

Utilizei o design pattern Revealing Module Pattern para construir o filesystem.js, para saber mais sobre ele clique aqui.

Todo o treeView do gerenciador de arquivos é montado a partir do jQuery, isso mesmo montado na raça em “modo Espartano” (expressão de Angelo Belchior).

Basicamente o método build aciona a API para trazer as pastas/arquivos na primeira vez que a tela é aberta e depois quando alguma opção do treeview for clicada.

Com os dados retornados pela API, ele monta todos os divs/events/html das pastas e arquivos.

Obviamente eu reinventei a roda, pois existem diversos componentes de treeview no mercado, tanto em jQuery como em outras bibliotecas.

Mas, me diverti fazendo isso e não era ponto focal do artigo.


# 3 – Frontend em VueJS

Chegamos ao ponto de criarmos uma solução de frontend melhor para o treeview do gerenciador de arquivos e para isso usaremos o VueJS, framework Single Page Application mais badalado do momento.

A solução do Lucas é TOP demais e te convido a ver as explicações dele, consumindo a API filesystem a partir do VueJS clicando na imagem abaixo.

Achou algum erro? Curtiu? Não curtiu?

Por favor comente e compartilhe!

Obrigado e até a próxima.

Artigos sobre ASP.NET CORE:

Crie seu Framework em ASP.NET CORE 3 e Blazor
.NET Core para Desenvolvedores .NET
IIS: Como Hospedar Aplicação .NET Core em 10 Passos
JWT: Customizando o Identity no ASP.NET CORE 3.0
AppSettings: 6 Formas de Ler o Config no ASP.NET CORE 3.0
Benchmark: ASP.NET 4.8 vs ASP.NET CORE 3.0


Faça download completo do código fonte no github.
Sobre o Autor:
Trabalha como arquiteto de soluções e desenvolvedor, tem mais de 18 anos de experiência em desenvolvimento de software em diversas plataformas sendo mais de 16 anos somente para o mercado de seguros.
Revisado por:
Paulistano, 21 anos apaixonado por tecnologia, trabalho com desenvolvimento de software atuando com .NET / Xamarin / AngularJS / ASP.NET CORE / MVC.


Estudante de Desenvolvimento de Jogos Digitais na PUC-SP, Estagiário na Broker, Consultoria e Soluções em TI. Amo trabalhar com informática, tanto no desenvolvimento de Software assim como hardware e montagem de computadores, Modelador Profissional e Game Designer