Um recurso incorporado é um arquivo que o compilador grava dentro do próprio assembly, de modo que ele viaja dentro da DLL ou do EXE, em vez de ser distribuído como um arquivo avulso ao lado. Um arquivo de texto, uma imagem ou um PDF entra sem alterações e volta a sair por meio de System.Reflection, e não por um caminho de arquivo.
Com isso, o assembly e os recursos dele passam a ser um arquivo só, em vez de vários, e um recurso ausente vira um erro de compilação, e não um chamado de suporte. Os recursos também aparecem em outras formas: tratamos da parte de localização em Localização no ASP.NET Core e da parte de strings em Como ler uma string de um arquivo .resx (de recursos) em C#.
Mãos à obra.
O que são recursos incorporados?
Um recurso incorporado é um arquivo que o compilador copia para dentro do assembly em tempo de compilação e lista no manifesto dele. A DLL ou o EXE carrega os bytes, então não há um arquivo separado para implantar, nenhum caminho relativo para errar e nenhuma chance de o arquivo sumir entre a compilação e a execução.
O arquivo em si não muda. Texto continua sendo texto, e um PDF continua sendo um PDF. O que muda é a forma de acessá-lo: por meio de System.Reflection, e não de System.IO.
Duas coisas colocam um arquivo lá, e ambas gravam o mesmo item no arquivo de projeto. No Visual Studio, definimos a Build Action do arquivo como Embedded resource. No .csproj, nós mesmos adicionamos uma entrada <EmbeddedResource Include="..." />.
Duas chamadas em Assembly o tiram de lá. GetManifestResourceNames() retorna todos os nomes do manifesto, e GetManifestResourceStream(name) abre um deles como Stream.
Esse nome não é o caminho do arquivo, e esse único fato é o assunto da maior parte deste artigo.
Como adicionar um recurso incorporado a um projeto?
Dois mecanismos adicionam um arquivo ao assembly, e os dois acabam gravando a mesma linha no arquivo de projeto.
No Visual Studio, selecionamos o arquivo no Solution Explorer, abrimos Properties e definimos Build Action como Embedded resource. O Visual Studio grava a entrada por nós.
No arquivo de projeto, escrevemos a entrada à mão, como <EmbeddedResource Include="Resources\text-file.txt" />. Editar o arquivo diretamente oferece duas coisas que a lista suspensa não consegue expressar. Include aceita curingas do MSBuild (globs), então Resources\**\*.txt incorpora uma subárvore inteira e continua incorporando os arquivos adicionados a ela depois. E um caminho pode sair da pasta do projeto, algo que a janela Properties não tem como indicar.
Uma categoria não precisa de entrada nenhuma. O SDK do .NET já incorpora todos os arquivos .resx do projeto, e é por isso que um arquivo de designer de recursos funciona sem que ninguém adicione nada. Definir EnableDefaultEmbeddedResourceItems como false desativa esse comportamento.
Vamos começar preparando um programa de teste rápido, uma aplicação de linha de comando básica. Podemos criar uma nova aplicação de linha de comando direto no Visual Studio ou usar o comando dotnet:
dotnet new console -n Embedded_Resources_in_NET
Com o projeto de linha de comando criado, já podemos adicionar recursos incorporados à nossa aplicação.
Vamos criar arquivos de exemplo de texto (.txt) e de PDF (.pdf) para os testes. Podemos pegar arquivos do nosso disco rígido ou extraí-los da aplicação de exemplo do Code Maze.
Criação das pastas e dos arquivos de exemplo
Vamos preparar as pastas e os arquivos dos recursos. Primeiro, criamos duas subpastas no diretório do projeto: Resources e Files. Os nomes específicos não são cruciais, mas Resources é um nome muito usado.
Podemos fazer isso assim:
md Resources cd Resources md Pdf cd .. md Files
Assim, criamos uma estrutura de pastas em que as pastas Resources e Files ficam no mesmo nível, e a pasta Pdf fica dentro de Resources.
Em seguida, copie um arquivo de texto do disco para cada uma das pastas Resources e Files, com o nome text-file.txt. Da mesma forma, vamos duplicar um arquivo PDF de exemplo, com o nome pdf-file.pdf, e colocá-lo na subpasta Pdf dentro de Resources.
Depois dessas etapas, a estrutura de pastas vai ficar assim:
|
|-- Resources
|-- text-file.txt
|-- Pdf
|-- pdf-file.pdf
|-- Files
|-- text-file.txt
O conteúdo dos arquivos não importa para o nosso objetivo.
Adição de recursos incorporados pelo Visual Studio
Agora que incluímos todas as pastas e arquivos no projeto .NET/C#, eles devem aparecer no Solution Explorer.
Para marcar cada um desses arquivos para incorporação, siga estes passos:
- Clique com o botão direito em cada arquivo.
- No menu de contexto, escolha ‘Properties’.
- Na janela Properties, selecione ‘Build Action’.
- Escolha ‘Embedded resource’ no menu suspenso.

