ASP.NET API中IConfiguration GetSection扩展与URL自动补全需求
背景
我的ASP.NET API项目中,appsettings.json存储了多个URL配置项:
"Url1": "http://localhost:154/", "Url2": "https://localhost:222/", "Url3": "https://www.googelc.com/"
我通过_configuration.GetSection()获取这些配置:
private readonly IConfiguration _configuration; IConfigurationSection? URL1 = _configuration.GetSection("Url1");
问题:若appsettings.json中的URL未以/结尾(如"Url1": "http://localhost:154"),项目将无法正常运行。
需求:希望在获取配置时,自动检查key包含"url"的配置项,若值未以/结尾则补充该字符。
受限原因:IConfiguration是接口且无直接可修改的实现类,其定义如下:
namespace Microsoft.Extensions.Configuration { public interface IConfiguration { string this[string key] { get; set; } IEnumerable<IConfigurationSection> GetChildren(); IChangeToken GetReloadToken(); IConfigurationSection GetSection(string key); } }
核心问题
除了每次获取URL时手动检查外:
- 有哪些替代解决方案?
- 是否可以修改
_Configuration.GetSection()的逻辑? - 是否支持运行时修改
appsettings.json补全URL的/?
解答
1. 替代解决方案
方案一:自定义强类型配置绑定类
创建包含所有URL配置的强类型类,在属性的set方法中自动处理斜杠补全:
public class UrlSettings { private string _url1; public string Url1 { get => _url1; set => _url1 = EnsureTrailingSlash(value); } private string _url2; public string Url2 { get => _url2; set => _url2 = EnsureTrailingSlash(value); } // 通用处理方法 private string EnsureTrailingSlash(string url) { if (!string.IsNullOrEmpty(url) && url.StartsWith("http") && !url.EndsWith("/")) { return url + "/"; } return url; } }
在Program.cs中注册并绑定配置:
builder.Services.Configure<UrlSettings>(builder.Configuration);
使用时通过IOptions<UrlSettings>注入,直接获取处理后的URL。
方案二:自定义配置包装器
实现包装IConfiguration的类,重写GetSection和索引器逻辑,自动处理URL补全:
public class UrlFixConfiguration : IConfiguration { private readonly IConfiguration _innerConfig; public UrlFixConfiguration(IConfiguration innerConfig) { _innerConfig = innerConfig; } public string this[string key] { get { var value = _innerConfig[key]; return FixUrlIfNeeded(key, value); } set => _innerConfig[key] = value; } public IEnumerable<IConfigurationSection> GetChildren() { return _innerConfig.GetChildren().Select(s => new UrlFixConfigurationSection(s)); } public IChangeToken GetReloadToken() => _innerConfig.GetReloadToken(); public IConfigurationSection GetSection(string key) { var section = _innerConfig.GetSection(key); return new UrlFixConfigurationSection(section); } // URL补全逻辑 private string FixUrlIfNeeded(string key, string value) { if (!string.IsNullOrEmpty(key) && key.IndexOf("url", StringComparison.OrdinalIgnoreCase) >= 0 && !string.IsNullOrEmpty(value) && value.StartsWith("http") && !value.EndsWith("/")) { return value + "/"; } return value; } // 包装配置节点类 private class UrlFixConfigurationSection : IConfigurationSection { private readonly IConfigurationSection _innerSection; public UrlFixConfigurationSection(IConfigurationSection innerSection) { _innerSection = innerSection; } public string this[string key] { get => new UrlFixConfiguration(_innerSection)[key]; set => _innerSection[key] = value; } public IEnumerable<IConfigurationSection> GetChildren() => _innerSection.GetChildren().Select(s => new UrlFixConfigurationSection(s)); public IChangeToken GetReloadToken() => _innerSection.GetReloadToken(); public IConfigurationSection GetSection(string key) => new UrlFixConfigurationSection(_innerSection.GetSection(key)); public string Key => _innerSection.Key; public string Path => _innerSection.Path; public string Value { get { var value = _innerSection.Value; return new UrlFixConfiguration(_innerSection.Configuration).FixUrlIfNeeded(_innerSection.Key, value); } set => _innerSection.Value = value; } } }
在Program.cs中替换配置实例:
var config = builder.Configuration; builder.Services.AddSingleton<IConfiguration>(new UrlFixConfiguration(config));
此后所有通过IConfiguration获取的URL都会自动补全斜杠。
方案三:配置加载阶段转换
在配置构建完成后,遍历所有配置项处理URL:
var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddEnvironmentVariables(); var config = builder.Build(); // 递归处理所有配置节点 FixUrlSections(config.GetChildren()); void FixUrlSections(IEnumerable<IConfigurationSection> sections) { foreach (var section in sections) { if (!string.IsNullOrEmpty(section.Value) && section.Key.IndexOf("url", StringComparison.OrdinalIgnoreCase) >= 0 && section.Value.StartsWith("http") && !section.Value.EndsWith("/")) { section.Value += "/"; } FixUrlSections(section.GetChildren()); } }
2. 是否可以修改_Configuration.GetSection()的逻辑?
不能直接修改IConfiguration默认实现的GetSection逻辑,但可以通过包装器模式(如上述方案二)包裹原有配置实例,在包装类中重写GetSection的行为,实现URL自动补全。这种方式无需修改框架源码,符合扩展设计原则。
3. 是否支持运行时修改appsettings.json补全URL的/?
支持,但需注意以下几点:
- 需开启配置文件的自动重载:
builder.Configuration.AddJsonFile("appsettings.json", reloadOnChange: true); - 直接修改文件存在风险:多实例部署时仅当前实例生效;若应用无文件写入权限会抛出异常。
- 生产环境更推荐使用配置绑定或包装器方案,而非修改原始配置文件。
临时方案说明
目前我在Program.cs中调用静态方法,在应用启动时修改appsettings.json:
public static void EditAppSettings() { try { var filePath = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "appsettings.json"); string json = File.ReadAllText(filePath); dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json); foreach (var item in jsonObj) { string value = item.Value; if (value.Contains("http")) { if (!value.EndsWith("/")) value += "/"; item.Value = value; } } string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented); File.WriteAllText(filePath, output); } catch (Exception ex) { ///do nothing } }
该方法会读取配置文件,对包含http的URL补全/后重新写入。此方式适合单实例开发环境,生产环境建议使用前面提到的配置绑定或包装器方案。
内容的提问来源于stack exchange,提问作者maryam mohammadbagheri

