當(dāng)前位置:首頁 > IT技術(shù) > Windows編程 > 正文

《ASP.NET Core 與 RESTful API 開發(fā)實(shí)戰(zhàn)》-- (第7章)-- 讀書筆記(上)
2021-10-22 16:44:23


第 7 章 高級(jí)主題

7.1 緩存

緩存是一種通過存儲(chǔ)資源的備份,在請求時(shí)返回資源備份的技術(shù)。ASP.NET Core 支持多種形式的緩存,既支持基于 HTTP 的緩存,也支持內(nèi)存緩存和分布式緩存,還提供響應(yīng)緩存中間件

HTTP 緩存,服務(wù)端返回資源時(shí),能夠在響應(yīng)消息中包含 HTTP 緩存消息頭

驗(yàn)證緩存資源的方式有兩種:

  • 通過響應(yīng)消息頭中的 Last-Modified
  • 使用實(shí)體標(biāo)簽消息頭

ASP.NET Core 提供的 [ResponseCache] 特性能夠?yàn)橘Y源指定 HTTP 緩存行為

在 AuthorController 中為 GetAuthorAsync 方法添加該特性

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


請求該接口時(shí),可以看到響應(yīng)消息頭中包含了緩存信息

當(dāng)應(yīng)用中多個(gè)接口需要添加同樣的緩存行為時(shí),為了避免重復(fù),還可以使用緩存配置來完成同樣的功能

在 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")]


當(dāng)緩存的資源已經(jīng)過時(shí)后,客戶端需要到服務(wù)器驗(yàn)證資源是否有效,可以通過實(shí)體標(biāo)簽頭驗(yàn)證

[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 方法內(nèi)容如下:

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;
}
}
}


響應(yīng)緩存中間件,使用它能夠?yàn)閼?yīng)用程序添加服務(wù)器端緩存功能

在 Startup 中配置響應(yīng)緩存

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();
。。。


添加響應(yīng)緩存服務(wù)時(shí),ResponseCachingOptions 包含3個(gè)屬性:

  • SizeLimit:緩存大小
  • MaximumBodySize:響應(yīng)正文最大值
  • UseCaseSensitivePaths:是否區(qū)分請求路徑大小寫

響應(yīng)緩存中間件同樣使用特性設(shè)置

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


當(dāng)服務(wù)端第二次接收同樣的請求時(shí),它將從緩存直接響應(yīng)客戶端

VaryByQueryKeys 屬性可以根據(jù)不同的查詢關(guān)鍵字來區(qū)分不同的響應(yīng)

內(nèi)存緩存,利用服務(wù)器上的內(nèi)存來實(shí)現(xiàn)對(duì)數(shù)據(jù)的緩存

需要先在 Startup 中添加該服務(wù)

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


然后在需要緩存的位置注入 IMemoryCache 接口,并調(diào)用相關(guān)方法

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 對(duì)象來控制緩存時(shí)間和優(yōu)先級(jí)

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


分布式緩存,有效解決內(nèi)存緩存不足的問題,由多個(gè)應(yīng)用服務(wù)器共享

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

ASP.NET Core 提供了 IDistributedCache 接口的3種實(shí)現(xiàn)方式:

  • 分布式內(nèi)存緩存
  • 分布式 SQLServer 緩存
  • 分布式 Redis 緩存

分布式內(nèi)存緩存實(shí)際上并非分布式緩存,與內(nèi)存緩存一樣,可用于開發(fā)測試階段

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


分布式 SQLServer 緩存使用前,需要使用命令 dotnet sql-cache create 創(chuàng)建緩存數(shù)據(jù)庫

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


添加nuget

Install-Package Microsoft.Extensions.Caching.SqlServer


之后在容器種注入服務(wù)

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


之后在容器種注入服務(wù)

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


同時(shí),在 appsettings.json 配置文件中添加 Redis 服務(wù)配置信息

"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;

// 為了簡單,僅當(dāng)請求中不包含過濾和搜索查詢字符串時(shí),才進(jìn)行緩存,實(shí)際情況不應(yīng)該有此限制
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 在序列化集合對(duì)象時(shí)會(huì)將其作為數(shù)組處理,因而會(huì)忽略集合對(duì)象中的其他屬性,為了保留這些屬性,需要自定義 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>);
}
}
}


《ASP.NET Core 與 RESTful API 開發(fā)實(shí)戰(zhàn)》-- (第7章)-- 讀書筆記(上)_sql

?
?


本文摘自 :https://blog.51cto.com/u

開通會(huì)員,享受整站包年服務(wù)立即開通 >