#
Caching Data in Your API
#
START FROM PREVIOUS MODULE'S END
Developing the Web API Business Rules
#
RESPONSE CACHING
#
ADD RESPONSE CACHING TO ADDCACHING() IN SERVICESCONFIGURATION.CS
public static void AddCaching(this IServiceCollection services,
IConfiguration configuration)
{
services.AddResponseCaching();
}
#
ADD TO CONFIGURE() IN STARTUP.CS
**** Note: needs to go after CORS**
using Chinook.API.Configurations;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAppSettings(builder.Configuration);
builder.Services.AddConnectionProvider(builder.Configuration);
builder.Services.ConfigureRepositories();
builder.Services.ConfigureSupervisor();
builder.Services.AddAPILogging();
builder.Services.AddCORS();
builder.Services.ConfigureValidators();
builder.Services.AddCaching(builder.Configuration);
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseCors();
app.UseResponseCaching();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
#
ADD TO CONTROLLERS OR ACTIONS
[Route("api/[controller]")]
[ApiController]
[EnableCors("CorsPolicy")]
[ResponseCache(Duration = 604800)]
public class GenreController : ControllerBase
Note: 604800 is seconds and equals a week
#
What is seen in the Response Header
Response Header: Cache-Control: public,max-age=604800
Test in Postman and web browser to see how the API Consumer behaves.
#
IN-MEMORY CACHING
#
Install Microsoft.Extensions.Caching.Abstractions NuGet package to Domain
dos
dotnet add package Microsoft.Extensions.Caching.Abstractions
#
ADD IN-MEMORY CACHING TO ADDCACHING() IN SERVICESCONFIGURATION.CS
public static void AddCaching(this IServiceCollection services,
IConfiguration configuration)
{
services.AddResponseCaching();
services.AddMemoryCache();
}
#
ADD MEMORYCACHE TO SUPERVISOR AND GET FROM DI
#
ChinookSupervisor.cs
public partial class ChinookSupervisor : IChinookSupervisor
{
private readonly IAlbumRepository _albumRepository;
private readonly IArtistRepository _artistRepository;
private readonly ICustomerRepository _customerRepository;
private readonly IEmployeeRepository _employeeRepository;
private readonly IGenreRepository _genreRepository;
private readonly IInvoiceLineRepository _invoiceLineRepository;
private readonly IInvoiceRepository _invoiceRepository;
private readonly IMediaTypeRepository _mediaTypeRepository;
private readonly IPlaylistRepository _playlistRepository;
private readonly ITrackRepository _trackRepository;
private readonly IValidator<AlbumApiModel> _albumValidator;
private readonly IValidator<ArtistApiModel> _artistValidator;
private readonly IValidator<CustomerApiModel> _customerValidator;
private readonly IValidator<EmployeeApiModel> _employeeValidator;
private readonly IValidator<GenreApiModel> _genreValidator;
private readonly IValidator<InvoiceApiModel> _invoiceValidator;
private readonly IValidator<InvoiceLineApiModel> _invoiceLineValidator;
private readonly IValidator<MediaTypeApiModel> _mediaTypeValidator;
private readonly IValidator<PlaylistApiModel> _playlistValidator;
private readonly IValidator<TrackApiModel> _trackValidator;
private readonly IMemoryCache _cache;
public ChinookSupervisor(IAlbumRepository albumRepository,
IArtistRepository artistRepository,
ICustomerRepository customerRepository,
IEmployeeRepository employeeRepository,
IGenreRepository genreRepository,
IInvoiceLineRepository invoiceLineRepository,
IInvoiceRepository invoiceRepository,
IMediaTypeRepository mediaTypeRepository,
IPlaylistRepository playlistRepository,
ITrackRepository trackRepository,
IValidator<AlbumApiModel> albumValidator,
IValidator<ArtistApiModel> artistValidator,
IValidator<CustomerApiModel> customerValidator,
IValidator<EmployeeApiModel> employeeValidator,
IValidator<GenreApiModel> genreValidator,
IValidator<InvoiceApiModel> invoiceValidator,
IValidator<InvoiceLineApiModel> invoiceLineValidator,
IValidator<MediaTypeApiModel> mediaTypeValidator,
IValidator<PlaylistApiModel> playlistValidator,
IValidator<TrackApiModel> trackValidator,
IMemoryCache memoryCache
)
{
_albumRepository = albumRepository;
_artistRepository = artistRepository;
_customerRepository = customerRepository;
_employeeRepository = employeeRepository;
_genreRepository = genreRepository;
_invoiceLineRepository = invoiceLineRepository;
_invoiceRepository = invoiceRepository;
_mediaTypeRepository = mediaTypeRepository;
_playlistRepository = playlistRepository;
_trackRepository = trackRepository;
_albumValidator = albumValidator;
_artistValidator = artistValidator;
_customerValidator = customerValidator;
_employeeValidator = employeeValidator;
_genreValidator = genreValidator;
_invoiceValidator = invoiceValidator;
_invoiceLineValidator = invoiceLineValidator;
_mediaTypeValidator = mediaTypeValidator;
_playlistValidator = playlistValidator;
_trackValidator = trackValidator;
_cache = memoryCache;
}
}
#
ADD CODE TO SUPERVISOR FOR EACH ENTITY TYPE NEEDED
#
ChinookSupervisorAlbum.cs
public async Task<IEnumerable<AlbumApiModel>> GetAllAlbum()
{
List<Album> albums = await _albumRepository.GetAll();
var albumApiModels = albums.ConvertAll();
foreach (var album in albumApiModels)
{
var cacheEntryOptions =
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(604800))
.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(604800);
;
_cache.Set(string.Concat("Album-", album.Id), album, (TimeSpan)cacheEntryOptions);
}
return albumApiModels;
}
public async Task<AlbumApiModel?> GetAlbumById(int id)
{
var albumApiModelCached = _cache.Get<AlbumApiModel>(string.Concat("Album-", id));
if (albumApiModelCached != null)
{
return albumApiModelCached;
}
else
{
var album = await _albumRepository.GetById(id);
if (album == null) return null;
var albumApiModel = album.Convert();
var result = (_artistRepository.GetById(album.ArtistId)).Result;
if (result != null)
albumApiModel.ArtistName = result.Name;
albumApiModel.Tracks = (await GetTrackByAlbumId(id) ?? Array.Empty<TrackApiModel>()).ToList();
var cacheEntryOptions =
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(604800))
.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(604800);
;
_cache.Set(string.Concat("Album-", albumApiModel.Id), albumApiModel, (TimeSpan)cacheEntryOptions);
return albumApiModel;
}
}
#
DISTRIBUTED CACHING
#
Install tool for MSSQL distrubuted caching using the commend in Command Prompt
dos
dotnet tool install --global dotnet-sql-cache
#
CREATE NEW EMPTY DATABASE ChinookCacheDb IN MSSQL
USE master;
GO
CREATE DATABASE ChinookCacheDb;
GO
-- Verify the database files and sizes
SELECT name, size, size*1.0/128 AS [Size in MBs]
FROM sys.master_files
WHERE name = N'ChinookCacheDb';
GO
#
Run from Command Prompt
dos
dotnet sql-cache create "Data Source=.;Initial Catalog=ChinookCacheDb;Integrated Security=True;TrustServerCertificate=true;" dbo ChinookCache
#
INSTALLING THE MSSQL DOCKER CONTAINER WITH DIST CACHING DATABASE
Please install this updated docker image with the database for the distributed caching demo.
*** Instructions can be found here Installing and Setting Up SQL Server 2022 in Docker
dos
docker pull woodruffsolutions/sql-2022-chinook-dist-caching
#
Install Microsoft.Extensions.Caching.SqlServer NuGet package to Domain
dos
dotnet add package Microsoft.Extensions.Caching.SqlServer
#
ADD ChinookSQLCache CONNECTIONSTRING TO APPSETTINGS
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ChinookDbWindows": "Server=.;Database=Chinook;Trusted_Connection=True;TrustServerCertificate=True;Application Name=Chinook7WebAPI",
"ChinookDbDocker": "Server=localhost,1433;Database=Chinook;User=sa;Password=P@55w0rd;Trusted_Connection=False;Application Name=ChinookASPNETCoreAPINTier",
"ChinookSQLCache": "Data Source=.;Initial Catalog=ChinookCacheDb;Integrated Security=True;TrustServerCertificate=True"
}
}
#
ADD DISTRIBUTED CACHING TO ADDCACHING() IN SERVICESCONFIGURATION.CS
public static void AddCaching(this IServiceCollection services,
IConfiguration configuration)
{
services.AddResponseCaching();
services.AddMemoryCache();
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = configuration.GetConnectionString("ChinookSQLCache");
options.SchemaName = "dbo";
options.TableName = "ChinookCache";
});
}
#
ADD DISTRIBUTEDCACHE TO SUPERVISOR AND GET FROM DI
#
ChinookSupervisor.cs
public partial class ChinookSupervisor : IChinookSupervisor
{
private readonly IAlbumRepository _albumRepository;
private readonly IArtistRepository _artistRepository;
private readonly ICustomerRepository _customerRepository;
private readonly IEmployeeRepository _employeeRepository;
private readonly IGenreRepository _genreRepository;
private readonly IInvoiceLineRepository _invoiceLineRepository;
private readonly IInvoiceRepository _invoiceRepository;
private readonly IMediaTypeRepository _mediaTypeRepository;
private readonly IPlaylistRepository _playlistRepository;
private readonly ITrackRepository _trackRepository;
private readonly IValidator<AlbumApiModel> _albumValidator;
private readonly IValidator<ArtistApiModel> _artistValidator;
private readonly IValidator<CustomerApiModel> _customerValidator;
private readonly IValidator<EmployeeApiModel> _employeeValidator;
private readonly IValidator<GenreApiModel> _genreValidator;
private readonly IValidator<InvoiceApiModel> _invoiceValidator;
private readonly IValidator<InvoiceLineApiModel> _invoiceLineValidator;
private readonly IValidator<MediaTypeApiModel> _mediaTypeValidator;
private readonly IValidator<PlaylistApiModel> _playlistValidator;
private readonly IValidator<TrackApiModel> _trackValidator;
private readonly IMemoryCache _cache;
private readonly IDistributedCache _distributedCache;
public ChinookSupervisor(IAlbumRepository albumRepository,
IArtistRepository artistRepository,
ICustomerRepository customerRepository,
IEmployeeRepository employeeRepository,
IGenreRepository genreRepository,
IInvoiceLineRepository invoiceLineRepository,
IInvoiceRepository invoiceRepository,
IMediaTypeRepository mediaTypeRepository,
IPlaylistRepository playlistRepository,
ITrackRepository trackRepository,
IValidator<AlbumApiModel> albumValidator,
IValidator<ArtistApiModel> artistValidator,
IValidator<CustomerApiModel> customerValidator,
IValidator<EmployeeApiModel> employeeValidator,
IValidator<GenreApiModel> genreValidator,
IValidator<InvoiceApiModel> invoiceValidator,
IValidator<InvoiceLineApiModel> invoiceLineValidator,
IValidator<MediaTypeApiModel> mediaTypeValidator,
IValidator<PlaylistApiModel> playlistValidator,
IValidator<TrackApiModel> trackValidator,
IMemoryCache memoryCache,
IDistributedCache distributedCache
)
{
_albumRepository = albumRepository;
_artistRepository = artistRepository;
_customerRepository = customerRepository;
_employeeRepository = employeeRepository;
_genreRepository = genreRepository;
_invoiceLineRepository = invoiceLineRepository;
_invoiceRepository = invoiceRepository;
_mediaTypeRepository = mediaTypeRepository;
_playlistRepository = playlistRepository;
_trackRepository = trackRepository;
_albumValidator = albumValidator;
_artistValidator = artistValidator;
_customerValidator = customerValidator;
_employeeValidator = employeeValidator;
_genreValidator = genreValidator;
_invoiceValidator = invoiceValidator;
_invoiceLineValidator = invoiceLineValidator;
_mediaTypeValidator = mediaTypeValidator;
_playlistValidator = playlistValidator;
_trackValidator = trackValidator;
_cache = memoryCache;
_distributedCache = distributedCache;
}
}
#
ADD CODE TO SUPERVISOR FOR EACH ENTITY TYPE NEEDED
#
ChinookSupervisorTrack.cs
public async Task<IEnumerable<TrackApiModel>> GetAllTrack()
{
List<Track> tracks = await _trackRepository.GetAll();
var trackApiModels = tracks.ConvertAll();
foreach (var track in trackApiModels)
{
DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(3600));
cacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(86400);
await _distributedCache.SetStringAsync($"Track-{track.Id}", JsonSerializer.Serialize(track),
cacheEntryOptions);
}
return trackApiModels;
}
public async Task<TrackApiModel?> GetTrackById(int id)
{
var trackApiModelCached = await _distributedCache.GetStringAsync($"Track-{id}");
if (trackApiModelCached != null)
{
return JsonSerializer.Deserialize<TrackApiModel>(trackApiModelCached);
}
else
{
var track = await _trackRepository.GetById(id);
if (track == null) return null;
var trackApiModel = track.Convert();
trackApiModel.Genre = await GetGenreById(trackApiModel.GenreId);
trackApiModel.Album = await GetAlbumById(trackApiModel.AlbumId);
trackApiModel.MediaType = await GetMediaTypeById(trackApiModel.MediaTypeId);
if (trackApiModel.Album != null) trackApiModel.AlbumName = trackApiModel.Album.Title;
if (trackApiModel.MediaType != null) trackApiModel.MediaTypeName = trackApiModel.MediaType.Name;
if (trackApiModel.Genre != null) trackApiModel.GenreName = trackApiModel.Genre.Name;
DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(3600));
cacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(86400);
await _distributedCache.SetStringAsync($"Track-{track.Id}", JsonSerializer.Serialize(trackApiModel),
cacheEntryOptions);
return trackApiModel;
}
}