第 7 章 高级主题

7.1 缓存

缓存是一种通过存储资源的备份,在请求时返回资源备份的技术。ASP.NET Core 支持多种形式的缓存,既支持基于 HTTP 的缓存,也支持内存缓存和分布式缓存,还提供响应缓存中间件

HTTP 缓存,服务端返回资源时,能够在响应消息中包含 HTTP 缓存消息头

验证缓存资源的方式有两种:

  • 通过响应消息头中的 Last-Modified
  • 使用实体标签消息头

ASP.NET Core 提供的 [ResponseCache] 特性能够为资源指定 HTTP 缓存行为

在 AuthorController 中为 GetAuthorAsync 方法添加该特性

[HttpGet("{authorId}", Name = nameof(GetAuthorAsync))]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public async Task<ActionResult<AuthorDto>> GetAuthorAsync(Guid authorId)

请求该接口时,可以看到响应消息头中包含了缓存信息

当应用中多个接口需要添加同样的缓存行为时,为了避免重复,还可以使用缓存配置来完成同样的功能

在 Startup 的 ConfigureServices 中添加

services.AddMvc(configure =>
{
configure.CacheProfiles.Add("Default", new CacheProfile {Duration = 60});
configure.CacheProfiles.Add("Never",
new CacheProfile {Location = ResponseCacheLocation.None, NoStore = true});
。。。

接着在特性中使用即可

[ResponseCache(CacheProfileName = "Default")]

当缓存的资源已经过时后,客户端需要到服务器验证资源是否有效,可以通过实体标签头验证

[HttpGet("{authorId}", Name = nameof(GetAuthorAsync))]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public async Task<ActionResult<AuthorDto>> GetAuthorAsync(Guid authorId)
{
var author = await RepositoryWrapper.Author.GetByIdAsync(authorId);
if (author == null)
{
return NotFound();
} var entityHash = HashFactory.GetHash(author);
Response.Headers[HeaderNames.ETag] = entityHash;
if (Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var requestETag) && entityHash == requestETag)
{
return StatusCode(StatusCodes.Status304NotModified);
} var authorDto = Mapper.Map<AuthorDto>(author);
return authorDto;
}

GetHash 方法内容如下:

namespace Library.API.Helpers
{
public class HashFactory
{
public static string GetHash(object entity)
{
string result = string.Empty;
var json = JsonConvert.SerializeObject(entity);
var bytes = Encoding.UTF8.GetBytes(json); using (var hasher = MD5.Create())
{
var hash = hasher.ComputeHash(bytes);
result = BitConverter.ToString(hash);
result = result.Replace("-", "");
} return result;
}
}
}

响应缓存中间件,使用它能够为应用程序添加服务器端缓存功能

在 Startup 中配置响应缓存

public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCaching(options =>
{
options.UseCaseSensitivePaths = true;
options.MaximumBodySize = 1024;
});
。。。 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IMapper mapper, ILoggerFactory loggerFactory)
{
app.UseResponseCaching();
。。。

添加响应缓存服务时,ResponseCachingOptions 包含3个属性:

  • SizeLimit:缓存大小
  • MaximumBodySize:响应正文最大值
  • UseCaseSensitivePaths:是否区分请求路径大小写

响应缓存中间件同样使用特性设置

[ResponseCache(Duration = 60,VaryByQueryKeys = new string[]{"sortBy","searchQuery"})]

当服务端第二次接收同样的请求时,它将从缓存直接响应客户端

VaryByQueryKeys 属性可以根据不同的查询关键字来区分不同的响应

内存缓存,利用服务器上的内存来实现对数据的缓存

需要先在 Startup 中添加该服务

public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
。。。

然后在需要缓存的位置注入 IMemoryCache 接口,并调用相关方法

public class BookController : ControllerBase
{
public IMapper Mapper { get; set; }
public IRepositoryWrapper RepositoryWrapper { get; set; } public IMemoryCache MemoryCache { get; set; } public BookController(IMapper mapper, IRepositoryWrapper repositoryWrapper, IMemoryCache memoryCache)
{
Mapper = mapper;
RepositoryWrapper = repositoryWrapper;
MemoryCache = memoryCache;
} [HttpGet]
public async Task<ActionResult<List<BookDto>>> GetBooksAsync(Guid authorId)
{
List<BookDto> bookDtoList = new List<BookDto>();
string key = $"{authorId}_books";
if (!MemoryCache.TryGetValue(key, out bookDtoList))
{
var books = await RepositoryWrapper.Book.GetBookAsync(authorId);
bookDtoList = Mapper.Map<IEnumerable<BookDto>>(books).ToList();
MemoryCache.Set(key, bookDtoList);
} //var books = await RepositoryWrapper.Book.GetBooksAsync(authorId);
//var bookDtoList = Mapper.Map<IEnumerable<BookDto>>(books); return bookDtoList.ToList();
}
。。。

还可以使用 MemoryCacheEntryOptions 对象来控制缓存时间和优先级

//MemoryCache.Set(key, bookDtoList);
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
options.AbsoluteExpiration = DateTime.Now.AddMinutes(10);
options.Priority = CacheItemPriority.Normal;
MemoryCache.Set(key, bookDtoList, options);

分布式缓存,有效解决内存缓存不足的问题,由多个应用服务器共享

ASP.NET Core 使用分布式缓存,需要用到 IDistributedCache

ASP.NET Core 提供了 IDistributedCache 接口的3种实现方式:

  • 分布式内存缓存
  • 分布式 SQLServer 缓存
  • 分布式 Redis 缓存

分布式内存缓存实际上并非分布式缓存,与内存缓存一样,可用于开发测试阶段

public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
services.AddDistributedMemoryCache();
}
else
{
// 使用其他分布式缓存
}
。。。

分布式 SQLServer 缓存使用前,需要使用命令 dotnet sql-cache create 创建缓存数据库

dotnet sql-cache create “Date Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;” dbo TestCache

添加nuget

Install-Package Microsoft.Extensions.Caching.SqlServer

之后在容器种注入服务

public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = Configuration["DistCache_ConnectionString"];
options.SchemaName = "dbo";
options.TableName = "TestCache";
});
。。。

分布式 Redis 缓存

添加nuget

Install-Package Microsoft.Extensions.Caching.Redis

之后在容器种注入服务

public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
services.AddDistributedRedisCache(options =>
{
options.Configuration = Configuration["Caching:Host"];
options.InstanceName = Configuration["Caching:Instance"];
});
。。。

同时,在 appsettings.json 配置文件中添加 Redis 服务配置信息

"Caching": {
"Host": "127.0.0.1:6379",
"Instance": "master"
}

然后,在 AuthorController 注入 IDistributedCache 接口即可使用

public IDistributedCache DistributedCache { get; set; }

public AuthorController(IMapper mapper, IRepositoryWrapper repositoryWrapper, ILogger<AuthorController> logger, IDistributedCache distributedCache)
{
Mapper = mapper;
RepositoryWrapper = repositoryWrapper;
Logger = logger;
DistributedCache = distributedCache;
}

接下来,在 GetAuthorsAsync 方法中使用

    public async Task<ActionResult<List<AuthorDto>>> GetAuthorsAsync([FromQuery] AuthorResourceParameters parameters)
{
PagedList<Author> pagedList = null; // 为了简单,仅当请求中不包含过滤和搜索查询字符串时,才进行缓存,实际情况不应该有此限制
if (string.IsNullOrWhiteSpace(parameters.BirthPlace) && string.IsNullOrWhiteSpace(parameters.SearchQuery))
{
string cacheKey =
$"authors_page_{parameters.PageNumber}_pageSize_{parameters.PageSize}_{parameters.SortBy}";
string cacheContent = await DistributedCache.GetStringAsync(cacheKey); JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new PagedListConvert<Author>());
settings.Formatting = Formatting.Indented; if (string.IsNullOrWhiteSpace(cacheContent))
{
pagedList = await RepositoryWrapper.Author.GetAllAsync(parameters);
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(2)
}; var serializedContent = JsonConvert.SerializeObject(pagedList, settings);
await DistributedCache.SetStringAsync(cacheKey, serializedContent);
}
else
{
pagedList = JsonConvert.DeserializeObject<PagedList<Author>>(cacheContent, settings);
}
}
else
{
pagedList = await RepositoryWrapper.Author.GetAllAsync(parameters);
} //var pagedList = await RepositoryWrapper.Author.GetAllAsync(parameters);
。。。

