https://source.dot.net/
依赖注入框架(核心对象):
IOC(控制反转)容器
- 映射依赖,注册类型,注册服务
- 实例解析
举例:5岁小朋友需要吃食物
两种:
主动 自己动手 正转
被动,找父母 反转 对食物的控制权
需要类型的实例
依赖注入,是实现控制反转的一种方式
通过 依赖注入 实现控制反转
父母(第三方)食物(依赖)那给你(注入)
抽象==接口=服务
发消息(接口)=短信
发消息(接口)=微信
工厂模式==IOC
.net core 基础类库 扩展类库
Asp.net core web开发平台
.netcore 原生DI
需要引入
microsoft.extensions.dependencyinjection
microsoft.extensions.dependencyinjection.abstractions
有两个的原因 是设计上的 抽象和实现的分离 基础类型在abstractions
如果自己要做扩展,最小依赖原则,只需要引入:microsoft.extensions.dependencyinjection.abstractions
其他的中间件 类似的做法
我们添加的对象是保存在ServiceCollection 对象中 本质是个集合
添加后返回的是一个 ServiceProvider 称之为是服务提供对象
var provider = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddScoped<IMessage, Message>()
.AddSingleton<ITool, Tool>()
.AddTransient Account>()//也可以这样
.BuildServiceProvider();
provider 代表依赖注入的容器对象
一般来注册都是依赖接口的方式
Debug.Assert(provider.GetService<ITool>() is Tool);
GetService 只能获得一个对象
GetServices 可以一次获得多个
var services = new ServiceCollection()
.AddTransient<Base, Account>()
.AddTransient<Base, Message>()
.AddTransient<Base, Tool>()
.BuildServiceProvider()
.GetServices<Base>().ToList();
Debug.Assert(services.OfType<Account>().Any());
Debug.Assert(services.OfType<Message>().Any());
单例的服务实例-保存在根容器
作用域模式 当前容器
瞬时 不保存
可以有子容器
谁负责创建 谁负责销毁
using (var root = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddScoped<IMessage, Message>()
.AddSingleton<ITool, Tool>()
.BuildServiceProvider())
{
using (var scope = root.CreateScope())
{
var child = scope.ServiceProvider;
child.GetService<IAccount>();
child.GetService<IMessage>();
child.GetService<ITool>();
Console.WriteLine("释放子容器");
}
Console.WriteLine("释放根容器");
}
作用域
public Test(IAccount account)
{
Console.WriteLine($"Ctor:Test(IAccount)");
}
public Test(IAccount account, IMessage message)
{
Console.WriteLine($"Ctor:Test(IAccount,IMessage)");
}
var test = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddTransient<IMessage, Message>()
.AddTransient<ITest, Test>()
.BuildServiceProvider()
.GetService<ITest>();
如果某个构造函数的参数能成为其他构造函数的超级,构造函数会选择,若是都有两个,都不能成为超级则 程序会不发选择,而异常
服务范围
容器==服务提供对象
子容器 只关系父容器
每个作用域对象都有一个明确边界
针对一个http请求
Application
Request
每一个请求针对一个子容器, 处理完成就会被释放
第三方
Autofac sctutor
最大的应该用就是项目的扩张性和灵活性
架构问题:
Autofac
Hosting 托管主机应用
服务集合--- 容器构建器-- 服务提供对象
Autofac
autofac.extensions.dependencyinjection
var serviceCollection = new ServiceCollection()
.AddTransient<IAccount, Account>()
.AddTransient<IMessage, Message>();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(serviceCollection);
containerBuilder.RegisterType<Tool>().As<ITool>();
var container = containerBuilder.Build();
IServiceProvider provider = new AutofacServiceProvider(container);
整合autofac 强大的注册功能
public void ConfigureContainer(ContainerBuilder builder)
{
var assembly = Assembly.GetExecutingAssembly();
// 程序集注册
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
// 筛选命名空间为 Services
.Where(t => t.Namespace == assembly.GetName().Name + ".Services")
// 暴露注册类型的接口
.AsImplementedInterfaces()
// 生命周期模式为Scope
.InstancePerLifetimeScope();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseServiceProviderFactory(new AutofacServiceProviderFactory());
扩展性
单例实例不用自己创建
Scrutor 。Net 内置 DI 的扩展包
性能高效
services.Scan(scan => scan
.FromAssemblyOf<Startup>()
.AddClasses(classes =>
classes.Where(t => t.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase)))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithScopedLifetime()
);
文件配置系统:
IFileProvider 抽象的文件系统 层次化目录结构
监控文件变化:
var provider = new ServiceCollection()
.AddSingleton<IFileProvider>(new PhysicalFileProvider(@"e:\其它"))
.AddSingleton<FileManager>()
.BuildServiceProvider();
var fileManager = provider.GetService<FileManager>();
fileManager.WatchAsync("Text.txt").Wait();
Console.Read();
public async Task WatchAsync(string path)
{
Console.WriteLine(await ReadAsync(path));
ChangeToken.OnChange(() => _fileProvider.Watch(path), async () =>
{
Console.Clear();
Console.WriteLine(await ReadAsync(path));
});
}
public async Task<string> ReadAsync(string path)
{
await using var stream = _fileProvider.GetFileInfo(path).CreateReadStream();
var buffer = new byte[stream.Length];
await stream.ReadAsync(buffer, 0, buffer.Length);
return Encoding.Default.GetString(buffer);
}
读取配置文件:
public class AppConfigDemo
{
public string Name { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
public static void Run()
{
var source = new Dictionary<string, string>
{
["Name"] = "AppConfig",
["StartDate"] = "2020年5月1日",
["EndDate"] = "2020年5月5日"
};
var options = new ConfigurationBuilder()
.AddInMemoryCollection(source)
.Build()
.Get<AppConfigDemo>();
Console.WriteLine($"Name:{options.Name}");
Console.WriteLine($"StartDate:{options.StartDate}");
Console.WriteLine($"EndDate:{options.EndDate}");
}
读取jeson
public class AppConfigDemo
{
public string Name { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
public Behavior Behavior { get; set; }
}
public class Behavior
{
public bool IsRead { get; set; }
public bool IsWrite { get; set; }
}
public static void Run()
{
var options = new ConfigurationBuilder()
.Add(new JsonConfigurationSource{Path = "appsettings.json"})
.Build()
.Get<AppConfigDemo>();
Console.WriteLine($"Name:{options.Name}");
Console.WriteLine($"StartDate:{options.StartDate}");
Console.WriteLine($"EndDate:{options.EndDate}");
Console.WriteLine($"Behavior.IsRead:{options.Behavior.IsRead}");
Console.WriteLine($"Behavior.IsWrite:{options.Behavior.IsWrite}");
}
动态加载文件变化
public class AppConfigDemo
{
public string Name { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
public Behavior Behavior { get; set; }
}
public class Behavior
{
public bool IsRead { get; set; }
public bool IsWrite { get; set; }
}
public static void Run()
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
Read(config.Get<AppConfigDemo>());
ChangeToken.OnChange(() => config.GetReloadToken(), () =>
{
Read(config.Get<AppConfigDemo>());
});
Console.Read();
}
public static void Read(AppConfigDemo options)
{
Console.Clear();
Console.WriteLine($"Name:{options.Name}");
Console.WriteLine($"StartDate:{options.StartDate}");
Console.WriteLine($"EndDate:{options.EndDate}");
Console.WriteLine($"Behavior.IsRead:{options.Behavior.IsRead}");
Console.WriteLine($"Behavior.IsWrite:{options.Behavior.IsWrite}");
}
}
读取xml
public class AppConfigDemo
{
public string Name { get; set; }
public string Ver { get; set; }
}
public static void Run()
{
Environment.SetEnvironmentVariable("APP_NAME", "AppDemo");
Environment.SetEnvironmentVariable("APP_VER", "Alpha");
var config = new ConfigurationBuilder()
.AddEnvironmentVariables("APP_")
.Build()
.Get<AppConfigDemo>();
Console.WriteLine($"Name:{config.Name}");
Console.WriteLine($"Ver:{config.Ver}");
}
}
Hosting 与管道
Hosting 托管主机
定义个托管服务
public class SystemClock : IHostedService
{
private Timer _timer;
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(state =>
{
Console.WriteLine($"Current Time:{DateTime.Now.ToLongTimeString()}");
}, null,0, 1000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
}
public static void Start()
{
var host = new HostBuilder()
.ConfigureServices(collection => collection
.AddHostedService<SystemClock>())
.Build();
host.Run();
}
}
托管服务所依赖的服务,也可以注册到 容器中
配置选项
var collector = new Collector();
var host = new HostBuilder()
.ConfigureAppConfiguration(builder => builder.AddJsonFile("appsettings.json"))
.ConfigureServices((context, collection) => collection
.AddSingleton<ITemperatureCollector>(collector)
.AddSingleton<IHumidityCollector>(collector)
.AddSingleton<IAirQualityCollector>(collector)
.AddHostedService<AirEnvironmentService>()
.AddOptions()
.Configure<AirEnvironmentOptions>(
context.Configuration.GetSection("AirEnvironment"))
).Build();
host.Run();
三个核心对象:
Ihostservice 启动或者停止 服务两个方法
Ihostbuilder Ihost
一个程序,只有一个主机对象
和程序一起启动或者停止
管道:
请求处理管道 ==http处理请求
var host = Host.CreateDefaultBuilder()
.ConfigureWebHost(builder => builder
.UseKestrel()
.Configure(app => //管道中间件
app.Run(context =>
context.Response.WriteAsync("Hello!"))
)
).Build();
host.Run();
ConfigureWebHost 自己定义 ConfigureWebHostDefault
请求处理管道 一个服务器(监听)一组中间件
请求消息 响应消息
Func 委托 requesstDelegate
requesstDelegate http 请求处理器
注册中间件 通过委托的方式进行
public static void Start()
{
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.Configure(app => app
.Use(Middleware1)
.Use(Middleware2)
)
).Build();
host.Run();
}
private static RequestDelegate Middleware1(RequestDelegate next)
{
async Task App(HttpContext context)
{
await context.Response.WriteAsync("Middleware 1 Begin.");
await next(context);
await context.Response.WriteAsync("Middleware 1 End.");
}
return App;
}
private static RequestDelegate Middleware2(RequestDelegate next)
{
async Task App(HttpContext context)
{
await context.Response.WriteAsync("Middleware 2 Begin.");
}
return App;
}
}
根据注册的顺序来执行,后面没有,就直接返回
注册强类中间件
public class HelloMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await context.Response.WriteAsync("Hello");
}
}
public static void Start()
{
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.ConfigureServices(collection => collection.AddSingleton<HelloMiddleware>())
.Configure(app => app
.UseMiddleware<HelloMiddleware>()
)
).Build();
host.Run();
}
}
基于约定来注册中间件
- 有效的公共构造函数(参数必须包含一个requestdelegate 参数 也可以包含其他类型的参数顺序无所谓)
- 必须有 invoke 或者invokeAsync 的方法
基于约定的中间件 框架会自己注册并且是以单例的方式进行注册
public class HelloMiddleware
{
private readonly RequestDelegate _next;
private readonly string _content;
private readonly bool _isToNext;
public HelloMiddleware(RequestDelegate next, string content, bool isToNext = false)
{
_next = next;
_content = content;
_isToNext = isToNext;
}
public async Task InvokeAsync(HttpContext httpContext)
{
await httpContext.Response.WriteAsync($"Hello {_content}!\r\n");
if (_isToNext) await _next(httpContext);
}
}
public static void Start()
{
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.Configure(app => app
.UseMiddleware<HelloMiddleware>("Rick", true)
.UseMiddleware<HelloMiddleware>("Motry")
)
).Build();
host.Run();
}
}
可以通过startup 类来完成服务和中间件的注册
经典的startup类
public class Startup
{
//可有可无,并且系统也会注册很多公共服务,系统的服务注册完成后,才会执行注册 个人自己写的服务
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
}
}
public void Strat()
{
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.UseStartup<Startup>())
.Build();
host.Run();
}
}
查看系统默认注册了哪些公共的服务的示例:
public void ConfigureServices(IServiceCollection services)
{
foreach (var service in services)
{
var serviceName = service.ServiceType.Name;
var implType = service.ImplementationType;
if (implType != null)
{
Console.WriteLine($"{service.Lifetime, -15}{serviceName,-40}{implType.Name}");
}
}
}
public void Configure(IApplicationBuilder app)
{
}
}
public static void Start()
{
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.UseStartup<Startup>())
.Build();
host.Run();
}
}
说明下: public Startup(IConfiguration configuration, IHostEnvironment hostingEnvironment)
{
Debug.Assert(configuration != null);
Debug.Assert(hostingEnvironment != null);
}
Startup 类中只能注册 公共的服务,和 执行顺序有关
中间件的注入,不仅支持构造函数注入,也支持特定的方法注入
InvokeAsync 可以
在基于约定的中间件中 最好不用构造函数注入
因为基于约定的中间件是单例方式注册,可能会导致其他服务一直不能被释放掉
放到InvokeAsync方法中 第一个 参数为HttpContext
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext,Test1 test1, Test2 test2)
{
Debug.Assert(test1 != null);
Debug.Assert(test2 != null);
await httpContext.Response.WriteAsync("Test");
}
在 mvc 项目中 注入主要用在两个位置,一个是控制器 构造函数注入,一个试图
工作流
项目实战
可以拆分成 微服务的架构来进行
使用 Dapper 来实现 JadeFramework
Automapper
Linux 和 docker 中有大小写区分 所以命令建议
类名
用小写 加 下划线
改造 一个模型 对应个 仓促 对应个service
Ares 对页面分组
多租户问题 目前没有处理
JWT
前后端分离
流程发起
流程审核
流程设计
通知 使用 singalR 框架 实时通信
短信 微信 邮件 电话
singalR messagehub sinagR
// 4、使用SignalR
services.AddSignalR().AddNewtonsoftJsonProtocol();
引包:
microsoft.aspnetcore.signalr.protocols.newtonsoftjson
创建 MessageHub 建立客户端链接
消息发送过程 1 发给 hub hun在发给2
SignalRMessageGroups 保存用户的信息
// 2、映射SignalR 连接中心
endpoints.MapHub<MessageHub>("/messageHub", options => options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransports.All);
MessageHubGroup 扩展
事件总线(消息总线) 就是通用的订阅发布系统
RuanMou.WorkFlow BLT.APS.Project
CREATE TABLE IF NOT EXISTS `oa_message_user` (
`Id` long NOT NULL COMMENT ‘主键‘,
`MessageId` long NOT NULL COMMENT ‘流程实例id‘,
`UserId` long NOT NULL COMMENT ‘当前节点id‘,
`IsRead` varchar(2) DEFAULT NULL COMMENT ‘节点名称‘,
)
ABP
特性:abp cli 模块化 都租户 认证授权
虚拟文件系统 主题 后台作业 事件总线
对象映射 依赖注入 数据过滤
单体和微服务都可以
手动创建一个 ABP
模块化 封装细节 提供接口
模块之间没有必然联系 互补影响
代码质量提升
多人协助 会不干扰
高内聚低耦合
模块分为两种:根据功能和用途分
应用程序模块:实现业务
框架模块:核心模块 通用功能
模块了负责管理整个模块的生命周期
模块配置 首先执行
模块初始化
模块销毁
模块间的依赖关系 决定模块的启动顺序
启动流程
模块的加载顺序:先加载依赖 在加载自己
拓扑排序算法 根据依赖关系
启动模块最后一个执行
- 注册abp基础设施与核心服务
- 加载整个应用的所有模块,执行每一个模块的配置方法(3个)
- 按顺序遍历所有模块,执行每个模块的初始化方法(3个)
ABP依赖注入两种方式:
手动注册
自动注册:
按照约定 (所有模块类,所有控制器)
按照依赖接口 (HelloWorldService:IScopedDependency)
按照依赖特性 // [Dependency(ServiceLifetime.Transient)]
// [ExposeServices(typeof(IHelloService))]
配置选项
DDD 领域模型
领域模型:实体(唯一并可以持续变化)和值对象
实体类(充血模型)
聚合
Domain 领域
仓储:用来协调领域(实体)和数据映射(数据模型ORM)
仓储=只能通过聚合根来持久化和数据检索
数据访问层
持久化方案