第 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语言基础之基础的输入输出

    前言 学一门编程语言,不能编写让用户输入数据然后输出处理后的数据的程序那么就等于没学,而在C语言中可以用printf() 和 scanf() 函数进行输入和输出操作.这两个函数是内置的库函数,定义在 ...

  2. 如何实现 Excel 表格转置(行列互换)

    直接上经验贴: https://baijiahao.baidu.com/s?id=1690475581736550777&wfr=spider&for=pc 大概就是 先复制粘贴,在粘 ...

  3. ShardingSphere 使用 ShardingJdbc 与 mybatis plus实现分库分表及读写分离

    本文为博主原创,未经允许不得转载: 目录: 一. 官网及git 地址 二. Apache ShardingSphere 简介 三.spring boot + mybaits plus +shardin ...

  4. idea导入maven项目结构不全

    本文为博主原创,转载请注明出处 将本地的项目导入idea中,其操作第一步为: File->open->选中导入maven项目的pom文件,正常情况通过该步骤项目就会导入到idea中. 通过 ...

  5. jvisualm 结合 visualGC 进行jvm监控,并分析垃圾回收

    本文为博主原创,未经允许不得转载 1.jvisualm 的使用 打开 jdk 安装目录bin目录下的 jvisualvm.exe 工具 2. visual GC插件的安装及监控分析 Visual GC ...

  6. Java之利用openCsv导出csv文件

    当时导入的时候用的openCsv,那么导出的时候自然也是用这个,查了好多资料才找到解决方案,下面记录一下实现过程. 1.Controller层: /** * 导出csv文件 */ @RequestMa ...

  7. Icoding 链表 删除范围内结点

    1.题目: 已知线性表中的元素(整数)以值递增有序排列,并以单链表作存储结构.试写一高效算法,删除表中所有大于mink且小于maxk的元素(若表中存在这样的元素),分析你的算法的时间复杂度. 链表结点 ...

  8. JS - HTML精确定位

    scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最 ...

  9. [转帖]在 TiDB 中正确使用索引,性能提升 666 倍

    https://tidb.net/book/tidb-monthly/2022/2022-04/usercase/index-666 背景​ 最近在给一个物流系统做TiDB POC测试,这个系统是基于 ...

  10. [转帖]harbor 更改网段(docker-compose)

    https://blog.csdn.net/Darkernote/article/details/119390862 问题:harbor 安装后网段冲突 docker-compose 一般安装会创建一 ...