2 09 2015
Armazenando bibliotecas em subdiretórios no C#
Você já deve ter ouvido aquele ensinamento de que não devemos reinventar a roda, certo? O que isso quer dizer no mundo do desenvolvimento de software é que, ao invés de ficarmos escrevendo bibliotecas novas para resolver problemas que já foram resolvidos, devemos, na medida do possível, utilizar bibliotecas já prontas que dão conta do recado. Por exemplo, você quer tirar fotos com a webcam no seu aplicativo? Utilize uma biblioteca pronta que implementa essa funcionalidade. O mesmo vale para leitura de RSS, manipulação de arquivos zip, mapeamento de dados entre objetos, manipulação de PDFs, etc.
Mas, quando utilizamos uma biblioteca no nosso aplicativo, temos que distribuí-la juntamente com a aplicação. É comum nesse caso colocarmos as dlls das bibliotecas externas no mesmo diretório da aplicação, dessa forma, o .NET consegue carregá-las normalmente. Porém, dependendo da quantidade de bibliotecas externas que utilizamos, a pasta de instalação do nosso aplicativo pode acabar ficando uma verdadeira bagunça. O que podemos fazer para melhorar um pouco essa situação? Um dos inscritos da minha newsletter perguntou se seria possível armazenar as bibliotecas em subdiretórios e, baseado nessa questão, resolvi escrever esse artigo mostrando as minhas conclusões.
Depois de pesquisar sobre esse tema, descobri que temos duas opções para armazenarmos e carregarmos bibliotecas em subdiretórios ao desenvolvermos aplicativos com o .NET Framework. A primeira delas é adicionarmos uma tag nova no app.config. Já a segunda opção é utilizarmos o evento AssemblyResolve do AppDomain. Vamos conferir como conseguimos utilizar essas duas opções.
Configurando o Assembly Binding no app.config
Para suportarmos o carregamento de bibliotecas em um subdiretório utilizando o app.config, temos que adicionar uma nova tag com a informação de assembly binding. Por padrão, ao criarmos um projeto com o Visual Studio, ele já adiciona um arquivo app.config. Portanto, abra esse arquivo e, dentro da tag “configuration“, adicione a seguinte informação:
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="lib" /> </assemblyBinding> </runtime>
Veja como fica o antes:
E o depois:
Como você pode perceber, dentro da tag “probing“, atributo “privatePath“, conseguimos definir um subdiretório (nesse exemplo “lib“) que o .NET Framework “olhará” para fazer o carregamento de bibliotecas externas. Dessa forma, quando executamos a nossa aplicação, primeiramente o .NET Framework tentará encontrar a biblioteca externa no GAC, depois na pasta do aplicativo e depois no subdiretório “lib“. Fácil, não? Só não esqueça de distribuir o arquivo de configuração junto com a aplicação!
Utilizando o evento AssemblyResolve
A segunda opção para fazermos o carregamento de bibliotecas externas a partir de subdiretórios é a utilização do evento AssemblyResolve. Esse evento é disparado toda vez que o .NET Framework não consegue encontrar uma biblioteca externa necessária ao nosso aplicativo. No handler desse evento, temos a possibilidade de indicar um novo caminho de onde o assembly deve ser carregado.
Para utilizarmos essa alternativa, temos que fazer o tratamento desse evento em algum lugar global (de preferência no método Main da classe Program ou em algum outro lugar global onde você esteja fazendo a inicialização do seu aplicativo):
class Program { static void Main(string[] args) { AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSubFolder); // Aqui você continua com o resto de código do método Main. } static System.Reflection.Assembly LoadFromSubFolder(object sender, ResolveEventArgs args) { string folderPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); string assemblyPath = System.IO.Path.Combine(folderPath, "lib", new System.Reflection.AssemblyName(args.Name).Name + ".dll"); if (!System.IO.File.Exists(assemblyPath)) return null; System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(assemblyPath); return assembly; } }
Note que a utilização desse evento é muito simples. Quando uma biblioteca não é encontrada pelo .NET Framework, tentamos encontrar uma biblioteca com o mesmo nome só que no subdiretório “lib“. Caso ela seja encontrada, nós carregamos uma instância de Assembly utilizando essa biblioteca e retornamos. Caso contrário, retornamos null.
Essa opção, apesar de parecer mais complexa à primeira vista, é muito mais poderosa e flexível. Você pode fazer o malabarismo que quiser e carregar bibliotecas de onde quer que você precise, ao contrário da primeira opção, onde você só pode informar um subdiretório a ser considerado.
Concluindo
A utilização de bibliotecas é algo que todo programador terá que enfrentar durante o desenvolvimento de aplicativos. Nesse artigo você conferiu como organizar as bibliotecas externas em um subdiretório no desenvolvimento de aplicações com o .NET Framework. Não perca mais tempo e vá agora mesmo organizar a pasta do seu aplicativo!
Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se aqui ou utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Calculando a distância entre dois pontos utilizando Google Maps e C# Qual a diferença entre EnumerateFiles e GetFiles?