Como listar os recursos incorporados em um assembly?
Agora que incorporamos três recursos na aplicação, vamos escrever um pouco de código C# para obter a lista desses arquivos.
Primeiro, precisamos de uma referência ao assembly que contém os recursos incorporados. Daqui em diante, todos os membros ficam dentro de uma classe SampleResourceReader, em um arquivo só dela, que começa com using System.Diagnostics; e using System.Reflection;. Como adicionamos todos os recursos ao assembly principal, podemos referenciá-lo facilmente:
private static Assembly ThisAssembly
=> typeof(SampleResourceReader).Assembly;
Com a referência ao assembly em mãos, podemos listar todos os recursos que ele contém usando o método GetManifestResourceNames():
private static void ListResourcesInAssembly(Assembly? assembly)
{
if (assembly is null)
return;
var resources = assembly.GetManifestResourceNames();
if (resources.Length == 0)
return;
Console.WriteLine($"Resources in {assembly.FullName}");
foreach (var resource in resources)
{
Console.WriteLine(resource);
}
Console.WriteLine();
}
Esta é uma função de uso geral, então primeiro verificamos se temos uma referência de assembly válida. Depois, chamamos o método GetManifestResourceNames() para obter a lista de tudo o que está incorporado nele. Se não houver arquivos incorporados, saímos da função. Caso contrário, exibimos os nomes dos recursos no console.

E-book gratuito
Sua Web API está pronta para produção?
33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.
O e-book está em inglês.
Baixe o checklist gratuitoPDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.
Os nomes dos arquivos incorporados
Ao chamar ListResourcesInAssembly(ThisAssembly) por meio de um método público, a partir do Program.cs, obtemos a lista de arquivos:
Resources in Embedded_Resources_in_NET, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Embedded_Resources_in_NET.Files.text-file.txt Embedded_Resources_in_NET.Resources.text-file.txt Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf
Cada nome de recurso é separado por pontos. Lendo esse nome de trás para frente, descobrimos de onde ele veio:

