Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Кэширование представляет собой сохранение данных в специальном месте для более быстрого доступа к ним в будущем. Применение кэширование может значительно повысить производительность приложения ASP.NET, существенно уменьшая количество обращений к источникам данных, например, к базам данных. Особенно эффективно кэширование в тех случаях, когда у нас есть на веб-странице некоторые элементы, данные которых редко изменяются или изменяются через определенный промежуток времени.
Самым простым способом кэширования в ASP.NET Core предствляет использование объекта Microsoft.Extensions.Caching.Memory.IMemoryCache, который позволяет сохранять данные в кэше на сервере.
Для рассмотрения механизма кэширования возьмем какую-нибудь простенькую задачу. Допустим, нам надо кэшировать профиль пользователя или некоторую информацию о пользователе, которая может не изменяться в течение более долгого периода времени, и поэтому эту информацию мы можем кэшировать, чтобы в будущем избежать лишних обращений к бд.
Для решения этой задачи создадим проект ASP.NET Core по типу ASP.NET Core Web App (Model-View-Controller). Пусть он будет называться CachingMVC. Взаимодействовать с базой данных мы будем через Entity Framework.
Вначале добавим в проект в папку Models новый класс User, который будет описывать используемые данные:
public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public int Age { get; set; } }
Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в папку Models класс контекста данных ApplicationContext:
using Microsoft.EntityFrameworkCore; namespace CachingMVC.Models { public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options) { Database.EnsureCreated(); } } }
Для взаимодействия с контекстом и бд создадим специальный сервис. Для этого вначале добавим в проект папку Services и в ней определим новый класс UserService:
using CachingMVC.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace CachingMVC.Services { public class UserService { private ApplicationContext db; private IMemoryCache cache; public UserService(ApplicationContext context, IMemoryCache memoryCache) { db = context; cache = memoryCache; } public void Initialize() { if (!db.Users.Any()) { db.Users.AddRange( new User { Name = "Tom", Email = "tom@gmail.com", Age = 35 }, new User { Name = "Alice", Email = "alice@yahoo.com", Age = 29 }, new User { Name = "Sam", Email = "sam@online.com", Age = 37 } ); db.SaveChanges(); } } public async Task<IEnumerable<User>> GetUsers() { return await db.Users.ToListAsync(); } public async Task AddUser(User user) { db.Users.Add(user); int n = await db.SaveChangesAsync(); if (n > 0) { cache.Set(user.Id, user, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }); } } public async Task<User> GetUser(int id) { User user = null; if (!cache.TryGetValue(id, out user)) { user = await db.Users.FirstOrDefaultAsync(p => p.Id == id); if (user != null) { cache.Set(user.Id, user, new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(5))); } } return user; } } }
Данный сервис через встроенный механизм внедрения зависимостей будет получать контекст данных и использовать его для взаимодействия с бд. Кроме того, данный класс реализует логику кэширования.
Через встроенный механизм внедрения зависимостей в конструкторе мы можем получить объект кэша IMemoryCache. Применяя методы интефейса IMemoryCache, мы можем управлять кэшем:
bool TryGetValue(object key, out object value): пытаемся получить элемент по ключу key. При успешном получении параметр value заполняется полученным элементом, а метод возвращает true
object Get(object key): дополнительный метод расширения, который получает по ключу key элемент и возвращает его
void Remove(object key): удаляет из кэша элемент по ключу key
object Set(object key, object value, MemoryCacheEntryOptions options): добавляет в кэш элемент с ключом key и значением value, применяя опции кэширования MemoryCacheEntryOptions
По сути встроенная реализация интерфейса IMemoryCache - класс MemoryCache, который используется по умолчанию, инкапсулирует все объекты кэша в виде словаря Dictionary.
Здесь же кэширование реализуется в двух случаях: при получение объекта по id из бд и при добавлении этого объекта.
При добавлении объект вначале добавляется в базу данных, и в случае удачного добавления, также добавляется в кэш:
public void AddUser(User user) { db.Users.Add(user); int n = db.SaveChanges(); if (n > 0) { cache.Set(user.Id, user, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }); } }
При сохранении объекта в кэше в качестве его ключа выступает значение свойства Id.
С помощью параметра AbsoluteExpirationRelativeToNow
здесь устанавливатся время кэширования - 5 минут.
При получении объекта по id вначале пытаемся найти этот объект в кэше, и если там не оказалось, то извлеаем его и бд и затем добавляем в кэш.
public async Task<User> GetUser(int id) { User user = null; if (!cache.TryGetValue(id, out user)) { user = await db.Users.FirstOrDefaultAsync(p => p.Id == id); if (user != null) { cache.Set(user.Id, user, new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(5))); } } return user; }
Если ключ в кэше был найден, то в объект user передается извлекаемое из кэша значение, а метод TryGetValue()
возвращает true.
Для установки времени кэширования здесь применяется альтернативный способ - метод SetAbsoluteExpiration, который в данном случае таже устанавливает 5 минут.
В итоге у нас получится следующая структура проекта:
Затем нам надо зарегистрировать сервисы кэширования и entity framework и применить компонент middleware в классе Startup:
using CachingMVC.Models; using CachingMVC.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace CachingMVC { public class Startup { public void ConfigureServices(IServiceCollection services) { string connectionString = @"Server=(localdb)\MSSQLLocalDB;Database=cacheappdb;Trusted_Connection=True;"; // внедрение зависимости Entity Framework services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connectionString)); // внедрение зависимости UserService services.AddTransient<UserService>(); // добавление кэширования services.AddMemoryCache(); services.AddControllersWithViews(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
Прежде всего для добавления возможности кэширования нам надо добавить сервис в методе ConfigureServices()
:
services.AddMemoryCache()
По сути этот сервис устанавливает зависимость для IMemoryCache, создавая объект синглтон:
services.TryAdd(ServiceDescriptor.Singleton<IMemoryCache, MemoryCache>());
И в классе контроллера HomeController определим следующий код:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using CachingMVC.Models; using CachingMVC.Services; namespace CachingMVC.Controllers { public class HomeController : Controller { UserService userService; public HomeController(UserService service) { userService = service; userService.Initialize(); } public async Task<IActionResult> Index(int id) { User user = await userService.GetUser(id); if (user != null) return Content($"User: {user.Name}"); return Content("User not found"); } } }
В итоге при первом обращении к приложению данные будут извлекаться из базы данных и сохраняться в кэш. При всех последующих обращениях в пределах времени кэширования (в данном случае в течение 5 минут) данные будут извлекаться из кэша: