最近再次拿起java spring看看,它的注解功能还是不错的,因此想在C#是实现一下,我记得我自己写的最早的一个ORM就是参考java bean搞定了,想来没事多看看其它语言总是好的。在项目中每个服务都有十几个配置文件,分散在不同目录,命名规则各异,有的用XML、有的用JSON、还有的直接写在代码里...
今天,我将分享一套基于注解的自动化配置管理方案,让你彻底告别配置文件的烦恼,让代码变得更加优雅和易维护!
C#// 传统方式:配置散落各处
var connStr = ConfigurationManager.AppSettings["DatabaseConnection"];
var timeout = int.Parse(ConfigurationManager.AppSettings["Timeout"] ?? "30");
var logPath = Environment.GetEnvironmentVariable("LOG_PATH") ?? "default.log";
C#// 容易出现运行时错误
var port = int.Parse(ConfigurationManager.AppSettings["Port"]); // 可能抛出异常
var enabled = ConfigurationManager.AppSettings["Enabled"] == "true"; // 字符串比较不可靠
配置项的有效性检查分散在各处,难以统一管理,经常出现配置错误导致的生产故障。
我们的方案采用**"约定优于配置"**的设计理念,通过三个核心注解实现自动化配置管理:
[Configuration]
- 标记配置类[ConfigProperty]
- 标记配置属性[AutoConfigScan]
- 启用自动扫描C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppConfigurationAnnotation
{
// 标记类为配置类的特性
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ConfigurationAttribute : Attribute
{
public string FileName { get; set; }
public string Section { get; set; }
public bool Required { get; set; } = true;
public ConfigurationAttribute(string fileName = null)
{
FileName = fileName;
}
}
// 标记属性为配置属性的特性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ConfigPropertyAttribute : Attribute
{
public string Key { get; set; }
public object DefaultValue { get; set; }
public bool Required { get; set; } = true;
public ConfigPropertyAttribute(string key = null)
{
Key = key;
}
}
// 标记需要自动配置扫描的特性
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AutoConfigScanAttribute : Attribute
{
public string ConfigDirectory { get; set; } = "config";
public string FilePattern { get; set; } = "*.json";
public AutoConfigScanAttribute(string configDirectory = "config")
{
ConfigDirectory = configDirectory;
}
}
}
💡 设计亮点:
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppConfigurationAnnotation
{
// 数据库配置
[Configuration("database")]
public class DatabaseConfig
{
[ConfigProperty("connectionString", Required = true)]
public string ConnectionString { get; set; }
[ConfigProperty("timeout", DefaultValue = 30)]
public int Timeout { get; set; }
[ConfigProperty("enableLogging", DefaultValue = false)]
public bool EnableLogging { get; set; }
[ConfigProperty("maxRetries", DefaultValue = 3)]
public int MaxRetries { get; set; }
}
// 应用程序配置
[Configuration("app")]
public class AppConfig
{
[ConfigProperty("name", Required = true)]
public string Name { get; set; }
[ConfigProperty("version", DefaultValue = "1.0.0")]
public string Version { get; set; }
[ConfigProperty("debug", DefaultValue = false)]
public bool Debug { get; set; }
[ConfigProperty("supportedLanguages")]
public string[] SupportedLanguages { get; set; }
}
// 日志配置
[Configuration("logging")]
public class LoggingConfig
{
[ConfigProperty("level", DefaultValue = "Info")]
public string Level { get; set; }
[ConfigProperty("filePath", DefaultValue = "logs/app.log")]
public string FilePath { get; set; }
[ConfigProperty("maxFileSize", DefaultValue = 10485760)] // 10MB
public long MaxFileSize { get; set; }
[ConfigProperty("enableConsole", DefaultValue = true)]
public bool EnableConsole { get; set; }
}
}
🚨 避坑指南:
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace AppConfigurationAnnotation
{
public class ConfigurationScanner
{
private readonly string _configDirectory;
private readonly Dictionary<string, JsonDocument> _configFiles;
private readonly Dictionary<Type, object> _configInstances;
public ConfigurationScanner(string configDirectory = "config")
{
_configDirectory = configDirectory;
_configFiles = new Dictionary<string, JsonDocument>();
_configInstances = new Dictionary<Type, object>();
}
// 扫描并加载所有配置文件
public void ScanAndLoadConfigurations()
{
try
{
LoadConfigFiles();
ScanAndCreateConfigInstances();
}
catch (Exception ex)
{
Console.WriteLine($"Error during configuration scanning: {ex.Message}");
throw;
}
}
// 加载config目录下的所有JSON文件
private void LoadConfigFiles()
{
if (!Directory.Exists(_configDirectory))
{
Console.WriteLine($"Config directory '{_configDirectory}' does not exist.");
return;
}
var jsonFiles = Directory.GetFiles(_configDirectory, "*.json", SearchOption.AllDirectories);
foreach (var filePath in jsonFiles)
{
try
{
var fileName = Path.GetFileNameWithoutExtension(filePath);
var jsonContent = File.ReadAllText(filePath);
var jsonDocument = JsonDocument.Parse(jsonContent);
_configFiles[fileName.ToLower()] = jsonDocument;
Console.WriteLine($"Loaded configuration file: {filePath}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load config file {filePath}: {ex.Message}");
}
}
}
// 扫描程序集中的配置类并创建实例
private void ScanAndCreateConfigInstances()
{
var assembly = Assembly.GetExecutingAssembly();
var configTypes = assembly.GetTypes()
.Where(t => t.GetCustomAttribute<ConfigurationAttribute>() != null)
.ToList();
foreach (var configType in configTypes)
{
try
{
var configInstance = CreateConfigInstance(configType);
if (configInstance != null)
{
_configInstances[configType] = configInstance;
Console.WriteLine($"Created configuration instance for: {configType.Name}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to create config instance for {configType.Name}: {ex.Message}");
}
}
}
// 创建配置类实例并填充属性
private object CreateConfigInstance(Type configType)
{
var configAttr = configType.GetCustomAttribute<ConfigurationAttribute>();
var instance = Activator.CreateInstance(configType);
// 确定要使用的配置文件
var fileName = configAttr.FileName?.ToLower() ?? configType.Name.ToLower();
if (fileName.EndsWith("config"))
{
fileName = fileName.Replace("config", "");
}
if (!_configFiles.ContainsKey(fileName))
{
if (configAttr.Required)
{
throw new FileNotFoundException($"Required configuration file not found: {fileName}.json");
}
return instance;
}
var jsonDocument = _configFiles[fileName];
var rootElement = jsonDocument.RootElement;
// 如果指定了Section,则使用特定的节点
if (!string.IsNullOrEmpty(configAttr.Section))
{
if (rootElement.TryGetProperty(configAttr.Section, out var sectionElement))
{
rootElement = sectionElement;
}
else if (configAttr.Required)
{
throw new InvalidOperationException($"Required section '{configAttr.Section}' not found in {fileName}.json");
}
}
// 填充属性值
PopulateConfigProperties(instance, configType, rootElement);
return instance;
}
// 填充配置属性
private void PopulateConfigProperties(object instance, Type configType, JsonElement jsonElement)
{
var properties = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.ToList();
foreach (var property in properties)
{
try
{
var configPropAttr = property.GetCustomAttribute<ConfigPropertyAttribute>();
var propertyName = configPropAttr?.Key ?? property.Name;
// 尝试获取JSON中的值
if (TryGetJsonValue(jsonElement, propertyName, property.PropertyType, out var value))
{
property.SetValue(instance, value);
}
else if (configPropAttr?.DefaultValue != null)
{
property.SetValue(instance, configPropAttr.DefaultValue);
}
else if (configPropAttr?.Required == true)
{
throw new InvalidOperationException($"Required property '{propertyName}' not found in configuration");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error setting property {property.Name}: {ex.Message}");
}
}
}
// 尝试从JSON中获取值并转换为目标类型
private bool TryGetJsonValue(JsonElement jsonElement, string propertyName, Type targetType, out object value)
{
value = null;
if (!jsonElement.TryGetProperty(propertyName, out var jsonProperty))
{
return false;
}
try
{
if (targetType == typeof(string))
{
value = jsonProperty.GetString();
}
else if (targetType == typeof(int) || targetType == typeof(int?))
{
value = jsonProperty.GetInt32();
}
else if (targetType == typeof(bool) || targetType == typeof(bool?))
{
value = jsonProperty.GetBoolean();
}
else if (targetType == typeof(double) || targetType == typeof(double?))
{
value = jsonProperty.GetDouble();
}
else if (targetType == typeof(DateTime) || targetType == typeof(DateTime?))
{
value = jsonProperty.GetDateTime();
}
else if (targetType.IsArray)
{
value = JsonSerializer.Deserialize(jsonProperty.GetRawText(), targetType);
}
else
{
// 复杂类型使用JsonSerializer
value = JsonSerializer.Deserialize(jsonProperty.GetRawText(), targetType);
}
return true;
}
catch
{
return false;
}
}
// 获取配置实例
public T GetConfiguration<T>() where T : class
{
return _configInstances.ContainsKey(typeof(T)) ? (T)_configInstances[typeof(T)] : null;
}
// 获取所有配置实例
public IEnumerable<object> GetAllConfigurations()
{
return _configInstances.Values;
}
// 释放资源
public void Dispose()
{
foreach (var jsonDoc in _configFiles.Values)
{
jsonDoc?.Dispose();
}
_configFiles.Clear();
_configInstances.Clear();
}
}
}
💡 核心技术点:
C#[AutoConfigScan("config")] // 启用自动扫描
class Program
{
static void Main(string[] args)
{
var scanner = new ConfigurationScanner("config");
try
{
// 🔄 一键扫描加载
scanner.ScanAndLoadConfigurations();
// 🎯 强类型获取配置
var dbConfig = scanner.GetConfiguration<DatabaseConfig>();
var appConfig = scanner.GetConfiguration<AppConfig>();
// 💪 类型安全的配置使用
Console.WriteLine($"数据库连接: {dbConfig.ConnectionString}");
Console.WriteLine($"超时时间: {dbConfig.Timeout}秒");
Console.WriteLine($"应用名称: {appConfig.Name} v{appConfig.Version}");
// 🔍 调试模式检查
if (appConfig.Debug)
{
Console.WriteLine("🐛 调试模式已启用");
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ 配置加载失败: {ex.Message}");
}
finally
{
scanner.Dispose(); // 🧹 资源清理
}
}
}
database.json:
JSON{
"connectionString": "Server=localhost;Database=MyApp;Trusted_Connection=true;",
"timeout": 60,
"enableLogging": true,
"maxRetries": 5
}
app.json:
JSON{
"name": "MyApplication",
"version": "2.1.0",
"debug": true,
"supportedLanguages": [ "en", "zh", "es" ]
}
logging.json:
JSON{
"level": "Debug",
"filePath": "logs/debug.log",
"maxFileSize": 52428800,
"enableConsole": true
}
C#namespace AppConfigurationAnnotation
{
class Program
{
static void Main(string[] args)
{
var scanner = new ConfigurationScanner("config");
try
{
// 扫描并加载所有配置
scanner.ScanAndLoadConfigurations();
// 获取特定配置
var dbConfig = scanner.GetConfiguration<DatabaseConfig>();
var appConfig = scanner.GetConfiguration<AppConfig>();
var loggingConfig = scanner.GetConfiguration<LoggingConfig>();
// 使用配置
Console.WriteLine("=== Database Configuration ===");
Console.WriteLine($"Connection String: {dbConfig?.ConnectionString}");
Console.WriteLine($"Timeout: {dbConfig?.Timeout}");
Console.WriteLine($"Enable Logging: {dbConfig?.EnableLogging}");
Console.WriteLine($"Max Retries: {dbConfig?.MaxRetries}");
Console.WriteLine("\n=== App Configuration ===");
Console.WriteLine($"Name: {appConfig?.Name}");
Console.WriteLine($"Version: {appConfig?.Version}");
Console.WriteLine($"Debug: {appConfig?.Debug}");
Console.WriteLine($"Supported Languages: {string.Join(", ", appConfig?.SupportedLanguages ?? new string[0])}");
Console.WriteLine("\n=== Logging Configuration ===");
Console.WriteLine($"Level: {loggingConfig?.Level}");
Console.WriteLine($"File Path: {loggingConfig?.FilePath}");
Console.WriteLine($"Max File Size: {loggingConfig?.MaxFileSize}");
Console.WriteLine($"Enable Console: {loggingConfig?.EnableConsole}");
// 获取所有配置实例
Console.WriteLine("\n=== All Configuration Types ===");
foreach (var config in scanner.GetAllConfigurations())
{
Console.WriteLine($"- {config.GetType().Name}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
scanner.Dispose();
}
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
}
}
C#[Configuration("complex")]
public class ComplexConfig
{
[ConfigProperty("serverSettings")]
public ServerSettings Server { get; set; }
[ConfigProperty("endpoints")]
public Dictionary<string, string> Endpoints { get; set; }
}
public class ServerSettings
{
public string Host { get; set; }
public int Port { get; set; }
public bool UseSSL { get; set; }
}
JSON{
"serverSettings": {
"host": "prod-server.company.com",
"port": 443,
"useSSL": true
},
"endpoints": {
"userApi": "https://api.company.com/users",
"orderApi": "https://api.company.com/orders",
"paymentGateway": "https://payment.company.com",
"emailService": "https://mail.company.com/api",
"fileStorage": "https://cdn.company.com",
"logService": "https://logs.company.com/collect"
}
}
这套方案已在多个生产项目中验证,能够显著提升配置管理的开发体验。你是否也遇到过类似的配置管理难题?欢迎在评论区分享你的解决方案,或者说说你希望看到哪些进阶功能!
💫 如果这篇文章对你有帮助,请转发给更多需要的同行开发者!
关注我,获取更多C#实战技巧和最佳实践分享!下期预告:《ASP.NET* Core中间件开发的7个高级技巧》*
相关信息
通过网盘分享的文件:AppConfigurationAnnotation.zip 链接: https://pan.baidu.com/s/1sZ9PBcIlj0zXldfFrYkvXw?pwd=aqaw 提取码: aqaw --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!