前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(上)

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

作者头像
郑子铭
发布2021-01-13 15:44:27
5640
发布2021-01-13 15:44:27
举报
文章被收录于专栏:DotNet NB && CloudNative

第 7 章 高级主题

7.1 缓存

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

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

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

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

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

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

代码语言:javascript
复制
[HttpGet("{authorId}", Name = nameof(GetAuthorAsync))]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public async Task<ActionResult<AuthorDto>> GetAuthorAsync(Guid authorId)

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

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

在 Startup 的 ConfigureServices 中添加

代码语言:javascript
复制
services.AddMvc(configure =>
{
    configure.CacheProfiles.Add("Default", new CacheProfile {Duration = 60});
    configure.CacheProfiles.Add("Never",
        new CacheProfile {Location = ResponseCacheLocation.None, NoStore = true});
。。。

接着在特性中使用即可

代码语言:javascript
复制
[ResponseCache(CacheProfileName = "Default")]

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

代码语言:javascript
复制
[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 方法内容如下:

代码语言:javascript
复制
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 中配置响应缓存

代码语言:javascript
复制
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:是否区分请求路径大小写

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

代码语言:javascript
复制
[ResponseCache(Duration = 60,VaryByQueryKeys = new string[]{"sortBy","searchQuery"})]

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

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

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

需要先在 Startup 中添加该服务

代码语言:javascript
复制
public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
    。。。

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

代码语言:javascript
复制
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 对象来控制缓存时间和优先级

代码语言:javascript
复制
//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 缓存

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

代码语言:javascript
复制
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        services.AddDistributedMemoryCache();
    }
    else
    {
        // 使用其他分布式缓存
    }
    。。。

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

代码语言:javascript
复制
dotnet sql-cache create “Date Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;” dbo TestCache

添加nuget

代码语言:javascript
复制
Install-Package Microsoft.Extensions.Caching.SqlServer

之后在容器种注入服务

代码语言:javascript
复制
public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)
{
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = Configuration["DistCache_ConnectionString"];
        options.SchemaName = "dbo";
        options.TableName = "TestCache";
    });
。。。

分布式 Redis 缓存

添加nuget

代码语言:javascript
复制
Install-Package Microsoft.Extensions.Caching.Redis

之后在容器种注入服务

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

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

代码语言:javascript
复制
"Caching": {
"Host": "127.0.0.1:6379",
"Instance": "master" 
}

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

代码语言:javascript
复制
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 方法中使用

代码语言:javascript
复制
    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 类

代码语言:javascript
复制
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>);
        }
    }
}

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 7 章 高级主题
    • 7.1 缓存
    相关产品与服务
    云数据库 Redis®
    腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档