Det här inlägget beskriver hur du lägger till responscache till controllers (klasser och metoder) i ASP.NET Core. Responscachning minskar belastningen på en webbserver, cachning innebär färre förfrågningar till en server och mindre arbete som behöver utföras av en server. Svarscache implementeras med huvudtaggar (headers) som anger hur klienter, ombud och mellanprogram tillfälligt skall spara den data som erhålls.
Responscache kan användas när den mottagna informationen väntas vara oförändrad under en tidsperiod, eller när det inte är viktigt att erhålla den senaste informationen vid varje begäran. Jag kommer att implementera svarscache för en API-metod som returnerar en tabell för ishockey, hockeytabeller förändras inte så ofta och jag vill kunna hantera så många förfrågningar från klienter och ombud som möjligt.
Cache-Control
är den primära huvudtaggen som används för cache, den används för att ange cache-direktiven: public, private, max-age, no-cache och no-store
. Andra cache-rubriker är: Age, Expires, Pragma and Vary
.
Konfiguration
Vår webb-API-metod måste kunna hantera förfrågningar från JavaScript, vi lägger till en CORS-policy och indikerar att vi använder CORS i StartUp
-klassen för vårt projekt.
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Annytab.Middleware;
using Annytab.Repositories;
using Annytab.Options;
using Annytab.Rules;
namespace Hockeytabeller
{
/// <summary>
/// This class handles application startup
/// </summary>
public class Startup
{
/// <summary>
/// Variables
/// </summary>
public IConfiguration configuration { get; set; }
/// <summary>
/// Create a new startup object
/// </summary>
/// <param name="configuration">A reference to configuration</param>
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
} // End of the constructor method
/// <summary>
/// This method is used to add services to the container.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
// Add the mvc framework
services.AddRazorPages();
// Add memory cache
services.AddDistributedMemoryCache();
// Add redis distributed cache
if (configuration.GetSection("AppSettings")["RedisConnectionString"] != "")
{
services.AddDistributedRedisCache(options =>
{
options.Configuration = configuration.GetSection("AppSettings")["RedisConnectionString"];
options.InstanceName = "Hockeytabeller:";
});
}
// Add cors
services.AddCors(options =>
{
options.AddPolicy("AnyOrigin", builder => builder.AllowAnyOrigin());
});
// Add the session service
services.AddSession(options =>
{
// Set session options
options.IdleTimeout = TimeSpan.FromMinutes(20d);
options.Cookie.Name = ".Hockeytabeller";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
// Create database options
services.Configure<DatabaseOptions>(options =>
{
options.connection_string = configuration.GetSection("AppSettings")["ConnectionString"];
options.sql_retry_count = 1;
});
// Create cache options
services.Configure<CacheOptions>(options =>
{
options.expiration_in_minutes = 240d;
});
// Add Authentication
services.AddAuthentication()
.AddCookie("Administrator", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.Cookie.MaxAge = TimeSpan.FromDays(1);
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = (context) =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.Redirect("/admin_login");
return Task.CompletedTask;
};
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("ApiAuthentication", null);
// Add clients
services.AddHttpClient();
// Add repositories
services.AddSingleton<IDatabaseRepository, MsSqlRepository>();
services.AddSingleton<IWebsiteSettingRepository, WebsiteSettingRepository>();
services.AddSingleton<IAdministratorRepository, AdministratorRepository>();
services.AddSingleton<IFinalRepository, FinalRepository>();
services.AddSingleton<IGroupRepository, GroupRepository>();
services.AddSingleton<IStaticPageRepository, StaticPageRepository>();
services.AddSingleton<IXslTemplateRepository, XslTemplateRepository>();
services.AddSingleton<ISitemapRepository, SitemapRepository>();
services.AddSingleton<IXslProcessorRepository, XslProcessorRepository>();
services.AddSingleton<ICommonServices, CommonServices>();
} // End of the ConfigureServices method
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IWebsiteSettingRepository website_settings_repository)
{
// Use error handling
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseStatusCodePagesWithReExecute("/home/error/{0}");
}
// Get website settings
KeyStringList settings = website_settings_repository.GetAllFromCache();
bool redirect_https = settings.Get("REDIRECT-HTTPS") == "true" ? true : false;
bool redirect_www = settings.Get("REDIRECT-WWW") == "true" ? true : false;
bool redirect_non_www = settings.Get("REDIRECT-NON-WWW") == "true" ? true : false;
// Add redirection and use a rewriter
RedirectHttpsWwwNonWwwRule rule = new RedirectHttpsWwwNonWwwRule
{
status_code = 301,
redirect_to_https = redirect_https,
redirect_to_www = redirect_www,
redirect_to_non_www = redirect_non_www,
hosts_to_ignore = new string[] { "localhost", "hockeytabeller001.azurewebsites.net" }
};
RewriteOptions options = new RewriteOptions();
options.Rules.Add(rule);
app.UseRewriter(options);
// Use static files
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Cache static files for 30 days
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=25920000");
ctx.Context.Response.Headers.Append("Expires", DateTime.UtcNow.AddDays(300).ToString("R", CultureInfo.InvariantCulture));
}
});
// Use sessions
app.UseSession();
// For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must
// appear between the calls to UseRouting and UseEndpoints to be effective.
app.UseRouting();
// Use CORS
app.UseCors();
// Use authentication and authorization middlewares
app.UseAuthentication();
app.UseAuthorization();
// Routing endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=home}/{action=index}/{id?}");
});
} // End of the Configure method
} // End of the class
} // End of the namespace
Mall för svar
Vår webb-API-metod returnerar html och mallen för svaret visas nedan.
@using Annytab.Repositories
@using Annytab.Models
@inject IGroupRepository group_repository
@{
// Get a group
Group group = ViewBag.Group;
IList<TeamInGroupStanding> teams = this.group_repository.GetTeamsFromXml(group.data_content);
Int32 rowCounter = 0;
}
@*Get teams in the group*@
<a href="@("https://www.hockeytabeller.se/home/group/" + group.page_name)" rel="nofollow" class="annytab-ht-not-hover">
<div class="annytab-ht-table">
<div class="annytab-ht-row">
<div class="annytab-ht-col-th-normal">RK</div>
<div class="annytab-ht-col-th-wide">Lag</div>
<div class="annytab-ht-col-th-normal">GP</div>
<div class="annytab-ht-col-th-normal">TP</div>
</div>
@for (int j = 0; j < teams.Count; j++)
{
@if (teams[j].name == "")
{
<div class="annytab-ht-row">
<div class="annytab-ht-col-line"></div>
<div class="annytab-ht-col-line"></div>
<div class="annytab-ht-col-line"></div>
<div class="annytab-ht-col-line"></div>
</div>
}
else
{
rowCounter++;
<div class="@(rowCounter % 2 != 0 ? "annytab-ht-row-main" : "annytab-ht-row-alt")">
<div class="annytab-ht-col-normal">@((rowCounter).ToString())</div>
<div class="annytab-ht-col-wide">@teams[j].name</div>
<div class="annytab-ht-col-normal">@teams[j].games</div>
<div class="annytab-ht-col-normal">@teams[j].points</div>
</div>
}
}
</div>
</a>
API-controller
Innehållet i vår API-controller visas nedan. Ett ResponseCache
-attribut kan anges för en klass eller för enskilda metoder i klassen. Vår API-metod kommer att returnera ett svar med en Cache-Control
-tagg som har ett public-direktiv (ResponseCacheLocation.Any
) och ett max-age-direktiv (Duration
) på 3600 sekunder.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Annytab.Repositories;
using Annytab.Models;
namespace Hockeytabeller.Api
{
/// <summary>
/// This class handles groups
/// </summary>
[Route("api/groups/[action]")]
public class GroupsController : Controller
{
#region Variables
private readonly ILogger logger;
private readonly IGroupRepository group_repository;
#endregion
#region Constructors
/// <summary>
/// Create a new controller
/// </summary>
public GroupsController(ILogger<GroupsController> logger, IGroupRepository group_repository)
{
// Set values for instance variables
this.logger = logger;
this.group_repository = group_repository;
} // End of the constructor
#endregion
#region Get methods
// Get html by page name
// GET api/groups/get_overview_as_html/shl
[HttpGet("{id}")]
[Microsoft.AspNetCore.Cors.EnableCors("AnyOrigin")]
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any)]
public IActionResult get_overview_as_html(string id = "")
{
// Get a group
Group group = this.group_repository.GetOneByPageName(id);
// Create view data
ViewDataDictionary view_data = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
view_data.Add("Group", group);
// Return a partial view
return new PartialViewResult { ViewName = "Views/home/_group_table.cshtml", ViewData=view_data, ContentType="text/html" };
} // End of the get_overview_as_html method
#endregion
} // End of the class
} // End of the namespace
Testa responscache
Du kan köra tester genom att göra förfrågningar till API-metoden från Postman. Du kan också använda koden nedan för att köra tester, använd Chrome DevTools för att kontrollera att CORS fungerar och för att kontrollera att svar har cachats.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
<style>
.annytab-ht-not-hover {
text-decoration: none;
}
.annytab-ht-table {
display: table;
width: 100%;
padding: 0px;
margin: 0px;
font-family: Arial, Helvetica, sans-serif;
background-color: #ffffff;
color: #000000;
overflow: hidden;
}
.annytab-ht-row {
display: table-row;
}
.annytab-ht-row-main {
display: table-row;
background-color: #ffffff;
}
.annytab-ht-row-alt {
display: table-row;
background-color: #f0f0f0;
}
.annytab-ht-col-th-normal {
display: table-cell;
padding: 4px;
color: #3d3d3d;
border-bottom: 1px solid #9e9e9e;
border-top: 1px solid #9e9e9e;
font-size: 14px;
line-height: 18px;
vertical-align: middle;
text-align: center;
width: 20%;
}
.annytab-ht-col-th-wide {
display: table-cell;
padding: 4px;
color: #3d3d3d;
border-bottom: 1px solid #9e9e9e;
border-top: 1px solid #9e9e9e;
font-size: 14px;
line-height: 18px;
vertical-align: middle;
text-align: left;
width: 40%;
}
.annytab-ht-col-line {
display: table-cell;
height: 1px;
background-color: #000000;
padding: 0px;
}
.annytab-ht-col-normal {
display: table-cell;
padding: 4px;
font-size: 12px;
line-height: 12px;
word-break: break-word;
vertical-align: middle;
text-align: center;
}
.annytab-ht-col-wide {
display: table-cell;
padding: 4px;
font-size: 12px;
line-height: 12px;
word-break: break-word;
vertical-align: middle;
text-align: left;
}
</style>
</head>
<body style="width:100%;font-family:Arial, Helvetica, sans-serif;padding:20px;">
<div class="hockeytabeller.se" data-group="shl"></div>
</body>
</html>
<script>
// Initialize when DOM content has been loaded
document.addEventListener('DOMContentLoaded', function () {
var elements = document.getElementsByClassName('hockeytabeller.se');
for (var i = 0; i < elements.length; i++) {
get_group(elements[i]);
}
}, false);
// Get a group
function get_group(element)
{
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.hockeytabeller.se/api/groups/get_overview_as_html/' + element.getAttribute('data-group'), true);
xhr.onload = function () {
if (xhr.status === 200) {
element.insertAdjacentHTML('beforeend', xhr.response);
}
};
xhr.send();
} // End of the get_group method
</script>