zoukankan      html  css  js  c++  java
  • 《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(上)

    第 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) 。

  • 相关阅读:
    2018-2-13-安装-aria2
    ..USERstm32f10x.h(428): error: #67: expected a "}" ADC1_2_IRQn = 18, /*!
    zubax_gnss移植到STM32F407
    ChibiOS/RT移植到STM32F407
    arm-none-eabi/bin/ld: build/com.zubax.gnss.elf section `.text' will not fit in region `flash'
    Traceback (most recent call last): File "../zubax_chibios/tools/make_boot_descriptor.py", line 251
    Eclipse 交叉编译环境
    PX4/Pixhawk uORB
    FreeRTOS 任务创建和删除(静态)
    FreeRTOS 任务创建和删除(动态)
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13326904.html
Copyright © 2011-2022 走看看