前言
想变优秀的第N天。
学习张老师的Blog.Core。
1.创建Asp.NetCore API
1.1创建项目
启用OpenAPI:sawgger
不适用顶级语句:使用main函数
使用控制器:controller
1.2配置说明
iisSettings:iis配置。
http:kestrl启动配置。
IIS Express:iis启动配置。
2.仓储+服务
创建以下公共类库和API,他们分别是:
Peng.Net8:WebApi。
Peng.Net8.Common:公共帮助类。
Peng.Net8.Model:实体层。
Peng.Net8.Repository:仓储层。
Peng.Net8.IService:服务接口层。
Peng.Net8.Service:服务层。
3.泛型基类
3.1仓储基类
IBaseRepository:需要对传入TEntity(实体模型)进行数据操作。
BaseRepository:实现IBaseRepository。
3.2服务基类
IBaseServices:对传入的TEntity(实体模型)进行操作,但是不能返回TEntity,需要返回TVo(视图模型),不能将实体字段暴露给WebAPI层。
BaseServices:实现IBaseServices。
4.AutoMapper(对象映射)
使用AutoMapper是为了将视图模型和实体模型进行相互转换。
4.1安装
在Peng.Net8.Common层安装AutoMapper的俩个包,这里直接粘贴保存就能自动安装。
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
4.2使用
将UserInfo和UserInfoVo的字段进行配置。
AutoMapperConfig:
/// <summary>
/// 静态全局 AutoMapper 配置文件
/// </summary>
public class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
return new MapperConfiguration(cfg =>
{
cfg.AddProfile(new CustomProfile());
});
}
}
注入:
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
AutoMapperConfig.RegisterMappings();
5.原生依赖注入
在NetCore开发中,减少new的使用,尽量使用依赖注入。(不应该使用new,比如复杂的链路、GC回收、内存泄露等)
AddSington:单例模式。
AddScope:会话模式。一个http请求。
AddTrasient:瞬时模式。
5.1仓储服务代码
仓储层:
服务层:
5.2依赖注入
注入:
6.自定义项目框架模版
官网地址:https://learn.microsoft.com/zh-cn/dotnet/core/tools/custom-templates
6.1template.json
{
"$schema": "https://json.schemastore.org/template.json",
"author": "peng", // 模板作者 必须
"classifications": [ "Web/WebAPI" ], //必须,这个对应模板的Tags 模板特征标识。上文举例的配置是因为我自定义的模板包括了console和webapi
"name": "Peng.Net8 Dotnet", //必须,这个对应模板的Templates 用户看到的模板名称
"identity": "Peng.Net8.Template", //可选,模板的唯一名称
"shortName": "PNetTpl", //必须,这个对应模板的Short Name 短名称。当使用CLI命令创建模板项目时,使用短名称将利于使用。
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "Peng.Net8", // 可选,要替换的名字
"preferNameDirectory": true // 可选,添加目录
}
6.2安装模板
安装模板 (绝对路径)
dotnet new install 绝对路径\Peng.Net8 --force
如果需要卸载重来的话,卸载模版命令
dotnet new uninstall 绝对路径\Peng.Net8
查看命令
donet new list
查看模版支持选项,使用的名称是template.json中的shortName
dotnet new 名称 -h
6.3新模版创建项目
使用新模版创建项目(记得关闭vs,要不会报错The process cannot access the file ‘ ’ because it is being used by another process.)。
dotnet new 模版名称 -n 项目名称
dotnet new PNetTpl -n PengPeng.Net8
- -n:项目名称
- -o:生成项目路径
- -E:/--EnableFramework 自定义命令 (生成项目模式)
创建成功
VS2022创建
6.4nuget
下载nuget.exe文件
https://www.nuget.org/downloads
打包模板,并生成.nupkg文件
nuget.exe pack Peng.Net8/peng.net8.template.nuspec
发布到nuget
nuget push Peng.Net8.Template.1.0.0.nupkg -Source "你的nuget 服务 url" -ApiKey "你的nuget api key"
7.Autofac
7.1安装Autofac
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
</ItemGroup>
7.2注入Autofac
构造函数注入
public class AutofacModuleRegister : Autofac.Module
{
/*
1、看是哪个容器起的作用,报错是什么
2、三步走导入autofac容器
3、生命周期,hashcode对比,为什么controller里没变化
4、属性注入
*/
protected override void Load(ContainerBuilder builder)
{
var basePath = AppContext.BaseDirectory;
var servicesDllFile = Path.Combine(basePath, "Peng.Net8.Service.dll");
var repositoryDllFile = Path.Combine(basePath, "Peng.Net8.Repository.dll");
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(BaseServices<,>)).As(typeof(IBaseServices<,>)).InstancePerDependency(); //注册服务
// 获取 Service.dll 程序集服务,并注册
var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces()
.InstancePerDependency()
.PropertiesAutowired();
// 获取 Repository.dll 程序集服务,并注册
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
builder.RegisterAssemblyTypes(assemblysRepository)
.AsImplementedInterfaces()
.PropertiesAutowired()
.InstancePerDependency();
}
}
属性注入:
public class AutofacPropertityModuleReg : Module
{
protected override void Load(ContainerBuilder builder)
{
var controllerBaseType = typeof(ControllerBase);
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
.PropertiesAutowired();
}
}
把autofac注入到ServiceCollection
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule<AutofacModuleRegister>();
builder.RegisterModule<AutofacPropertityModuleReg>();
});
// 属性注入
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
// Add services to the container.
builder.Services.AddControllers();
7.3构造函数注入
在构造函数中赋值:
7.4属性注入
ASP.NET Core默认不使用DI获取Controller,是因为DI容器构建完成后就不能变更了,但是Controller是可能有动态加载的需求的。
需要使用IControllerActivator开启Controller的属性注入,默认不开启。
必须使用public修饰属性
8.AOP(Log)
8.1安装
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
8.2动态代理实现ServiceAOP
实现IInterceptor,执行完成后打出日志
public class AOPLogInfo
{
/// <summary>
/// 请求时间
/// </summary>
public string RequestTime { get; set; } = string.Empty;
/// <summary>
/// 操作人员
/// </summary>
public string OpUserName { get; set; } = string.Empty;
/// <summary>
/// 请求方法名
/// </summary>
public string RequestMethodName { get; set; } = string.Empty;
/// <summary>
/// 请求参数名
/// </summary>
public string RequestParamsName { get; set; } = string.Empty;
/// <summary>
/// 请求参数数据JSON
/// </summary>
public string RequestParamsData { get; set; } = string.Empty;
/// <summary>
/// 请求响应间隔时间
/// </summary>
public string ResponseIntervalTime { get; set; } = string.Empty;
/// <summary>
/// 响应时间
/// </summary>
public string ResponseTime { get; set; } = string.Empty;
/// <summary>
/// 响应结果
/// </summary>
public string ResponseJsonData { get; set; } = string.Empty;
}
/// <summary>
/// 拦截器AOP 继承IInterceptor接口
/// </summary>
public class ServiceAOP : IInterceptor
{
/// <summary>
/// 实例化IInterceptor唯一方法
/// </summary>
/// <param name="invocation">包含被拦截方法的信息</param>
public void Intercept(IInvocation invocation)
{
string json;
try
{
json = JsonConvert.SerializeObject(invocation.Arguments);
}
catch (Exception ex)
{
json = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString();
}
DateTime startTime = DateTime.Now;
AOPLogInfo apiLogAopInfo = new AOPLogInfo
{
RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"),
OpUserName = "",
RequestMethodName = invocation.Method.Name,
RequestParamsName = string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()),
ResponseJsonData = json
};
try
{
//在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的
invocation.Proceed();
// 异步获取异常,先执行
if (IsAsyncMethod(invocation.Method))
{
//Wait task execution and modify return value
if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task)invocation.ReturnValue,
async () => await SuccessAction(invocation, apiLogAopInfo, startTime), /*成功时执行*/
ex =>
{
LogEx(ex, apiLogAopInfo);
});
}
//Task<TResult>
else
{
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[0],
invocation.ReturnValue,
async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o), /*成功时执行*/
ex =>
{
LogEx(ex, apiLogAopInfo);
});
}
}
else
{
// 同步1
string jsonResult;
try
{
jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue);
}
catch (Exception ex)
{
jsonResult = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString();
}
DateTime endTime = DateTime.Now;
string ResponseTime = (endTime - startTime).Milliseconds.ToString();
apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff");
apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms";
apiLogAopInfo.ResponseJsonData = jsonResult;
Console.WriteLine(JsonConvert.SerializeObject(apiLogAopInfo));
}
}
catch (Exception ex)
{
LogEx(ex, apiLogAopInfo);
throw;
}
}
private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, object o = null)
{
DateTime endTime = DateTime.Now;
string ResponseTime = (endTime - startTime).Milliseconds.ToString();
apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff");
apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms";
apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(o);
await Task.Run(() =>
{
Console.WriteLine("执行成功-->" + JsonConvert.SerializeObject(apiLogAopInfo));
});
}
private void LogEx(Exception ex, AOPLogInfo dataIntercept)
{
if (ex != null)
{
Console.WriteLine("error!!!:" + ex.Message + JsonConvert.SerializeObject(dataIntercept));
}
}
public static bool IsAsyncMethod(MethodInfo method)
{
return
method.ReturnType == typeof(Task) ||
method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
}
}
internal static class InternalAsyncHelper
{
public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
try
{
await actualReturnValue;
await postAction();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
finalAction(exception);
}
}
public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<object, Task> postAction,
Action<Exception> finalAction)
{
Exception exception = null;
try
{
var result = await actualReturnValue;
await postAction(result);
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue,
Func<object, Task> action, Action<Exception> finalAction)
{
return typeof(InternalAsyncHelper)
.GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(taskReturnType)
.Invoke(null, new object[] { actualReturnValue, action, finalAction });
}
}
8.3Autofac注入
只在服务层(Service)注入日志。
var aopTypes = new List<Type>() { typeof(ServiceAOP) };
builder.RegisterType<ServiceAOP>();
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>))
.InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(BaseServices<,>)).As(typeof(IBaseServices<,>))
.EnableInterfaceInterceptors()
.InterceptedBy(aopTypes.ToArray())
.InstancePerDependency(); //注册服务
// 获取 Service.dll 程序集服务,并注册
var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces()
.InstancePerDependency()
.PropertiesAutowired()
.EnableInterfaceInterceptors()
.InterceptedBy(aopTypes.ToArray());
8.4实现
日志成功打出。
9.Appseting单例类获取配置
AspNetCore五大接口对象:
- ILogger:日志。
- IServiceCollection:IOC。
- IOptions:选项。
- IConfiguration:配置。
- Middleware:中间件。
弊端:硬编码,重构会有很大影响。比如大小写。
9.1安装
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
9.2实现AppSettings
public class AppSettings
{
public static IConfiguration Configuration { get; set; }
static string contentPath { get; set; }
public AppSettings(string contentPath)
{
string Path = "appsettings.json";
//如果你把配置文件 是 根据环境变量来分开了,可以这样写
//Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json";
Configuration = new ConfigurationBuilder()
.SetBasePath(contentPath)
.Add(new JsonConfigurationSource
{
Path = Path,
Optional = false,
ReloadOnChange = true
}) //这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性
.Build();
}
public AppSettings(IConfiguration configuration)
{
Configuration = configuration;
}
/// <summary>
/// 封装要操作的字符
/// </summary>
/// <param name="sections">节点配置</param>
/// <returns></returns>
public static string app(params string[] sections)
{
try
{
if (sections.Any())
{
return Configuration[string.Join(":", sections)];
}
}
catch (Exception)
{
}
return "";
}
/// <summary>
/// 递归获取配置信息数组
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sections"></param>
/// <returns></returns>
public static List<T> app<T>(params string[] sections)
{
List<T> list = new List<T>();
// 引用 Microsoft.Extensions.Configuration.Binder 包
Configuration.Bind(string.Join(":", sections), list);
return list;
}
/// <summary>
/// 根据路径 configuration["App:Name"];
/// </summary>
/// <param name="sectionsPath"></param>
/// <returns></returns>
public static string GetValue(string sectionsPath)
{
try
{
return Configuration[sectionsPath];
}
catch (Exception)
{
}
return "";
}
}
9.3注入
builder.Services.AddSingleton(new AppSettings(builder.Configuration));
9.4使用
var redisEnable = AppSettings.app(new string[] { "Redis", "Enable" });
var redisConnectionString = AppSettings.GetValue("Redis:ConnectionString");
Console.WriteLine($"Enable: {redisEnable} , ConnectionString: {redisConnectionString}");
配置文件:
10.IOptions
10.1安装
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
10.2配置类
IConfigurableOptions是为了约束,只有继承IConfigurableOptions才被注入到ServiceCollection中。
public interface IConfigurableOptions
{
}
/// <summary>
/// Redis缓存配置选项
/// </summary>
public sealed class RedisOptions : IConfigurableOptions
{
/// <summary>
/// 是否启用
/// </summary>
public bool Enable { get; set; }
/// <summary>
/// Redis连接
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// 键值前缀
/// </summary>
public string InstanceName { get; set; }
}
10.3实现
将实现IConfigurableOptions接口的配置类注入到ServiceCollection中。
public static class AllOptionRegister
{
public static void AddAllOptionRegister(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
foreach (var optionType in typeof(ConfigurableOptions).Assembly.GetTypes().Where(s =>
!s.IsInterface && typeof(IConfigurableOptions).IsAssignableFrom(s)))
{
services.AddConfigurableOptions(optionType);
}
}
}
public static class ConfigurableOptions
{
internal static IConfiguration Configuration;
public static void ConfigureApplication(this IConfiguration configuration)
{
Configuration = configuration;
}
/// <summary>添加选项配置</summary>
/// <typeparam name="TOptions">选项类型</typeparam>
/// <param name="services">服务集合</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddConfigurableOptions<TOptions>(this IServiceCollection services)
where TOptions : class, IConfigurableOptions
{
Type optionsType = typeof(TOptions);
string path = GetConfigurationPath(optionsType);
services.Configure<TOptions>(Configuration.GetSection(path));
return services;
}
public static IServiceCollection AddConfigurableOptions(this IServiceCollection services, Type type)
{
string path = GetConfigurationPath(type);
var config = Configuration.GetSection(path);
Type iOptionsChangeTokenSource = typeof(IOptionsChangeTokenSource<>);
Type iConfigureOptions = typeof(IConfigureOptions<>);
Type configurationChangeTokenSource = typeof(ConfigurationChangeTokenSource<>);
Type namedConfigureFromConfigurationOptions = typeof(NamedConfigureFromConfigurationOptions<>);
iOptionsChangeTokenSource = iOptionsChangeTokenSource.MakeGenericType(type);
iConfigureOptions = iConfigureOptions.MakeGenericType(type);
configurationChangeTokenSource = configurationChangeTokenSource.MakeGenericType(type);
namedConfigureFromConfigurationOptions = namedConfigureFromConfigurationOptions.MakeGenericType(type);
services.AddOptions();
services.AddSingleton(iOptionsChangeTokenSource,
Activator.CreateInstance(configurationChangeTokenSource, Options.DefaultName, config) ?? throw new InvalidOperationException());
return services.AddSingleton(iConfigureOptions,
Activator.CreateInstance(namedConfigureFromConfigurationOptions, Options.DefaultName, config) ?? throw new InvalidOperationException());
}
/// <summary>获取配置路径</summary>
/// <param name="optionsType">选项类型</param>
/// <returns></returns>
public static string GetConfigurationPath(Type optionsType)
{
var endPath = new[] { "Option", "Options" };
var configurationPath = optionsType.Name;
foreach (var s in endPath)
{
if (configurationPath.EndsWith(s))
{
return configurationPath[..^s.Length];
}
}
return configurationPath;
}
}
10.4注入
这俩种方式都可以
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule<AutofacModuleRegister>();
builder.RegisterModule<AutofacPropertityModuleReg>();
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
hostingContext.Configuration.ConfigureApplication();
});
// 配置
//ConfigurableOptions.ConfigureApplication(builder.Configuration);
builder.Services.AddAllOptionRegister();
10.5使用
var redisOptions = _redisOptions.Value;
11.非依赖注入管道中获取所有服务
11.1.安装Serilog
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
11.2实现
RuntimeExtension:获取项目程序集
public static class RuntimeExtension
{
/// <summary>
/// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包
/// </summary>
/// <returns></returns>
public static IList<Assembly> GetAllAssemblies()
{
var list = new List<Assembly>();
var deps = DependencyContext.Default;
//只加载项目中的程序集
var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type == "project"); //排除所有的系统程序集、Nuget下载包
foreach (var lib in libs)
{
try
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
list.Add(assembly);
}
catch (Exception e)
{
Log.Debug(e, "GetAllAssemblies Exception:{ex}", e.Message);
}
}
return list;
}
public static Assembly GetAssembly(string assemblyName)
{
return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName));
}
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;
}
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;
}
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;
});
}
}
InternalApp:内部只用于初始化使用,获取IServiceCollection、IServiceProvider、IConfiguration等几个重要的内部对象
public static class InternalApp
{
internal static IServiceCollection InternalServices;
/// <summary>根服务</summary>
internal static IServiceProvider RootServices;
/// <summary>获取Web主机环境</summary>
internal static IWebHostEnvironment WebHostEnvironment;
/// <summary>获取泛型主机环境</summary>
internal static IHostEnvironment HostEnvironment;
/// <summary>配置对象</summary>
internal static IConfiguration Configuration;
public static void ConfigureApplication(this WebApplicationBuilder wab)
{
HostEnvironment = wab.Environment;
WebHostEnvironment = wab.Environment;
InternalServices = wab.Services;
}
public static void ConfigureApplication(this IConfiguration configuration)
{
Configuration = configuration;
}
public static void ConfigureApplication(this IHost app)
{
RootServices = app.Services;
}
}
App:实现非注入形式获取Service、Options
public class App
{
static App()
{
EffectiveTypes = Assemblies.SelectMany(GetTypes);
}
private static bool _isRun;
/// <summary>是否正在运行</summary>
public static bool IsBuild { get; set; }
public static bool IsRun
{
get => _isRun;
set => _isRun = IsBuild = value;
}
/// <summary>应用有效程序集</summary>
public static readonly IEnumerable<Assembly> Assemblies = RuntimeExtension.GetAllAssemblies();
/// <summary>有效程序集类型</summary>
public static readonly IEnumerable<Type> EffectiveTypes;
/// <summary>优先使用App.GetService()手动获取服务</summary>
public static IServiceProvider RootServices => IsRun || IsBuild ? InternalApp.RootServices : null;
/// <summary>获取Web主机环境,如,是否是开发环境,生产环境等</summary>
public static IWebHostEnvironment WebHostEnvironment => InternalApp.WebHostEnvironment;
/// <summary>获取泛型主机环境,如,是否是开发环境,生产环境等</summary>
public static IHostEnvironment HostEnvironment => InternalApp.HostEnvironment;
/// <summary>全局配置选项</summary>
public static IConfiguration Configuration => InternalApp.Configuration;
/// <summary>
/// 获取请求上下文
/// </summary>
public static HttpContext HttpContext => RootServices?.GetService<IHttpContextAccessor>()?.HttpContext;
//public static IUser User => GetService<IUser>();
#region Service
/// <summary>解析服务提供器</summary>
/// <param name="serviceType"></param>
/// <param name="mustBuild"></param>
/// <param name="throwException"></param>
/// <returns></returns>
public static IServiceProvider GetServiceProvider(Type serviceType, bool mustBuild = false, bool throwException = true)
{
if (HostEnvironment == null || RootServices != null &&
InternalApp.InternalServices
.Where(u =>
u.ServiceType ==
(serviceType.IsGenericType ? serviceType.GetGenericTypeDefinition() : serviceType))
.Any(u => u.Lifetime == ServiceLifetime.Singleton))
return RootServices;
//获取请求生存周期的服务
if (HttpContext?.RequestServices != null)
return HttpContext.RequestServices;
if (RootServices != null)
{
IServiceScope scope = RootServices.CreateScope();
return scope.ServiceProvider;
}
if (mustBuild)
{
if (throwException)
{
throw new ApplicationException("当前不可用,必须要等到 WebApplication Build后");
}
return default;
}
ServiceProvider serviceProvider = InternalApp.InternalServices.BuildServiceProvider();
return serviceProvider;
}
public static TService GetService<TService>(bool mustBuild = true) where TService : class =>
GetService(typeof(TService), null, mustBuild) as TService;
/// <summary>获取请求生存周期的服务</summary>
/// <typeparam name="TService"></typeparam>
/// <param name="serviceProvider"></param>
/// <param name="mustBuild"></param>
/// <returns></returns>
public static TService GetService<TService>(IServiceProvider serviceProvider, bool mustBuild = true)
where TService : class => (serviceProvider ?? GetServiceProvider(typeof(TService), mustBuild, false))?.GetService<TService>();
/// <summary>获取请求生存周期的服务</summary>
/// <param name="type"></param>
/// <param name="serviceProvider"></param>
/// <param name="mustBuild"></param>
/// <returns></returns>
public static object GetService(Type type, IServiceProvider serviceProvider = null, bool mustBuild = true) =>
(serviceProvider ?? GetServiceProvider(type, mustBuild, false))?.GetService(type);
#endregion
#region private
/// <summary>加载程序集中的所有类型</summary>
/// <param name="ass"></param>
/// <returns></returns>
private static IEnumerable<Type> GetTypes(Assembly ass)
{
Type[] source = Array.Empty<Type>();
try
{
source = ass.GetTypes();
}
catch
{
Console.WriteLine($@"Error load `{ass.FullName}` assembly.");
}
return source.Where(u => u.IsPublic);
}
#endregion
#region Options
/// <summary>获取配置</summary>
/// <typeparam name="TOptions">强类型选项类</typeparam>
/// <returns>TOptions</returns>
public static TOptions GetConfig<TOptions>()
where TOptions : class, IConfigurableOptions
{
TOptions instance = Configuration
.GetSection(ConfigurableOptions.GetConfigurationPath(typeof(TOptions)))
.Get<TOptions>();
return instance;
}
/// <summary>获取选项</summary>
/// <typeparam name="TOptions">强类型选项类</typeparam>
/// <param name="serviceProvider"></param>
/// <returns>TOptions</returns>
public static TOptions GetOptions<TOptions>(IServiceProvider serviceProvider = null) where TOptions : class, new()
{
IOptions<TOptions> service = GetService<IOptions<TOptions>>(serviceProvider ?? RootServices, false);
return service?.Value;
}
/// <summary>获取选项</summary>
/// <typeparam name="TOptions">强类型选项类</typeparam>
/// <param name="serviceProvider"></param>
/// <returns>TOptions</returns>
public static TOptions GetOptionsMonitor<TOptions>(IServiceProvider serviceProvider = null)
where TOptions : class, new()
{
IOptionsMonitor<TOptions> service =
GetService<IOptionsMonitor<TOptions>>(serviceProvider ?? RootServices, false);
return service?.CurrentValue;
}
/// <summary>获取选项</summary>
/// <typeparam name="TOptions">强类型选项类</typeparam>
/// <param name="serviceProvider"></param>
/// <returns>TOptions</returns>
public static TOptions GetOptionsSnapshot<TOptions>(IServiceProvider serviceProvider = null)
where TOptions : class, new()
{
IOptionsSnapshot<TOptions> service = GetService<IOptionsSnapshot<TOptions>>(serviceProvider, false);
return service?.Value;
}
#endregion
}
IConfiguration对象从App中获取
ApplicationSetup:通过事件获取WebApplication的状态。
public static class ApplicationSetup
{
public static void UseApplicationSetup(this WebApplication app)
{
app.Lifetime.ApplicationStarted.Register(() =>
{
App.IsRun = true;
});
app.Lifetime.ApplicationStopped.Register(() =>
{
App.IsRun = false;
//清除日志
Log.CloseAndFlush();
});
}
}
11.3注入
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule<AutofacModuleRegister>();
builder.RegisterModule<AutofacPropertityModuleReg>();
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
hostingContext.Configuration.ConfigureApplication();
});
//配置
builder.ConfigureApplication();
app.ConfigureApplication();
app.UseApplicationSetup();
11.4使用
[HttpGet(Name = "GetUserInfo")]
public async Task<object> GetUserInfo()
{
var userServiceObjNew = App.GetService<IBaseServices<UserInfo, UserInfoVo>>(false);
var redisOptions = App.GetOptions<RedisOptions>();
await Console.Out.WriteLineAsync(JsonConvert.SerializeObject(redisOptions));
return await userServiceObjNew.Query();
}
12.ControllerAsServices属性注入
IControllerActivator的默认实现不是ServiceBasedControllerActivator,而是DefaultControllerActivator。
控制器本身不是由依赖注入容器生成的,只不过是构造函数里的依赖是从容器里拿出来的,控制器不是容器生成的,所以他的属性也不是容器生成的。为了改变默认实现DefaultControllerActivator,所以使用ServiceBasedControllerActivator。
IControllerActivator源码地址:https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Controllers/IControllerActivator.cs
IControllerActivator 就俩个方法Create和Release。
查看DefaultControllerActivator 和ServiceBasedControllerActivator源码发现:
DefaultControllerActivator是由ITypeActivatorCache.CreateInstance创建对象。
ServiceBasedControllerActivator是由actionContext.HttpContext.RequestServices创建对象。
通过改变Controllers的创建方式来实现属性注入,将Controller的创建都由容器容器创建。以下俩种方式都是由容器创建Controller。
// 属性注入
//builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
// Add services to the container.
builder.Services.AddControllers().AddControllersAsServices();
Controller由容器创建完成,所以他的属性也是容器创建的,就可以实现属性注入。
属性修饰词必须是public
13.Redis分布式缓存
13.1安装Redis包
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
<PackageReference Include="StackExchange.Redis" Version="2.7.10" />
13.2Redis实现
这里不贴代码了 框架里都有
13.3IDistributedCache实现
IDistributedCache源码,IDistributedCache是微软官方提供的缓存接口标准。
实现ICaching,将IDistributedCache传进去
13.4注入
如果开启redis缓存,实现RedisCacheImpl
如果开启内存缓存,实现MemoryDistributedCache
根据配置决定IDistributedCache的实现。
注入
13.5使用
[HttpGet(Name = "GetUserInfo")]
public async Task<object> GetUserInfo()
{
var cacheKey = "peng";
List<string> cacheKeys = await _caching.GetAllCacheKeysAsync();
await Console.Out.WriteLineAsync("全部keys -->" + JsonConvert.SerializeObject(cacheKeys));
await Console.Out.WriteLineAsync("添加一个缓存");
await _caching.SetStringAsync(cacheKey, "pengpeng");
await Console.Out.WriteLineAsync("全部keys -->" + JsonConvert.SerializeObject(await _caching.GetAllCacheKeysAsync()));
await Console.Out.WriteLineAsync("当前key内容-->" + JsonConvert.SerializeObject(await _caching.GetStringAsync(cacheKey)));
await Console.Out.WriteLineAsync("删除key");
await _caching.RemoveAsync(cacheKey);
await Console.Out.WriteLineAsync("全部keys -->" + JsonConvert.SerializeObject(await _caching.GetAllCacheKeysAsync()));
return "";
}
14.ORM
- 1、CURD + Page
- 2、多表联查
- 3、字段级别操作
- 4、国产数据库
- 5、事务处理
- 6、多库操作
- 7、多租户/数据权限
- 8、分库分表操作
- 9、读写分离/从库
- 10、灾备数据库
- 11、调试SQL与日志记录
15.SqlSuger入门
15.1SqlSuger包安装
<ItemGroup>
<PackageReference Include="SqlSugarCore" Version="5.1.4.145" />
</ItemGroup>
15.2SqlSuger实现
注入:
/// <summary>
/// SqlSugar 启动服务
/// </summary>
public static class SqlsugarSetup
{
public static void AddSqlsugarSetup(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
// 默认添加主数据库连接
if (!string.IsNullOrEmpty(AppSettings.app("MainDB")))
{
MainDb.CurrentDbConnId = AppSettings.app("MainDB");
}
BaseDBConfig.MutiConnectionString.allDbs.ForEach(m =>
{
var config = new ConnectionConfig()
{
ConfigId = m.ConnId.ObjToString().ToLower(),
ConnectionString = m.Connection,
DbType = (DbType)m.DbType,
IsAutoCloseConnection = true,
MoreSettings = new ConnMoreSettings()
{
IsAutoRemoveDataCache = true,
SqlServerCodeFirstNvarchar = true,
},
InitKeyType = InitKeyType.Attribute
};
if (SqlSugarConst.LogConfigId.ToLower().Equals(m.ConnId.ToLower()))
{
BaseDBConfig.LogConfig = config;
}
else
{
BaseDBConfig.ValidConfig.Add(config);
}
BaseDBConfig.AllConfigs.Add(config);
});
if (BaseDBConfig.LogConfig is null)
{
throw new ApplicationException("未配置Log库连接");
}
// SqlSugarScope是线程安全,可使用单例注入
// 参考:https://www.donet5.com/Home/Doc?typeId=1181
services.AddSingleton<ISqlSugarClient>(o =>
{
return new SqlSugarScope(BaseDBConfig.AllConfigs);
});
}
}
// Sqlsugar ORM
builder.Services.AddSqlsugarSetup();
15.3BaseRepository
ISqlSugarClient只读,外部只可获取,不可修改
15.4BaseServices
15.5使用
属性注入
16.SqlSuger事务简单用法
16.1实现IUnitOfWorkManage
实现IUnitOfWorkManage,通过依赖注入ISqlSugarClient获取实例实现事务
16.2BeginTran
标准的开启、提交、回滚事物的写法
16.3UnitOfWork
通过析构函数实现事务,此时不需要回滚事务。
当Dispose时,会自动回滚事务
17.SqlSuger事务高级用法
动态代理实现事务
事务传播方式
如果有一个[UseTran(Propagation = Propagation.Required)]就开启事务
ConcurrentStack
第一个方法进来后开启事务,将方法加入队列。多个方法加入,只会在第一次开启事务。
执行完一个事务后在执行下一个事务。
当所有方法执行完成后,执行After。
获取栈中第一个方法。
然后提交事务。
如果异常就回滚事务。
最后再从栈中第一个方法开始全部移除,直到删除完成。
如果异常就直接回滚。
移除所有方法并回滚。
18.SqlSuger多库操作
Tenant指定数据库配置,不区分大小写。
SugarTable指定数据库表明。
ISqlSugarClient根据类上配置的数据库和表名获取数据库实例,从而实现多库。
建议使用GetConnectionScope,是线程安全的。
对内_db,对外_Db,_dbBase默认主库
19.SqlSuger分库分表
设置分表策略,这里是按月分表。
SplitField,设置分表字段,根据创建时间来分表。
仓储和服务实现分表添加和查询。
分表查询和添加。
20.授权认证[Authorize]入门
1、理解[Authorize]特性
2、JWT组成和安全设计
3、Claims声明和安全设计
4、HttpContext上下文的处理
5、基于Role、Claims的授权
6、基于Requirement的复杂授权
7、分布式微服务下的统一授权
8、认证中心的设计、单点登录
9、微前端 + 微服务的门户网站设计
10、其他应用技巧(数据权限、租户等)
在Controller加上特性[Authorize]
访问接口时会报错,因为你选择加上认证特性,需要指定认证方案。
包安装:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
</ItemGroup>
在Program加上认证方式。
// JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
ValidateIssuerSigningKey = true, //是否验证SecurityKey
ValidIssuer = "Peng.Core", //发行人 //Issuer,这两项和前面签发jwt的设置一致
ValidAudience = "wr", //订阅人
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sdfsdfsrty45634kkhllghtdgdfss345t678fs"))//拿到SecurityKey
};
});
21.基于源码分析Claims声明
21.1源码分析
app.UseAuthorization():开启授权
AuthorizationMiddlewareInternal:使用授权中间件
AuthorizationMiddleware:在授权中间件中添加策略
AuthorizationPolicyBuilder:通过角色授权
RolesAuthorizationRequirement:在请求上下文中获取角色信息
21.2权限控制
只允许SuperAdmin角色访问
21.3基于Claim和Role授权
21.4Claims声明产生
注入请求上下文服务
从请求上下文获取Claims
22.基于源码分析策略授权
22.1源码分析
AuthorizationMiddleware:授权中间件。
将所有策略添加到IList
AuthorizationPolicyBuilder:所以自定策略需要实现IAuthorizationRequirement接口。
IPolicyEvaluator:处理授权。
IPolicyEvaluator:AuthenticateAsync处理策略授权。
AuthorizationPolicyBuilder:Claim声明授权。
AuthorizationPolicyBuilder:角色授权。
Claim声明授权和角色授权都实现了AuthorizationHandler、IAuthorizationRequirement
22.2自定义策略授权
看上边的源码分析实现Claim声明授权和角色授权都实现了AuthorizationHandler、IAuthorizationRequirement。
所以自定策略授权也需要实现AuthorizationHandler、IAuthorizationRequirement。
在Program注入。
Controller加上特性自定义策略。
23.复杂策略授权
实现: AuthorizationHandler
获取角色所有菜单权限。
或者当接口没有权限信息时去读取数据库进行初始化
判断登录状态,如果登录判断token是否失效,失效就重新登录。
判断角色权限
获取token
24.微服务鉴权
- 分布式微服务下的统一鉴权
- 第一种服务实例少,内部鉴权,基于角色。
- 第二种服务实例多,需要管理接口,需要添加授权服务。
- 认证中心、单点登录:
- 和其他服务同级域名(一级域名)
- 携带SSO,在目标域名解析SSO,实现单点登录
- 微服务、微前端:
- 其他应用(数据权限、租户等)
25.数据权限-字段多租户
25.1HttpContextAccessor获取用户信息
定义IUser接口并且实现
AspNetUser从HttpContext请求获取用户信息
IUser注入
25.2SqlSuger实现字段多租户
全局配置User信息
将租户字段配置为查询过滤条件
TenantId == 0,代表公共数据,所有人可见。
SqlSugar配置数据权限
增加一个数据权限表,所有租户的表都要继承ITenantEntity
配置实体映射
多租户测试
26.数据权限-分表多租户
数据权限比菜单权限更细致。
分表多租户的表需要加上MultiTenant特性
租户隔离方案
获取Peng.Net8.Model命名空间下所有实体
筛选出来有MultiTenant特性并且是表隔离的。
db.MappingTables.Add:将数据库表明中TableName换成了TableName_TenantId。
分表多租户测试
27.数据权限-分库多租户
约定大于配置。
系统租户表。
DbType是数据库类型,这里是SqlLite。
Connection是数据库链接配置。
分表多租户数据表的数据库格式是TableName_Id(表名_租户ID)。
增加系统租户表,增加库隔离的隔离方案枚举。
读取系统租户表的租户配置,将租户配置的数据库链接添加到SqlSugar。
添加多租户的业务表实体和实体视图,并配置实体映射。
分库多租户测试
注意需要配置的地方:
- SysUserInfo表的登录用户的TenantId,将这个Id配置到SysTenant表的Id
- SysTenant数据库链接,因为是SqlLite本地数据库,注意地址。
测试
28.SqlSugar日志和缓存
28.1SqlSugar日志
实现SqlSugarAop,面向切面思想。
配置到SqlSugar。
使用登录接口,日志已经打印出来了。
28.2SqlSugar缓存
实现SqlSugarCacheService,继承SqlSugar的ICacheService。
开启缓存。
BaseRepository实现QueryWithCache缓存查询。
优化系统租户表查询,如果表没有更新会查询缓存。
BaseServices实现QueryWithCache缓存查询。
测试缓存查询
需要注意的是如果手动更改数据库数据,缓存不会更新。
缓存只有代码增删改的时候才会更新缓存。
标签:实战,string,builder,AspNetCore8.0,static,var,new,public From: https://www.cnblogs.com/pengboke/p/18075835