AssemblyExtensions
Provides assembly information extraction and embedded resource management utilities.
Overview
The AssemblyExtensions class offers powerful utilities for working with .NET assemblies, including metadata extraction, embedded resource operations, and type discovery.
API Reference
Core Methods
Assembly Information
GetAssembly(object instance)
Gets the assembly that contains the specified object's type.
var myObject = new MyClass();
var assembly = myObject.GetAssembly();
Console.WriteLine($"Assembly: {assembly.GetName().Name}");
GetVersion(Assembly assembly)
Retrieves the version information from an assembly.
var assembly = Assembly.GetExecutingAssembly();
var version = assembly.GetVersion();
Console.WriteLine($"Version: {version}");
Embedded Resource Operations
GetManifestResourceStream(Assembly assembly, string resourceName)
Gets a stream for reading an embedded resource.
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream("MyApp.Resources.config.json");
if (stream != null)
{
using var reader = new StreamReader(stream);
var content = reader.ReadToEnd();
}
MoveManifestResourceToDirectory(Assembly assembly, string resourceName, string targetDirectory)
Extracts an embedded resource to a directory.
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.MoveManifestResourceToDirectory(
"MyApp.Resources.template.html",
@"C:\Output"
);
Console.WriteLine($"Resource extracted to: {filePath}");
Type Discovery
GetTypesWithAttribute<T>(Assembly assembly) where T : Attribute
Finds all types in an assembly that have a specific attribute.
var assembly = Assembly.GetExecutingAssembly();
var serviceTypes = assembly.GetTypesWithAttribute<ServiceAttribute>();
foreach (var (attribute, type) in serviceTypes)
{
Console.WriteLine($"Service: {type.Name}, Lifetime: {attribute.Lifetime}");
}
Advanced Operations
Resource Management
MoveManifestResourceToCache(Assembly assembly, string resourceName, string filename = null)
Extracts resource to a temporary cache directory.
var assembly = Assembly.GetExecutingAssembly();
var cachedFile = assembly.MoveManifestResourceToCache(
"MyApp.Resources.data.xml",
"cached-data.xml"
);
// Use the cached file
var xmlContent = File.ReadAllText(cachedFile);
ExtractAllResources(Assembly assembly, string targetDirectory)
Extracts all embedded resources to a directory.
var assembly = Assembly.GetExecutingAssembly();
var extractedFiles = assembly.ExtractAllResources(@"C:\ExtractedResources");
foreach (var file in extractedFiles)
{
Console.WriteLine($"Extracted: {file}");
}
Practical Examples
Plugin System
public class PluginManager
{
public void LoadPlugins(string pluginDirectory)
{
foreach (var dllFile in Directory.GetFiles(pluginDirectory, "*.dll"))
{
var assembly = Assembly.LoadFrom(dllFile);
var pluginTypes = assembly.GetTypesWithAttribute<PluginAttribute>();
foreach (var (attribute, type) in pluginTypes)
{
var plugin = Activator.CreateInstance(type) as IPlugin;
RegisterPlugin(plugin, attribute);
}
}
}
private void RegisterPlugin(IPlugin plugin, PluginAttribute attribute)
{
Console.WriteLine($"Loaded plugin: {attribute.Name} v{attribute.Version}");
// Register plugin with dependency injection container
}
}
[AttributeUsage(AttributeTargets.Class)]
public class PluginAttribute : Attribute
{
public string Name { get; set; }
public string Version { get; set; }
public string Description { get; set; }
}
Configuration Template System
public class ConfigurationTemplateManager
{
private readonly Assembly _assembly;
public ConfigurationTemplateManager()
{
_assembly = Assembly.GetExecutingAssembly();
}
public string CreateConfigurationFile(string templateName, string outputPath, Dictionary<string, string> variables)
{
// Extract template from embedded resources
var templatePath = _assembly.MoveManifestResourceToCache(
$"MyApp.Templates.{templateName}.template",
$"{templateName}.tmp"
);
// Read and process template
var template = File.ReadAllText(templatePath);
foreach (var (key, value) in variables)
{
template = template.Replace($"{{{{ {key} }}}}", value);
}
// Write processed configuration
File.WriteAllText(outputPath, template);
return outputPath;
}
public string[] GetAvailableTemplates()
{
return _assembly.GetManifestResourceNames()
.Where(name => name.Contains(".Templates.") && name.EndsWith(".template"))
.Select(name => Path.GetFileNameWithoutExtension(name.Split('.').Last()))
.ToArray();
}
}
Service Discovery
public class ServiceDiscovery
{
public void RegisterServices(IServiceCollection services)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
RegisterTransientServices(services, assembly);
RegisterSingletonServices(services, assembly);
RegisterScopedServices(services, assembly);
}
}
private void RegisterTransientServices(IServiceCollection services, Assembly assembly)
{
var transientTypes = assembly.GetTypesWithAttribute<TransientServiceAttribute>();
foreach (var (attribute, type) in transientTypes)
{
var interfaceType = attribute.InterfaceType ?? type.GetInterfaces().FirstOrDefault();
if (interfaceType != null)
{
services.AddTransient(interfaceType, type);
}
else
{
services.AddTransient(type);
}
}
}
private void RegisterSingletonServices(IServiceCollection services, Assembly assembly)
{
var singletonTypes = assembly.GetTypesWithAttribute<SingletonServiceAttribute>();
foreach (var (attribute, type) in singletonTypes)
{
var interfaceType = attribute.InterfaceType ?? type.GetInterfaces().FirstOrDefault();
if (interfaceType != null)
{
services.AddSingleton(interfaceType, type);
}
else
{
services.AddSingleton(type);
}
}
}
}
[AttributeUsage(AttributeTargets.Class)]
public class TransientServiceAttribute : Attribute
{
public Type InterfaceType { get; set; }
}
[AttributeUsage(AttributeTargets.Class)]
public class SingletonServiceAttribute : Attribute
{
public Type InterfaceType { get; set; }
}
Resource Localization
public class LocalizationManager
{
private readonly Assembly _assembly;
private readonly Dictionary<string, Dictionary<string, string>> _localizedStrings;
public LocalizationManager()
{
_assembly = Assembly.GetExecutingAssembly();
_localizedStrings = new Dictionary<string, Dictionary<string, string>>();
LoadLocalizations();
}
private void LoadLocalizations()
{
var resourceNames = _assembly.GetManifestResourceNames()
.Where(name => name.Contains(".Localization.") && name.EndsWith(".json"));
foreach (var resourceName in resourceNames)
{
var cultureName = ExtractCultureFromResourceName(resourceName);
using var stream = _assembly.GetManifestResourceStream(resourceName);
using var reader = new StreamReader(stream);
var json = reader.ReadToEnd();
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
_localizedStrings[cultureName] = strings;
}
}
public string GetString(string key, string culture = "en")
{
if (_localizedStrings.TryGetValue(culture, out var strings) &&
strings.TryGetValue(key, out var value))
{
return value;
}
// Fallback to English
if (culture != "en" && _localizedStrings.TryGetValue("en", out var fallbackStrings) &&
fallbackStrings.TryGetValue(key, out var fallbackValue))
{
return fallbackValue;
}
return key; // Return key if no localization found
}
private string ExtractCultureFromResourceName(string resourceName)
{
// Extract culture from "MyApp.Localization.en.json"
var parts = resourceName.Split('.');
return parts[^2]; // Second to last part
}
}
Performance Considerations
- Assembly metadata operations are cached by the runtime
- Resource extraction creates temporary files that should be cleaned up
- Type discovery operations can be expensive for large assemblies
- Consider caching results for frequently accessed operations
Thread Safety
- Assembly metadata operations are thread-safe
- Resource extraction operations should be synchronized if writing to the same directory
- Type discovery results can be safely cached and shared
Best Practices
- Resource Naming: Use consistent naming conventions for embedded resources
- Cleanup: Dispose of streams and clean up extracted files when no longer needed
- Caching: Cache expensive operations like type discovery
- Error Handling: Handle cases where resources or types are not found
- Security: Validate resource names and output paths to prevent path traversal attacks