Em geral, um projeto Web serve páginas HTML e que com o auxílio de alguma tecnologia como PHP ou ASP.NET, tornam estas páginas dinâmicas. Além de HTML, projetos Web também possuem outros tipos de arquivos que complementam estas aplicações, tais como CSS, Javascripts, Imagens, etc.
Além disso, dependendo do tipo de projeto que estamos trabalhando, há outros tipos de arquivos que a aplicação aceita que os usuários enviem ou arquivos que a própria aplicação gera e disponibiliza a seus usuários. Esses arquivos são, na maioria das vezes, armazenados no disco, em um diretório específico para não misturar com os arquivos que fazem parte da aplicação em si, e não são referentes ao conteúdo que ela manipula.
Apesar do armazenamento em disco ser o mais comum, é possível ver empresas que optam por armazenar o conteúdo do arquivo no banco de dados, onde o backup é um de seus principais benefícios, pois tudo o que a aplicação precisa para trabalhar está ali. Só que quando optamos por inserir e manter os arquivos no banco de dados, a forma de acesso muda bastante. Em tempos de ASP.NET Web Forms, uma opção seria criar um handler (ASHX) para recuperar o arquivo solicitado pelo cliente, que informava na querystring o arquivo que ele estava querendo acessar; este handler, por sua vez, realizava a busca na base e escrevia no stream de saída os bytes do arquivo.
Dependendo da tecnologia que estamos utilizando, podemos adotar diferentes técnicas de acesso à este tipo de conteúdo. Se estivermos utilizando o OWIN, é possível criar algo mais simples e “flat” para expor os arquivos que estão salvos em uma base de dados. Como sabemos, o OWIN é um servidor Web bastante leve e estensível, e uma das opções que temos disponíveis, é justamente apontar de onde e como extrair os recursos (arquivos) que estão sendo solicitados ao servidor.
A ideia é criar um mecanismo que busque os arquivos solicitados em uma base de dados, utilizando o OWIN para criar um servidor Web customizado, que hospedaremos em uma aplicação de linha de comando (Console). O primeiro passo depois do projeto criado, é instalar os dois pacotes abaixo (através do Nuget):
Install-Package Microsoft.Owin.SelfHost
Install-Package Microsoft.Owin.StaticFiles
Antes de começarmos a programar, precisamos definir a estrutura da base de dados que irá acomodar os arquivos. Para manter a simplicidade, vamos nos conter em apenas uma tabela chamada File com três colunas: Name, Date e Content. A imagem abaixo ilustra os arquivos que adicionei manualmente para o exemplo. Quero chamar a atenção para três arquivos: Pagina.htm, Styles.css e CD.jpg. Como vocês já devem estar percebendo, a página HTML é que menciona o CSS para formatar a mesma e a imagem é colocada em um atributo <img /> nesta mesma página.
O próximo passo é customizar o nosso sistema de arquivos, e para isso, o OWIN fornece duas interfaces: IFileSystem e IFileInfo. A primeira define a estrutura que nosso “repositório” de arquivos deverá ter, enquanto a segunda determina quais os atributos (propriedades) nossos arquivos devem ter. A implementação da classe que representará o arquivo é simples, pois os dados já estão armazenados em nossa base, e utilizaremos o contrutor para que as informações sejam repassadas para a classe:
public class DatabaseFileInfo : IFileInfo
{
private readonly byte[] content;
internal DatabaseFileInfo(string name, DateTime date, byte[] content)
{
this.content = content;
this.Name = name;
this.LastModified = date;
this.Length = this.content.Length;
}
public Stream CreateReadStream()
{
return new MemoryStream(this.content);
}
public long Length { get; private set; }
public string Name { get; private set; }
//outras propriedades
}
O próximo passo é implementar o sistema de arquivos, utilizando a interface IFileSystem. Aqui devemos procurar pelo arquivo solicitado, e se encontrado, retornar o mesmo através do parâmetro de saída fileInfo, que deve ser instanciado com a classe que criamos acima. É importante notar que passamos a string de conexão com a base de dados, e no interior do método TryGetFileInfo fazemos a busca e utilizamos o DataReader para extrair as informações e instanciar a classe DatabaseFileInfo passando as informações inerentes ao arquivo solicitado.
public class DatabaseFileSystem : IFileSystem
{
private readonly string connectionString;
public DatabaseFileSystem(string connectionString)
{
this.connectionString = connectionString;
}
public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
{
fileInfo = null;
using (var conn = new SqlConnection(this.connectionString))
{
using (var cmd = new SqlCommand(“SELECT Name, Date, Content FROM [File] WHERE Name = @Name”, conn))
{
cmd.Parameters.AddWithValue(“@Name”, subpath.Replace(“/”, “”));
conn.Open();
using (var dr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
if (dr.Read())
fileInfo =
new DatabaseFileInfo(dr.GetString(0), dr.GetDateTime(1), (byte[])dr.GetValue(2));
}
}
return fileInfo != null;
}
}
As classes que criamos até agora não são suficientes para tudo funcione, pois precisamos acoplar à execução, e utilizaremos o modelo de configuração do OWIN para incluir este – novo – sistema de arquivos. Ao instalar os pacotes que foram mencionados acima, há alguns métodos de estensão que nos dão a chance de realizar estas configurações, e para ser mais preciso, me refiro ao método UseStaticFiles, que através da classe StaticFileOptions, é possível apontar a classe DatabaseFileSystem que acabamos de criar, para que o OWIN recorra à ela sempre que um arquivo for solicitado.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseStaticFiles(new StaticFileOptions()
{
FileSystem = new DatabaseFileSystem(“Data Source=.;Initial Catalog=DB;Integrated Security=True”)
});
}
}
Quando um arquivo não for encontrado, automaticamente o cliente receberá o erro 404 (NotFound). Já quando o arquivo for encontrado, o mesmo será renderizado diretamente no navegador, assim como podemos visualizar nos exemplos abaixo: