IHostedService
允许开发人员在后台创建多个任务(托管服务),并支持在web应用程序后台运行。生命周期为单例生命周期
需要引用Microsoft.Extensions.Hosting Nuget包
示例:
public class MyHostService : IHostedService
{
public MyHostService() { }
public async Task StartAsync(CancellationToken cancellationToken)
{
while (true)
{
await Task.Delay(5000);
await Console.Out.WriteLineAsync("hello word");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("结束");
return Task.CompletedTask;
}
}
var host = new HostBuilder().ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyHostService>();
});
await host.RunConsoleAsync();
可以实现实时监控后台数据的变化。
中间件(Middleware)
是一种从装配到应用管道以处理请求和响应的软件。
中间件是构建在管道之上的一层组件,多个中间件以责任链模式形成一种串行的顺序结构,每个中间件都会在其传递时进行处理并传递到下一个中间件,而响应内容则以相反的顺序返回。
自定义中间件
- 带有RequestDelegate参数的构造函数
- 一个返回Task类型和名为Invoke或InvokeAsync的方法,并且参数为HttpContext
public class CustomMiddleware
{
private readonly ILogger<CustomMiddleware> _logger;
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next, ILogger<CustomMiddleware> logger)
{
this._next = next;
this._logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogWarning($"Before Invoke {context.Request.Path}");
await _next(context);
_logger.LogWarning($"After Invor {context.Request.Path}");
}
}
制作简单的API统一响应格式与自动包装
项目中,需要统一对外访问的响应内容格式,这样开发人员就需要定义统一的结构化内容,但是如果每个方法都按照一个响应类进行包装,那么响应类的格式发生修改的时候,可能需要修改的地方有很多。因此,可以使用中间件的形式对所有的响应数据进行包装,无需关注返回的内容。对HTTP请求响应是将内容进行包装,生成统一的响应格式,并返回给客户端。
自定义响应数据格式(CustomApiResponse类)
public class CustomApiResponse
{
#region 属性
/// <summary>
/// 状态码
/// </summary>
public int Status { get; set; }
/// <summary>
/// 返回结果
/// </summary>
public object Result { get; set; }
/// <summary>
/// 请求的唯一编码
/// </summary>
public string RequestId { get; set; }
#endregion
internal CustomApiResponse(int status, object result, string requestId)
{
Status = status;
Result = result;
RequestId = requestId;
}
public static CustomApiResponse Create(int statusCode, object result, string requestId)
{
return new CustomApiResponse(statusCode, result, requestId);
}
}
自定义中间件(CustomMiddleware类)
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task InvokeAsync(HttpContext context)
{
//记录当前的stream信息
var originalResponseBodyStream = context.Response.Body;
using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
//把新建memoryStream传递给下一个中间件
await _next(context);
context.Response.Body = originalResponseBodyStream;
//对memoryStream中内存流指针进行重新定位,定位到开始的位置
memoryStream.Seek(0, SeekOrigin.Begin);
//对memoryStream返回拿到的数据进行读取
var readToEnd = await new StreamReader(memoryStream).ReadToEndAsync();
//string转成对象
var objResult = JsonConvert.DeserializeObject(readToEnd);
//创建我们自定义返回的类型格式
var result = CustomApiResponse.Create(context.Response.StatusCode, objResult,context.TraceIdentifier);
//将序列化后的参数写入HTTP的响应输出流中
await context.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
}
使用自定义的中间件
在Program.cs中,使用WebApplication的UseMiddleware方法添加自定义的中间件
var app = builder.Build();
//使用中间件
app.UseMiddleware<CustomMiddleware>();
示例返回的数据格式
{
"Status": 200,
"Result": [
{
"date": "2023-10-30T14:36:27.9761972+08:00",
"temperatureC": -3,
"temperatureF": 27,
"summary": "Mild"
},
{
"date": "2023-10-31T14:36:27.9764839+08:00",
"temperatureC": -11,
"temperatureF": 13,
"summary": "Chilly"
},
{
"date": "2023-11-01T14:36:27.9764854+08:00",
"temperatureC": 18,
"temperatureF": 64,
"summary": "Bracing"
},
{
"date": "2023-11-02T14:36:27.9764856+08:00",
"temperatureC": 29,
"temperatureF": 84,
"summary": "Freezing"
},
{
"date": "2023-11-03T14:36:27.9764857+08:00",
"temperatureC": 48,
"temperatureF": 118,
"summary": "Bracing"
}
],
"RequestId": "0HMUO95F213AK:00000005"
}
中间件常用的扩展方法
IApplicationBuilder的扩展方法Run方法
使用了Run中间件会返回相应的结果而且终止请求,不会在调用Run后面的中间件。
IApplicationBuilder的扩展方法Use方法
将中间件注册到管道中,不会终止请求。
IApplicationBuilder的扩展方法Map方法
指映射到路径上,并指定独立的执行管道。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/test", appMap =>
{
appMap.Run(async con =>
{
await con.Response.WriteAsync("test.Map方法");
});
});
IApplicationBuilder的扩展方法MapWhen方法
map方法主要是通过路由来判断是否进入分支管道,MapWhen是通过自己定义的判断条件来决定是否进入分支管道
public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration);
第二个参数Func<HttpContext, bool> predicate 用来判断是否进入分支管道
第三个参数Action configuration 添加到分支管道的中间件的Action委托
内存缓存
IMemoryCache
内存缓存的核心对象
Set扩展方法用来设置缓存
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value);
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options);
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, IChangeToken expirationToken);
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, DateTimeOffset absoluteExpiration);
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, TimeSpan absoluteExpirationRelativeToNow);
测试了简单的set方法
TryGetValue扩展方法来获取数值
返回bool值,判断但钱键是否存在
public static bool TryGetValue<TItem>(this IMemoryCache cache, object key, out TItem value);
Get扩展方法
可以实现获取没有的时候,新建一个键加入
public static object Get(this IMemoryCache cache, object key);
public static TItem Get<TItem>(this IMemoryCache cache, object key);
public static TItem GetOrCreate<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, TItem> factory);
public static Task<TItem> GetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory);
ICacheEntry
CacheEntry对象承载了缓存的特征,通过IMemoryCache 接口对象使用CreateEntry()方法
public interface ICacheEntry : IDisposable
{
//获取或设置对象的绝对过期日期。
DateTimeOffset? AbsoluteExpiration { get; set; }
//获取或设置一个有效期,从添加的时候开始计算,如设置20分钟失效
TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
//提供导致缓存内容过期的IChangeToken实例
IList<IChangeToken> ExpirationTokens { get; }
//存储对象的键
object Key { get; }
//获取或设置在缓存中失效后出发的回调
IList<PostEvictionCallbackRegistration> PostEvictionCallbacks { get; }
//获取或设置缓存项优先级。有四个等级
CacheItemPriority Priority { get; set; }
//获取或设置缓存的大小
long? Size { get; set; }
//获取或设置指定时间内不活动或不常访问的缓存失效策略
TimeSpan? SlidingExpiration { get; set; }
//缓存的值
object Value { get; set; }
}
MemoryCache对象
可以通过设置MemoryCacheOptions对象的SizeLimit属性来限制缓存的大小。如果启用了缓存大小限制,那么所有的条目都必须指定Size属性。
var cache = new ServiceCollection().AddMemoryCache(opt =>
{
opt.SizeLimit = 1;
}).BuildServiceProvider().GetRequiredService<IMemoryCache>();
IDistributedCache对象
分布式管理的核心对象,在Microsoft.Extensions.Caching.Distributed 工作集下。可以使用扩展方法Set和SetAsync来设置缓存,但是需要自己序列化成byte数组。也可以使用Setstring和SetStringAsync方法,节省序列化的操作
public interface IDistributedCache
{
byte[] Get(string key);
Task<byte[]> GetAsync(string key, CancellationToken token = default);
void Refresh(string key);
Task RefreshAsync(string key, CancellationToken token = default);
void Remove(string key);
Task RemoveAsync(string key, CancellationToken token = default);
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default);
}
- Get / GetAsync 用来获取缓存,返回一个byte类型的数组
- Set / SetAsync 用来设置或添加缓存
- Refresh / RefreshAsync 用来刷新缓存条目中最后访问时间,并将它设置为当前时间
- Remove / RemoveAsync 用来删除缓存
DistributedCacheEntryOptions类
分布式缓存可以通过DistributedCacheEntryOptions对象来指定相关的过滤策略,设置绝对过期时间和滑动时间。
public class DistributedCacheEntryOptions
{
public DistributedCacheEntryOptions();
public DateTimeOffset? AbsoluteExpiration { get; set; }
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
public TimeSpan? SlidingExpiration { get; set; }
}
基于Redis的分布式缓存
需要引用Microsoft.Extensions.Caching.StackExchangeRedis Nuget包
注册Redis服务
var cache = new ServiceCollection().AddStackExchangeRedisCache(opt =>
{
opt.Configuration = "localhost";//连接redis数据库的配置
}).BuildServiceProvider().GetRequiredService<IDistributedCache>();
cache.SetString("key1", "value1", new DistributedCacheEntryOptions()
{
AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(30),
SlidingExpiration = TimeSpan.FromSeconds(10)
});
HTTP缓存
有强缓存(本地缓存)和协商缓存(弱缓存),主要用于提高资源的获取速度,减少网络传输的开销,缓解服务端的压力。一般只有GET请求和HEAD请求可以缓存。
ResponseCachingMiddleware中间件
添加到服务器的请求管道中
builder.Services.AddResponseCaching(opt =>
{
opt.UseCaseSensitivePaths = true;
opt.MaximumBodySize = 1024;
});
var app = builder.Build();
app.UseResponseCaching();
ResponseCachingOptions属性
- SizeLimit : 缓存响应中间件缓存的大小。默认100M
- MaximumBodySize : 设置响应正文的最大值,默认64M
- UseCaseSensitivePaths : 标记缓存是否区分请求路径的大小写
ResponseCachingMiddleware中间件会用IMemoryCache对象设置缓存响应内容,并且通过CachedResponse类定义结构化的缓存内容