由于 Json.NET 在序列化集合对象时会将其作为数组处理,因而会忽略集合对象中的其他属性,为了保留这些属性,需要自定义 JsonConvert 类

namespace Library.API.Helpers
{
public class PagedListConvert<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
PagedList<T> result = (PagedList<T>) value;
JObject jsonObj = new JObject(); jsonObj.Add("totalCount", result.TotalCount);
jsonObj.Add("pageNumber", result.CurrentPage);
jsonObj.Add("pageSize", result.PageSize);
jsonObj.Add("Items", JArray.FromObject(result.ToArray(), serializer));
jsonObj.WriteTo(writer);
} public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObj = JObject.Load(reader); var totalCount = (int) jsonObj["totalCount"];
var pageNumber = (int) jsonObj["pageNumber"];
var pageSize = (int) jsonObj["pageSize"];
var items = jsonObj["Items"].ToObject<T[]>(serializer); PagedList<T> pageList = new PagedList<T>(items.ToList(), totalCount, pageNumber, pageSize);
return pageList;
} public override bool CanConvert(Type objectType)
{
return objectType == typeof(PagedList<T>);
}
}
}

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(上)的更多相关文章

  1. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  2. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  3. 菜单Menu(AS开发实战第四章学习笔记)

    4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...

  4. [Android]《Android艺术开发探索》第一章读书笔记

    1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...

  5. 温故知新,使用ASP.NET Core创建Web API,永远第一次

    ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...

  6. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  7. 零基础ASP.NET Core WebAPI团队协作开发

    零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...

  8. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

  9. 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战

    大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...

  10. Asp.Net Core 5 REST API - Step by Step

    翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...

随机推荐

  1. 一道C语言改错题

    下午,在上班,读者发来一道题目,问我怎么做.我大概瞄了一眼,看题目也不难.就先让他自己上网查下. 过了一会,他说查不到,问了群里,大家也不太会. 好吧,起码这位读者自己思考过,也问过了. 题目如下,找 ...

  2. vscode如何优雅的拥抱eslint

    https://www.toutiao.com/a6826129210260587019/?tt_from=weixin&utm_campaign=client_share&wxsha ...

  3. flexible+rem移动端适配

  4. P1439-DP【绿】

    轻敌了啊...题目一共只有几句话但我却忽略了一个重大信息... 总之我显示写出了时空复杂度都是n^2级别的朴素递推算法,这没什么,基本功而已,然后50分 我试了试滚动数组,把空间复杂度降到了n级别,但 ...

  5. 第1篇 numpy 语法

    import numpy as np A = np.array([ [1, 2, 3, 4], [5, 6, 7, 8], ], dtype=int) # dtype指定数据类型int float p ...

  6. 使用 nacos 搭建注册中心及配置中心

    本文为博主原创,转载请注明出处: 在分布式微服务框架中,现在都流行使用 nacos 作为分布式框架的注册中心与配置中心.当我们搭建一套spring boot 框架的时候,默认会将配置文件 放在 res ...

  7. springboot启动流程 (3) 自动装配

    在SpringBoot中,EnableAutoConfiguration注解用于开启自动装配功能. 本文将详细分析该注解的工作流程. EnableAutoConfiguration注解 启用Sprin ...

  8. AHB2APB设计

    AHB2APB Bridge位置 AHB子系统时钟在200Mhz左右,APB时钟在几十Khz到几十Mhz 所以要进行跨时钟域处理,从AHB高时钟频率转到APB低时钟频率 AHB2APB Bridge规 ...

  9. PG数据库的离线rpm包下载

    PG数据库的离线rpm包下载 背景 周末时间研究数据库的版本. 发现PostgreSQL数据库的版本号已经变成了一年一个大版本. 兼容起来其实成本很高. 想着能够在能够上网的机器上面弄好多套数据库. ...

  10. [转帖]Jmeter接口测试:参数化

    Jmeter接口请求中的参数经常需要通过参数进行赋值 引用形式:${} 变量时:${变量名} 函数时,${_函数名(参数1,参数2,参数3)} 值中"${n}"中,n为变量名:&q ...