‘Embedded_Resources_in_NET‘ é o nosso namespace raiz, que por padrão é o nome do projeto; Resources e Pdf são as respectivas subpastas, e pdf-file.pdf é o nome do arquivo do recurso incorporado. O ponto (‘.’) é o separador entre o namespace raiz, toda a estrutura de pastas e o nome do próprio recurso incorporado.
Essa estrutura nos permite ter dois arquivos idênticos chamados text-file.txt no assembly sem conflito. Um fica na subpasta Files, então o nome dele é Embedded_Resources_in_NET.Files.text-file.txt, enquanto o outro está na subpasta Resources, e o nome dele é Embedded_Resources_in_NET.Resources.text-file.txt. Como os nomes são diferentes, não há problema.
Recursos incorporados no arquivo .csproj
O Visual Studio registra no arquivo .csproj os arquivos selecionados para incorporação. Veja o trecho relevante do XML:
<ItemGroup>
<EmbeddedResource Include="Files\text-file.txt" />
<EmbeddedResource Include="Resources\Pdf\pdf-file.pdf" />
<EmbeddedResource Include="Resources\text-file.txt" />
</ItemGroup>
O Visual Studio grava um caminho literal por arquivo, mas o item aceita bem mais do que isso. A primeira linha abaixo incorpora um arquivo note.txt que agora adicionamos a uma nova pasta my-folder do projeto de exemplo, e as outras três mostram o que mais o item consegue expressar:
<ItemGroup>
<EmbeddedResource Include="my-folder\note.txt" />
<EmbeddedResource Include="Resources\**\*" Exclude="Resources\skipme.txt" />
<EmbeddedResource Include="..\..\README.md" Link="Docs\README.md" />
<EmbeddedResource Include="Files\note.txt" LogicalName="note" />
</ItemGroup>
O curinga continua incorporando os arquivos adicionados a Resources depois, Exclude retira um deles de novo, Link dá a um arquivo externo uma pasta virtual dentro do assembly e LogicalName substitui por completo o nome calculado. Os dois últimos mudam o nome final do recurso, e a tabela mais abaixo lista o que cada um produz. O nosso my-folder\note.txt é o caso mais simples: ele é incorporado como Embedded_Resources_in_NET.my_folder.note.txt, porque um nome de pasta que não é um identificador válido é reescrito para se tornar um.
Recursos incorporados fora do projeto
Modificando o arquivo .csproj, podemos incorporar arquivos que não estão nas subpastas do projeto, algo que a janela Properties não oferece. Editar o arquivo nos permite incluir arquivos de outros locais, assim:
<ItemGroup>
<EmbeddedResource Include="Files\text-file.txt" />
<EmbeddedResource Include="Resources\Pdf\pdf-file.pdf" />
<EmbeddedResource Include="Resources\text-file.txt" />
<EmbeddedResource Include="my-folder\note.txt" />
<EmbeddedResource Include="..\Embedded_Resources_in_NET.sln" />
<EmbeddedResource Include="..\..\README.md" />
</ItemGroup>
Com essa configuração, incorporamos um arquivo de solução, uma pasta acima, e até um arquivo README.md que está duas pastas acima. O repositório de exemplo tem os dois; nas nossas próprias pastas, criamos o que estiver faltando antes de compilar, e um arquivo vazio basta. Executar esse código gera:
Resources in Embedded_Resources_in_NET, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Embedded_Resources_in_NET.Files.text-file.txt Embedded_Resources_in_NET.Resources.text-file.txt Embedded_Resources_in_NET.my_folder.note.txt Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf Embedded_Resources_in_NET.Embedded_Resources_in_NET.sln Embedded_Resources_in_NET.README.md
Repare que todos esses recursos recebem nomes como se estivessem na mesma pasta do arquivo de projeto. O metadado Link resolve isso: <EmbeddedResource Include="..\..\README.md" Link="Docs\README.md" /> incorpora o arquivo como Embedded_Resources_in_NET.Docs.README.md.
No entanto, isso foi só um teste para explorar a possibilidade. Embora seja viável, incorporar recursos de fora do projeto não é recomendável. Não devemos incorporar recursos de fora do projeto, porque não temos como garantir que eles estejam disponíveis em tempo de compilação. Um checkout esparso ou parcial basta para quebrar isso: o compilador para com error CS1566: Error reading resource ... Could not find file.
Como ler recursos incorporados de outro assembly?
Nada em GetManifestResourceNames() está preso ao assembly em que estamos executando. Com uma referência a qualquer assembly carregado, a mesma chamada lista os recursos desse assembly.
Assembly.Load("Name") obtém a referência a partir de um nome simples de assembly, e typeof(SomeTypeInside).Assembly a obtém sem string nenhuma quando já referenciamos o projeto.
AppDomain.CurrentDomain.GetAssemblies() parece ser o jeito de varrer a aplicação inteira, mas não é. Ele retorna os assemblies já carregados neste processo, e esse não é o mesmo conjunto de assemblies que a nossa solução compila. Um projeto referenciado cujos tipos o código ainda não usou fica de fora desse array, mesmo que a DLL dele esteja na pasta de saída ao lado da nossa, porque o runtime carrega os assemblies no primeiro uso.
Um assembly satélite é outra coisa: um arquivo .resources.dll sem código que guarda os recursos de uma cultura, gerado a partir de arquivos .resx com sufixo de cultura e colocado em uma subpasta com o nome da cultura.
A localização é a origem dos assemblies satélite; então, se foi isso que nos trouxe até aqui, explicamos em outros artigos como localizar uma aplicação com arquivos .resx específicos de cada cultura e também como ler as strings de volta com ResourceManager.
Para este experimento, vamos criar um novo projeto ao lado do nosso projeto de console e incorporar um arquivo de texto a esse assembly:
dotnet new classlib -o Embedded_Resources_in_NET_Library
Em seguida, abra esse novo projeto no Visual Studio e incorpore um arquivo de texto em Resources\text-file.txt. Outra opção é editar o arquivo .csproj:
<ItemGroup>
<EmbeddedResource Include="Resources\text-file.txt" />
</ItemGroup>
Essa configuração vai incorporar o text-file.txt à nossa biblioteca de classes.
Leitura da lista de recursos incorporados de um assembly referenciado
Podemos aplicar a mesma abordagem a qualquer assembly, inclusive a bibliotecas de classes referenciadas.
Já temos o método ListResourcesInAssembly() para isso. Basta adicionar, no projeto de console, uma referência de projeto à biblioteca e especificar o assembly correto:
private static Assembly ReferencedAssembly =>
Assembly.Load("Embedded_Resources_in_NET_Library");
Depois, podemos chamar o método:
public static void ListResourcesInReferencedAssembly()
=> ListResourcesInAssembly(ReferencedAssembly);
Como a nossa biblioteca de classes contém só um recurso incorporado, recebemos uma lista com apenas um elemento:
Resources in Embedded_Resources_in_NET_Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Embedded_Resources_in_NET_Library.Resources.text-file.txt
Isso demonstra como podemos ler recursos incorporados de qualquer assembly, o que dá flexibilidade no gerenciamento de recursos.
Leitura da lista de recursos incorporados em todos os assemblies carregados
Para listar tudo o que está incorporado em todos os assemblies que compõem a nossa solução, podemos primeiro obter a lista de todos os assemblies do domínio de aplicação atual:
private static Assembly[] AllAssembliesOfCurrentAppDomain
=> AppDomain.CurrentDomain.GetAssemblies();
Em seguida, podemos percorrer essa lista e chamar o método ListResourcesInAssembly para cada assembly:
public static void ListResourcesInAllAssemblies()
=> AllAssembliesOfCurrentAppDomain.ToList().ForEach(ListResourcesInAssembly);
Isso abrange todos os assemblies que o processo carregou até agora, o que não é o mesmo que todos os assemblies da nossa solução. Chamar Assembly.Load() para os que nos interessam, antes da varredura, é o que deixa a lista completa.
Como ler o conteúdo de um recurso incorporado?
GetManifestResourceStream() é a contraparte de GetManifestResourceNames(): passe um desses nomes e ele retorna um Stream com os bytes incorporados.
Assim como obtemos a lista completa de nomes com o método GetManifestResourceNames(), podemos obter o conteúdo de um recurso com o método GetManifestResourceStream().
Com um objeto Stream em mãos, podemos realizar várias operações, como ler, transformar, exibir, salvar em disco, transmitir pela rede e muito mais. Os Streams oferecem uma forma flexível e poderosa de lidar com dados em C#.

