.Net 全自动绑定配置信息到配置对象
适用版本:.net core 3.1–latest
一、下面是使用一般方法绑定配置文件
这是appsettings.json 配置文件中需要我们绑定的对象数据
"DataPoolApiOption": {
"get_geo_model_by_idUrl": "123",
"get_geo_prop_model_by_idUrl": "123",
"get_sim_case_list": "123",
"get_sim_deck_well_list": "123",
"get_well_sim_summary_list": "123",
"get_reservior_sim_summary_list": "123",
"ParamFolder": "123",
"ProtoFolder": "123"
},
创建一个类,属性名称保证和json中一致
public class DataPoolApiOption
{
public string get_geo_model_by_idUrl { get; set; }
public string get_geo_prop_model_by_idUrl { get; set; }
public string get_sim_case_list { get; set; }
public string get_sim_deck_well_list { get; set; }
public string get_well_sim_summary_list { get; set; }
public string get_reservior_sim_summary_list { get; set; }
public string ParamFolder { get; set; }
public string ProtoFolder { get; set; }
}
startup.cs中我们使用Configuration.GetSection方法映射属性数据
var dpOptions = Configuration.GetSection(nameof(DataPoolApiOption));
var dpOptions = Configuration.GetSection("DataPoolApiOption");
var apiOptions = new DataPoolApiOption();
dpOptions.Bind(apiOptions);
或者 使用如下方式利用Configuration一个个的取出我们想要读取的信息
类中构造函数赋值 private readonly IConfiguration _configuration;
var get_geo_model_by_idUrl = this._configuration["get_geo_model_by_idUrl"];
优点:简单、快。小型Demo项目完全可以使用这种方式操作,没必要搞得那么“通用”。
缺点:难以维护,哪里都是。一旦配置信息使用的地方增多,那马上就会变得毫无章法可言。每次新增,变更,删除等都要 “全局搜索” 式的修改。
二、下面我们来使用全自动注入方式进行管理
说明:利用反射、泛型、IOption等多种工具实现最优雅、解放双手式的注入管理,让我们的配置信息不在变得讨厌。
背景:参考了全局注入代码,由此演变而来的自动注入方式
优点:映射关系清晰,管理维护方便,统一修改统一更新,不担心滥用,只负责管理对象和json即可
第一步:首先我们定义一个Interface,该Interface的目的是给我们的被Bind类打上记号,告诉反射我们要映射的对象是谁,让我们需要bind的类继承这个interface
/// <summary>
/// 该接口需要所有配置绑定对象实现(继承该接口系统自动识别并注入配置对象)
/// </summary>
public interface IAutoOptoin
{
}
使用上面定义好的类继承 IAutoOption
第二步:编写一般的绑定方法,如下
/// <summary>
/// 注入工具
/// </summary>
public class InjectInOptions
{
/// <summary>
/// 向Options中注入 - 注入方法
/// 目前被反射 AutoInjectOptionsExtend.AutoInjectOptions<T> 调用
/// </summary>
/// <typeparam name="T">被注入类型</typeparam>
/// <param name="services">被扩展的服务</param>
/// <param name="configuration">程序配置</param>
/// <param name="key">appsetting key</param>
public static void SetInOptionProvider<T>(IServiceCollection services, IConfiguration configuration, string key) where T : class
{
services.Configure<T>(configuration.GetSection(key));
}
}
第三步:写一个反射调用静态泛型方法的功能,如下 (参数代码中有说明,看不太明白的小伙伴可以拿下来试试)
/// <summary>
/// 反射自动识别配置类
/// </summary>
public class InvokeMethodUtil
{
/// <summary>
/// 反射调用泛型方法
/// </summary>
/// <param name="type">方法所在类</param>
/// <param name="genericTypes">泛型type[]</param>
/// <param name="methodName">被调用方法名称</param>
/// <param name="methodParams">被调用方法所有参数</param>
public static void BeginInvokeGenericMethod(Type type, Type[] genericTypes, string methodName, params object[] methodParams)
{
创建实例
var ins = Activator.CreateInstance(type);
获取方法堆栈
MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static) ?? throw new ArgumentException($"Reflection failed{JsonConvert.SerializeObject(ins)}");
获取泛型方法堆栈
method = method.MakeGenericMethod(genericTypes);
执行泛型结果
method.Invoke(ins, methodParams);
}
}
现在一切准备就绪,我们再编写一个过滤全局被绑定的类、同时拼接以上三步的静态扩展方法就好了
第四步:使用RuntimeUtil内置的获取运行程序所有的类的思路,过滤出我们想要绑定的对象,同时利用反射动态抓取配置文件的配置对象进行绑定
》》》》前提条件:被绑定的类名一定要和appsetting.json中的对象名一致,否则会出现无法成功绑定数据的情况,但不会报错《《《《
RuntimeUtil我会放在下面
/// <summary>
/// 自动化工具
/// </summary>
public static class AutoInjectOptionsExtend
{
/// <summary>
/// 根据接口自动注入实现对象
/// </summary>
/// <param name="services">IServiceCollection</param>
/// <param name="configuration">IConfiguration</param>
public static void AutoInjectOptions<T>(this IServiceCollection services, IConfiguration configuration)
{
foreach (var item in RuntimeUtil.GetAllCoreAssemblies())
{
foreach (var install in item.GetTypes().Where(t => typeof(T).IsAssignableFrom(t) && t.IsClass))
{
Type[] invokeTypes = new Type[1] { install };
object[] invokeParams = new object[3] { services, configuration, install.Name };
InvokeMethodUtil.BeginInvokeGenericMethod(typeof(InjectInOptions), invokeTypes, nameof(InjectInOptions.SetInOptionProvider), invokeParams);
};
}
}
}
该方法中的泛型类型就是在上面定义的IAutoOption,使用方法如下:
startup.cs中
public void ConfigureServices(IServiceCollection services)
{
services.AutoInjectOptions<IAutoOptoin>(Configuration);
}
效果展示
1、通过构造函数注入 .Value取出绑定数据后的对象
2、测试方法
3、结果 : 可以看到和我们的appsettings.json中数据是一致的
如果改进和不足请同行大佬及时指出,我们互相学习进步!
By Loyar.
RuntimeUtil :
依赖包:Microsoft.Extensions.DependencyModel
/// <summary>
/// 运行时扩展工具
/// </summary>
public class RuntimeUtil
{
/// <summary>
/// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包
/// </summary>
/// <returns></returns>
public static IList<Assembly> GetAllAssemblies()
{
var list = new List<Assembly>();
var deps = DependencyContext.Default ?? throw new ArgumentException("注入内容获取失败 位置:RuntimeUtil.GetAllAssemblies() 18-Line");
//排除所有的系统程序集、Nuget下载包
var libs = deps.CompileLibraries.Where(lib => lib.Type == AssembleTypeConsts.Project);//只获取本项目用到的包
foreach (var lib in libs)
{
try
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
list.Add(assembly);
}
catch (System.Exception ex)
{
throw new System.Exception(ex.ToString());
}
}
return list;
}
/// <summary>
/// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包和Yuebon.Commons.dll
/// </summary>
/// <returns></returns>
public static IEnumerable<Assembly> GetAllCoreAssemblies()
{
var list = new List<Assembly>();
var deps = DependencyContext.Default ?? throw new ArgumentNullException("获取注入内容出错 位置 :RuntimeUtil.GetAllCoreAssemblies() 41-Line ");
//排除所有的系统程序集、Nuget下载包
var libs = deps.CompileLibraries.Where(lib => lib.Type == AssembleTypeConsts.Project);//只获取本项目用到的包
foreach (var lib in libs)
{
try
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
list.Add(assembly);
}
catch (System.Exception ex)
{
throw new System.Exception(ex.ToString());
}
}
return list;
}
/// <summary>
///获取某个程序集下所有信息
/// </summary>
/// <param name="assemblyName"></param>
/// <returns></returns>
public static Assembly GetAssembly(string assemblyName)
{
return GetAllCoreAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName));
}
/// <summary>
///获取所有Type属性
/// </summary>
/// <returns></returns>
public static IList<Type> GetAllTypes()
{
var list = new List<Type>();
foreach (var assembly in GetAllAssemblies())
{
var typeInfos = assembly.DefinedTypes;
foreach (var typeInfo in typeInfos)
{
list.Add(typeInfo.AsType());
}
}
return list;
}
/// <summary>
///根据程序集获取Type属性
/// </summary>
/// <param name="assemblyName"></param>
/// <returns></returns>
public static IList<Type> GetTypesByAssembly(string assemblyName)
{
var list = new List<Type>();
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));
var typeInfos = assembly.DefinedTypes;
foreach (var typeInfo in typeInfos)
{
list.Add(typeInfo.AsType());
}
return list;
}
/// <summary>
/// 获取实现类
/// </summary>
/// <param name="typeName"></param>
/// <param name="baseInterfaceType"></param>
/// <returns></returns>
public static Type? GetImplementType(string typeName, Type baseInterfaceType)
{
return GetAllTypes().FirstOrDefault(t =>
{
if (t.Name == typeName &&
t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name))
{
var typeInfo = t.GetTypeInfo();
return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsGenericType;
}
return false;
});
}
}
/// <summary>
///基本配置
/// </summary>
public class AssembleTypeConsts
{
/// <summary>
///依赖包
/// </summary>
public const string Package = "package";
/// <summary>
/// 程序集
/// </summary>
public const string ReferenceAssembly = "referenceassembly";
/// <summary>
/// 项目名称
/// </summary>
public const string Project = "project";
}