.net core 实战-读书笔记
06 作用域与对象释放行为
避免在根容器下创建实现IDisposable接口接口瞬时服务:这种对象创建后,只有在应用退出时,才会释放
避免手动创建实现了IDisposable对象,应该使用容器来管理其生命周期
07 用Autofac增强容器能力,引入面向切面编程的能力
第三方容器使用的场景
核心扩展点
public interface IServiceProviderFactory<TContainerBuilder>
Autofac增强功能:
- 名称的注入
- 属性注入
- 子容器
- 动态代理AOP
08丨配置框架:让服务无缝适应各种环境
核心扩展点
- IConfigurationSource
- IConfigurationProvider
09丨命令行配置提供程序:最简单快捷的配置注入方法
参数支持如下方式:
- 无前缀的key=value模式
- 双中横线模式 —key=value 或 —key value
- 正斜杠模式 /key=value 或 /key value
备注:等号分隔符和空格分隔符,只能二选一,不能混合使用
其中多种模式,可以混合使用,建立对象时,可以传入一个字典用于定义名称别名
10丨环境变量配置提供程序:容器环境下配置注入的最佳途径
适用场景:
- 在Docker中运行时
- 在Kubernaetes中运行时
- 需要设置ASP.Net Core内置的一些特殊配置时
分层符号:
双下划代替环境变量中的冒号 这具是环境变更中特殊点
支持环境变更前缘注入,如只注入:tmp“);
文件配置提供程序支持:ini,json,xml,UserSecrets,Newtonsoft,同时提供文件监视功能
IChangeToken,用于配置变更后,自动通知程序功能 同时获取Token只能使用一次,只能使用一次
使用ChangeToken方法OnChange来注册,就没有这种限制
配置根上面的方法,有Bind方法,可以进行类的绑定
- IOption
- 范围IOptionSnap
- 单例:IOptionMonitor
- 代码更新:IPostconfigureOptions
Serilog 日志组件
gRpc组件实践
全局安装命令:dotnet tool install dotnet-grpc -g
有如下四种调用方法:
- dotnet grpc add-file filepath
- dotnet grpc add-url fileurl -o outputfilepath
- dotnet grpc remove filepath/fileurl
- dotnet grpc refresh fileurl
最佳实践
- 使用单独的Git仓库管理proto文件
- 使用submodule将proto文件集成到工程目录中
- 使用dotnet-grpc命令行添加proto文件及相关依赖包引用
版本化管理协议文件,同时动态生成的代码不会签入到配置管理工具中
asp.net core集成
syntax = "proto3";
option csharp_namespace = "gRpcServer";
package gRpcServer
service OrderGrpc {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- asp.net core服务端集成
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://+:5000"
},
"Https": {
"Url": "https://+:5001"
},
"Http2": {
"Url": "http://+:5002",
"Protocols": "Http2"
}
}
//"Certificates": {
// "Default": {
// "Path": "cer.pfx",
// "Password": "123456"
// }
//}
}
public void ConfigureServices(IServiceCollection services)
{
//加入Grpc框架注入
services.AddGrpc(p => {
p.EnableDetailedErrors = false;
//p.Interceptors.Add<>();
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
//注入grpc服务
endpoints.MapGrpcService<OrderServices>();
endpoints.MapControllers();
});
}
- net core 客户端集成
private static string HttpInvoke()
{
var channel = GrpcChannel.ForAddress("http://localhost:5000");
var client = new OrderGrpcClient(channel);
var reply = client.SayHello(new HelloRequest { Name = "HttpInvoke" });
return reply.Message;
}
private static string Http2Invoke()
{
//启用http2走非加密通道
AppContext.SetSwitch(
"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var channel = GrpcChannel.ForAddress("http://localhost:5002");
var client = new OrderGrpcClient(channel);
var reply = client.SayHello(new HelloRequest { Name = "Http2Invoke" });
return reply.Message;
}
private static string HttpsInvoke()
{
var httpClientHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:5001"
, new GrpcChannelOptions { HttpClient = httpClient });
var client = new OrderGrpcClient(channel);
var reply = client.SayHello(new HelloRequest { Name = "HttpsInvoke" });
return reply.Message;
}
- asp.net core 客户端集成
//AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); //允许使用不加密的HTTP/2协议
services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
})
.ConfigurePrimaryHttpMessageHandler(provider =>
{
var handler = new SocketsHttpHandler();
handler.SslOptions.RemoteCertificateValidationCallback = (a, b, c, d) => true; //允许无效、或自签名证书
return handler;
}).AddTransientHttpErrorPolicy(p => p.WaitAndRetryForeverAsync(i => TimeSpan.FromSeconds(i * 3)));
调用:
OrderGrpcClient service = context.RequestServices.GetService<OrderGrpcClient>();
try {
var r = service.CreateOrder(new CreateOrderCommand { BuyerId = "abc" });
}
catch (Exception ex)
{
}
Polly的能力
- 失败重试:要求服务幂等调用
- 服务熔断(有状态):部分服务不可用服务,自动离线
- 超时处理:请求服务超时处理
- 舱壁隔离(有状态):服务限流功能
- 缓存策略(有状态):Aop嵌入缓存功能
- 失败降级:服务不可用,晌应友好的结果
- 组合策略:组合上面的策略
使用步骤
- 定义要处理的异常类型或返回值
- 定义要处理的运作(重试,熔断、降级响应等)
- 使用定义的策略来执行远程调用或业务代码
失败重试最佳实践
HttpRequestException 500 408 这三种情况才进行重试
- 设置失败重试次数
- 设置带有步长策略的失败等待间隔
- 设置降级响应
- 设置断路器
网关搭建
- 添加包Ocelot
- 添加配置文件 ocelot.json
- 添加配置读取代码
- 注册Ocelot服务
- 注册Ocelot中间件
cookie和jwt共存启用方法
在配置中,加入两种身价认证方法,如下所示:
public void ConfigureServices(IServiceCollection services)
{
#region
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); //允许使用不加密的HTTP/2协议
services.AddGrpcClient<GeekTime.Ordering.API.Grpc.OrderService.OrderServiceClient>(options =>
{
options.Address = new Uri(Configuration.GetValue<string>("ServiceUrls:OrderingAPI"));
}).ConfigurePrimaryHttpMessageHandler(provider =>
{
var handler = new SocketsHttpHandler();
handler.SslOptions.RemoteCertificateValidationCallback = (a, b, c, d) => true; //允许无效、或自签名证书
return handler;
});
services.AddHealthChecks();
services.AddHttpClient<IOrderService, OrderService>().ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri("https://localhost:5001");
});
//services.AddScoped<IOrderService, OrderService>();
services.AddHttpClient("myClient").ConfigureHttpClient(client =>
{
}).ConfigurePrimaryHttpMessageHandler(service =>
{
return new SocketsHttpHandler() { };
}).ConfigureHttpMessageHandlerBuilder(builder =>
{
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
options.ForwardedHeaders = ForwardedHeaders.All;
});
#endregion
var secrityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"]));
services.AddSingleton(secrityKey);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = "localhost",//Audience
ValidIssuer = "localhost",//Issuer
IssuerSigningKey = secrityKey//拿到SecurityKey
};
});
}
启用jwt验证
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Jwt()
{
return Content(User.FindFirst("Name").Value);
}
启用Cookie验证
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Cookie()
{
return Content(User.FindFirst("Name").Value);
}
两种验证同时启用
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme + "," + CookieAuthenticationDefaults.AuthenticationScheme
)]
public IActionResult Cookie()
{
return Content(User.FindFirst("Name").Value);
}
跨域请求启用步骤
- 注入跨域策略
services.AddCors(options =>
{
options.AddPolicy("api", builder =>
{
builder.WithOrigins("https://localhost:5001").AllowAnyHeader().AllowCredentials().WithExposedHeaders("abc");
builder.SetIsOriginAllowed(orgin => true).AllowCredentials().AllowAnyHeader();
});
});
- 启用跨域
app.UseRouting();
app.UseResponseCaching();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
- api上设置跨域策略
[Authorize]
[HttpPost]
[EnableCors("api")] //DisbaleCors
public object PostCors(string name)
{
return new { name = name + DateTime.Now.ToString() };
}