E-book gratuito
Sua Web API está pronta para produção?
33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.
O e-book está em inglês.
Baixe o checklist gratuitoPDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.
Busca de um recurso incorporado nos assemblies
Para simplificar a localização de um recurso incorporado específico em todos os assemblies que o nosso processo carregou, podemos implementar um método utilitário:
private static Stream? FindResource(Func<string[]?, string?> finder)
{
foreach (var assembly in AllAssembliesOfCurrentAppDomain)
{
var resourceNames = assembly.GetManifestResourceNames();
var resourceName = finder(resourceNames);
if (resourceName is not null)
{
Console.WriteLine($"Resource {resourceName} found in {assembly.FullName}");
return assembly.GetManifestResourceStream(resourceName);
}
}
return null;
}
O método percorre cada assembly, obtém a lista de nomes de recursos desse assembly com GetManifestResourceNames() e passa essa lista para um método finder externo.
Se o método finder encontrar o recurso desejado, imprimimos uma mensagem informando que ele foi encontrado e retornamos o Stream correspondente com GetManifestResourceStream(). Se o recurso não for encontrado em nenhum assembly, retornamos null.
Busca de um recurso pelo nome completo
Para encontrar um recurso incorporado pelo nome completo, podemos criar um método de busca que retorna o nome se ele corresponder exatamente:
FindResource(names => names?.FirstOrDefault(rn => rn == resourceName));
Esse método usa a função LINQ FirstOrDefault() para encontrar o primeiro nome de recurso que corresponde ao resourceName informado.
Busca de um recurso por parte do nome
Da mesma forma, para encontrar um recurso informando só parte do nome, como ‘pdf-file.pdf’, podemos modificar o método de busca para verificar se o nome contém o trecho informado:
FindResource(names => names?.FirstOrDefault(rn => rn.Contains(partialResourceName)));
Aqui, usamos o método Contains para verificar se algum nome de recurso contém o partialResourceName especificado. Se houver correspondência, esse nome de recurso é retornado.
Exibição do conteúdo de um recurso incorporado
Com um objeto Stream que representa o recurso incorporado, exibir o conteúdo na tela é simples:
private static void DisplayResource(string resourceName, Stream resourceStream)
{
using var reader = new StreamReader(resourceStream);
var resourceContent = reader.ReadToEnd();
Console.WriteLine($"Resource {resourceName} content:");
Console.WriteLine(resourceContent);
}
O método lê o conteúdo do stream do recurso com um StreamReader. Em seguida, imprime o nome do recurso e o conteúdo no console.
Streams no Code Maze, em artigos como As classes StreamWriter e StreamReader em C#, MemoryStream em C#: como usar, Uso de streams com HttpClient para melhorar o desempenho e o uso de memória, entre outros.Exibição do arquivo PDF
Para exibir o arquivo PDF incorporado, primeiro precisamos salvar o conteúdo dele em disco e, depois, abrir o arquivo salvo com um programa de PDF. Podemos fazer isso com dois métodos:
private static string SaveResourceToAFile(string partialResourceName, Stream resourceStream)
{
var tempFileName = Path.Combine(Path.GetTempPath(), partialResourceName);
using var fileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.Write);
resourceStream.CopyTo(fileStream);
fileStream.Close();
return tempFileName;
}
private static void ShowFile(string fileName)
=> Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
O método SaveResourceToAFile() recebe o nome parcial do arquivo e o Stream que representa o recurso incorporado. Ele cria um arquivo temporário na pasta temporária do usuário atual e grava nele o conteúdo do stream do recurso. Depois, retorna o nome do arquivo recém-criado.
O método ShowFile() inicia um novo processo usando o programa padrão do sistema para abrir arquivos PDF e passa o nome do arquivo como argumento. Isso abre o arquivo PDF no visualizador de PDF padrão instalado no sistema.
Por que GetManifestResourceStream() retorna null?
Porque a string que passamos não é um nome do manifesto, e o método avisa isso retornando null em vez de lançar uma exceção.
Quatro diferenças explicam quase todos os erros. O prefixo é o namespace raiz do projeto, não o nome do assembly, e os dois podem ser diferentes mesmo que ninguém tenha mudado nenhum deles. Os nomes de pasta viram identificadores, então um hífen ou um espaço vira sublinhado, e um dígito no início ganha um sublinhado na frente. O separador é um ponto, nunca uma barra ou uma barra invertida. E a correspondência diferencia maiúsculas de minúsculas, então uma única letra maiúscula errada não retorna nada.
Sem um código de cultura, o nome do arquivo fica intacto, com hífens e tudo, e é por isso que metade de um nome errado parece certa.
O caminho seguro é parar de montar a string. Chame GetManifestResourceNames() uma vez, imprima o que ele retornar e copie a entrada que ele fornecer.
Esse null é documentado, não acidental. A referência da Microsoft para Assembly.GetManifestResourceStream diz sobre o valor retornado:
nullse nenhum recurso tiver sido especificado durante a compilação ou se o recurso não estiver visível para o chamador.
Veja o que o compilador produz a partir de cada tipo de entrada. Duas propriedades do MSBuild mudam a resposta: EnableDefaultEmbeddedResourceItems, que desativa o curinga automático .resx, e EmbeddedResourceUseDependentUponConvention, que faz um .resx ao lado de um arquivo de código-fonte receber o nome do tipo declarado nesse arquivo.
| O arquivo no projeto | Nome do recurso no manifesto | A regra |
|---|---|---|
Resources\text-file.txt | MyApp.Resources.text-file.txt | Namespace raiz, depois cada pasta e depois o nome do arquivo, unidos por pontos |
my-folder\a.txt | MyApp.my_folder.a.txt | Os nomes de pasta viram identificadores: um hífen ou um espaço vira _ |
two words\a.txt | MyApp.two_words.a.txt | Mesma regra, inclusive para espaços |
2digits\a.txt | MyApp._2digits.a.txt | Uma pasta que começa com um dígito ganha um _ no início |
Files\my-file-name.txt | MyApp.Files.my-file-name.txt | Sem um código de cultura, o nome do arquivo fica intacto; os hífens dele se mantêm |
..\..\README.md | MyApp.README.md | As pastas fora do projeto são descartadas |
..\..\README.md com Link="Docs\README.md" | MyApp.Docs.README.md | Link escolhe a pasta virtual de um arquivo externo |
Files\note.txt com LogicalName="note" | note | LogicalName substitui todo o nome calculado |
Auto.resx, sem nenhuma entrada no arquivo de projeto | MyApp.Auto.resources | O SDK incorpora todo .resx por padrão e o compila, por isso a extensão muda |
Forms\Form1.resx ao lado de Forms\Form1.cs, que declara Some.Ns.Form1 | Some.Ns.Form1.resources | O nome vem do tipo que fica junto dele, e não do caminho da pasta |
Conclusão
Incorporar recursos em assemblies .NET é simples. Podemos selecioná-los no Visual Studio ou adicioná-los manualmente como tags XML no arquivo .csproj.
Por padrão, todo recurso incorporado que não seja um arquivo .resx nem um arquivo com sufixo de cultura, como note.fr.txt, que vai para um assembly satélite, recebe um nome formado pela concatenação do namespace raiz do projeto com o caminho da pasta dentro do projeto e o nome do arquivo, separados por pontos, com cada nome de pasta convertido em um identificador válido. Por exemplo, Embedded_Resources_in_NET.Resources.Pdf.pdf-file.pdf.
Chamar o método GetManifestResourceNames() do objeto Assembly retorna a lista de todos os nomes de recursos de um assembly. Da mesma forma, o método GetManifestResourceStream() obtém o stream do recurso incorporado.
Um único mecanismo leva arquivos de texto, imagens e arquivos binários, como PDFs, dentro do próprio assembly. Cada assembly continua sendo um arquivo só, e todo recurso de que a aplicação precisa viaja junto com o código que o lê.
Testado com .NET 10.0.10.