{"id":483923,"date":"2026-06-17T07:54:05","date_gmt":"2026-06-17T07:54:05","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=483923"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=483923","title":{"rendered":"\u041a\u0435\u0448\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u0434\u0430\u0447\u0443 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 .NET MVC Core"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 ASP.NET MVC Core \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. \u0412 \u043c\u0438\u0440\u0435 SaaS \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u043d\u044b\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 &#8212; \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0442\u0435\u043c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0435\u0435, \u0447\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 &#171;\u0435\u0434\u0438\u043d\u0438\u0446\u0443 \u0436\u0435\u043b\u0435\u0437\u0430&#187; (\u0435\u0441\u043b\u0438 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a \u0432\u044b\u0440\u0430\u0437\u0438\u0442\u044c\u0441\u044f). \u0422\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u043e, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0438 \u043e\u0442\u0434\u0430\u0447\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 &#8212; \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e CPU \u0438 memory-\u0435\u043c\u043a\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438, \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u0435\u0448\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e HTTP \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 Cache-Control \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u043d\u0438\u0437\u0438\u0442\u044c \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0436\u0435\u043b\u0435\u0437\u043e.<\/p>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 ImageController \u0441 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c View, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u0437 \u0431\u0434, \u043d\u0430 \u043b\u0435\u0442\u0443 \u0438\u0437\u043c\u0435\u043d\u044f\u044f \u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u044b, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043d\u0435 \u043f\u0440\u0435\u0432\u044b\u0448\u0430\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 maxWidth \u0438 maxHeight:<\/p>\n<pre><code class=\"cs\">public class ImageController : Controller{    [HttpGet]    public ActionResult ViewResized(int id, int maxWidth, int maxHeight)    {        ...        return new ImageResult { FileName = fileName, Content = memoryStream.ToArray() };    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>(\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u0442.\u043a. \u0441\u0442\u0430\u0442\u044c\u044f \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043e \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u0430 \u043d\u0435 \u043e \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438)<\/p>\n<p>\u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u044d\u0448, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043e\u0442\u0432\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a Cache-Control. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438:<\/p>\n<ol>\n<li>\n<p>\u0414\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442 \u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435<\/p>\n<pre><code class=\"cs\">\/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a: Cache-Control: public, max-age=60[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]public ActionResult ViewResized(int id, int maxWidth, int maxHeight)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/li>\n<li>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u0432 \u043a\u043e\u0434 \u0441\u0430\u043c\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/p>\n<pre><code class=\"cs\">public ActionResult ViewResized(int id, int maxWidth, int maxHeight){    Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue    {        Public = true,        MaxAge = 60    };    ...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 (middleware) \u0434\u043b\u044f \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/li>\n<\/ol>\n<p>\u042f \u043d\u0435 \u043b\u044e\u0431\u043b\u044e \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0438 \u043d\u0435\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u0442\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0441 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u043c \u0441\u043b\u043e\u0435\u043c. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u0434 \u0441\u0430\u043c\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f &#171;\u0447\u0438\u0441\u0442\u044b\u043c&#187;, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043a\u044d\u0448\u0430, \u0447\u0438\u0442\u0430\u044f \u0435\u0433\u043e \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0431\u0434 \u0438\u043b\u0438 \u0444\u0430\u0439\u043b\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>\u041a\u043e\u0434 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"cs\">internal class ImageCacheMiddleware: MiddlewareWithService{    private readonly RequestDelegate next;    public ImageCacheMiddleware(RequestDelegate next)    {        this.next = next;    }    public async Task Invoke(HttpContext context)    {        var settingsProvider = context.RequestServices.GetService&lt;ISettingsProvider&gt;();        int imageCacheIntervalInSeconds = settingsProvider.Get(\"ImageCacheIntervalInSeconds\");        if (imageCacheIntervalInSeconds &gt; 0)        {            context.Response.OnStarting(() =&gt;            {                \/\/ add the header only if it hasn't been set by a controller already                if (!context.Response.Headers.ContainsKey(\"Cache-Control\"))                {                    context.Response.Headers.Append(\"Cache-Control\", $\"public, max-age={imageCacheIntervalInSeconds}\");                }                return Task.CompletedTask;            });        }        await next.Invoke(context);    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 ISettingsProvider (\u043d\u0430\u0448 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a. \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u044f \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c. \u041a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u0441\u043a\u0430\u0437\u0430\u043d\u043e \u0432\u044b\u0448\u0435, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u043a\u0438 \u0438\u0437 \u0431\u0434, \u0444\u0430\u0439\u043b\u0430, \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u0442.\u0434.) \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 ImageCacheIntervalInSeconds. \u0415\u0441\u043b\u0438 \u043e\u043d \u0431\u043e\u043b\u044c\u0448\u0435 0, \u0442\u043e \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 (Response.OnStarting) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a Cache-Control \u0441\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c<\/p>\n<pre><code>public, max-age={imageCacheIntervalInSeconds}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 public, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u0442\u0432\u0435\u0442 \u0432 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u0430\u043a\u0436\u0435 \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u043a\u0441\u0438 \u0438 cdn. \u0415\u0441\u043b\u0438 \u0432 \u0432\u0430\u0448\u0435\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u044d\u0442\u043e \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0445 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432 (no-cache, no-store, private).<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"cs\">internal static class ImageCacheMiddlewareExtension{    public static IApplicationBuilder UseImageCacheMiddleware(this IApplicationBuilder builder)    {        return builder.UseMiddleware&lt;ImageCacheMiddleware&gt;();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 Program.cs:<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);...var app = builder.Build();...app.UseWhen(    context =&gt; context.Request.Path.ToString().ToLower().Contains(\"\/image\/view\"),    appBranch =&gt; {        appBranch.UseImageCacheMiddleware();    });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043b\u043e\u0439 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 Network \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 &#8212; \u043f\u0435\u0440\u0432\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0434\u0435\u0442 \u0441 \u0443\u0441\u0442\u043d\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c Cache-Control, \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438 \u043e\u0442\u0432\u0435\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c cached.<\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u0430\u043c\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c NUnit \u0438 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u0443\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u043b\u0443\u0448\u0435\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f <a href=\"https:\/\/github.com\/devlooped\/moq\" rel=\"noopener noreferrer nofollow\">Moq<\/a>.<\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0443 \u043d\u0430\u0441 \u0434\u0432\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u0430:<\/p>\n<ol>\n<li>\n<p>\u0417\u0430\u043c\u043e\u043a\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043a\u044d\u0448\u0430 \u0438\u0437 \u0431\u0434<\/p>\n<\/li>\n<li>\n<p>\u0418\u043d\u0438\u0446\u0438\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/p>\n<\/li>\n<\/ol>\n<p>\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u043c\u043e\u043a\u0430\u0442\u044c \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u0432\u044b\u0437\u043e\u0432\u043e\u0432, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u044b\u0437\u043e\u0432 \u043a \u0442\u043e\u043c\u0443 \u0436\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c-\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c:<\/p>\n<pre><code class=\"cs\">HttpContext.RequestServices.GetService&lt;T&gt;()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u0436\u0438\u0437\u043d\u044c, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u043a\u043e\u0434 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f &#8212; \u0432\u044b\u0434\u0435\u043b\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 ISettingsProvider \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"cs\">protected virtual T getService&lt;T&gt;(HttpContext ctx){    return ctx.RequestServices.GetService&lt;T&gt;();}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0437\u0430\u0442\u0435\u043c \u043f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043b\u0430\u0441\u0441-\u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a ImageCacheMiddlewareForTesting \u0441 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430:<\/p>\n<pre><code class=\"cs\">internal class ImageCacheMiddlewareForTesting: ImageCacheMiddleware{    private ISettingsProvider settingsProvider;    public ImageCacheMiddlewareForTesting(RequestDelegate next, ISettingsProvider settingsProvider) : base(next)    {        this.settingsProvider = settingsProvider;    }    protected override T getService&lt;T&gt;(HttpContext context)    {        if (typeof(T) == typeof(ISettingsProvider))            return (T)this.settingsProvider;        throw new NotSupportedException();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442-\u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 (mock) \u0441 \u043d\u0443\u0436\u043d\u044b\u043c \u043d\u0430\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435\u043c.<\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0432\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Moq &#8212; \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430, \u0437\u0430\u043f\u0438\u0448\u0435\u043c \u0435\u0433\u043e \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback, \u0438 \u0437\u0430\u0442\u0435\u043c \u0441\u044d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0447\u0430\u043b\u043e \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u043f\u0440\u044f\u043c\u044b\u043c \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430. \u0412\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"cs\">[TestFixture]public class TestImageCacheMiddleware{    [Test]    public void test_WHEN_cache_lifetime_specified_THEN_it_is_added_to_headers()    {        \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 - \u0432\u0440\u0435\u043c\u044f \u043a\u044d\u0448\u0430 1 \u0441\u0435\u043a        var settingsProvider = new Mock&lt;ISettingsProvider&gt;();        settingsProvider.Setup(x =&gt; x.Get(It.IsAny&lt;string&gt;())).Returns(1);        var headers = new HeaderDictionary();        var response = new Mock&lt;HttpResponse&gt;();        response.Setup(x =&gt; x.Headers).Returns(headers);        \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback        Func&lt;Task&gt; capturedCallback = null;        response.Setup(r =&gt; r.OnStarting(It.IsAny&lt;Func&lt;Task&gt;&gt;()))            .Callback&lt;Func&lt;Task&gt;&gt;(callback =&gt; capturedCallback = callback);        var ctx = new Mock&lt;HttpContext&gt;();        ctx.Setup(x =&gt; x.Response).Returns(response.Object);        var requestDelegate = new Mock&lt;RequestDelegate&gt;();        var middleware = new ImageCacheMiddlewareForTesting(requestDelegate.Object, settingsProvider.Object);        middleware.Invoke(ctx.Object).GetAwaiter().GetResult();        \/\/ \u0441\u0438\u043c\u0443\u043b\u044f\u0446\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c        if (capturedCallback != null)        {            capturedCallback().GetAwaiter().GetResult();        }        \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u044b\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430        ClassicAssert.AreEqual(1, headers.Count);        ClassicAssert.AreEqual(\"public, max-age=1\", headers[\"Cache-Control\"]);    }    [Test]    public void test_WHEN_cache_lifetime_not_specified_THEN_it_is_not_added_to_headers()    {        \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 - \u0432\u0440\u0435\u043c\u044f \u043a\u044d\u0448\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e        var settingsProvider = new Mock&lt;ISettingsProvider&gt;();        settingsProvider.Setup(x =&gt; x.GetValue(It.IsAny&lt;string&gt;(), 0)).Returns(0);        var headers = new HeaderDictionary();        var response = new Mock&lt;HttpResponse&gt;();        response.Setup(x =&gt; x.Headers).Returns(headers);        \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback        Func&lt;Task&gt; capturedCallback = null;        response.Setup(r =&gt; r.OnStarting(It.IsAny&lt;Func&lt;Task&gt;&gt;()))            .Callback&lt;Func&lt;Task&gt;&gt;(callback =&gt; capturedCallback = callback);        var ctx = new Mock&lt;HttpContext&gt;();        ctx.Setup(x =&gt; x.Response).Returns(response.Object);        var requestDelegate = new Mock&lt;RequestDelegate&gt;();        var middleware = new ImageCacheMiddlewareForTesting(requestDelegate.Object, settingsProvider.Object);        middleware.Invoke(ctx.Object).GetAwaiter().GetResult();        \/\/ \u0441\u0438\u043c\u0443\u043b\u044f\u0446\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c        if (capturedCallback != null)        {            capturedCallback().GetAwaiter().GetResult();        }        \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0435 \u0431\u044b\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430        ClassicAssert.AreEqual(0, headers.Count);    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044e \u043e\u0442\u0434\u0430\u0447\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a, \u0441\u043d\u0438\u0437\u0438\u043b\u0438 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0436\u0435\u043b\u0435\u0437\u043e \u0438 \u043f\u043e\u043a\u0440\u044b\u043b\u0438 \u043a\u043e\u0434 \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0435\u0433\u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0440\u0435\u0441\u0441\u0430 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1048484\/\">https:\/\/habr.com\/ru\/articles\/1048484\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 ASP.NET MVC Core \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. \u0412 \u043c\u0438\u0440\u0435 SaaS \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u043d\u044b\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 &#8212; \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0442\u0435\u043c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0435\u0435, \u0447\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 &#171;\u0435\u0434\u0438\u043d\u0438\u0446\u0443 \u0436\u0435\u043b\u0435\u0437\u0430&#187; (\u0435\u0441\u043b\u0438 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a \u0432\u044b\u0440\u0430\u0437\u0438\u0442\u044c\u0441\u044f). \u0422\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u043e, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0438 \u043e\u0442\u0434\u0430\u0447\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434\u0435 &#8212; \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e CPU \u0438 memory-\u0435\u043c\u043a\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438, \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u0435\u0448\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e HTTP \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 Cache-Control \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u043d\u0438\u0437\u0438\u0442\u044c \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0436\u0435\u043b\u0435\u0437\u043e.\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 ImageController \u0441 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c View, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u0437 \u0431\u0434, \u043d\u0430 \u043b\u0435\u0442\u0443 \u0438\u0437\u043c\u0435\u043d\u044f\u044f \u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u044b, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043d\u0435 \u043f\u0440\u0435\u0432\u044b\u0448\u0430\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 maxWidth \u0438 maxHeight:public class ImageController : Controller{    [HttpGet]    public ActionResult ViewResized(int id, int maxWidth, int maxHeight)    {        &#8230;        return new ImageResult { FileName = fileName, Content = memoryStream.ToArray() };    }}(\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u0442.\u043a. \u0441\u0442\u0430\u0442\u044c\u044f \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043e \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u0430 \u043d\u0435 \u043e \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438)\u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u044d\u0448, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043e\u0442\u0432\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a Cache-Control. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438:\u0414\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442 \u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a: Cache-Control: public, max-age=60[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]public ActionResult ViewResized(int id, int maxWidth, int maxHeight)\u0418\u0437\u043c\u0435\u043d\u0438\u0432 \u043a\u043e\u0434 \u0441\u0430\u043c\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044fpublic ActionResult ViewResized(int id, int maxWidth, int maxHeight){    Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue    {        Public = true,        MaxAge = 60    };    &#8230;}\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 (middleware) \u0434\u043b\u044f \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u042f \u043d\u0435 \u043b\u044e\u0431\u043b\u044e \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0438 \u043d\u0435\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u0442\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0441 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u043c \u0441\u043b\u043e\u0435\u043c. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u0434 \u0441\u0430\u043c\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f &#171;\u0447\u0438\u0441\u0442\u044b\u043c&#187;, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043a\u044d\u0448\u0430, \u0447\u0438\u0442\u0430\u044f \u0435\u0433\u043e \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0431\u0434 \u0438\u043b\u0438 \u0444\u0430\u0439\u043b\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438.\u041a\u043e\u0434 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:internal class ImageCacheMiddleware: MiddlewareWithService{    private readonly RequestDelegate next;    public ImageCacheMiddleware(RequestDelegate next)    {        this.next = next;    }    public async Task Invoke(HttpContext context)    {        var settingsProvider = context.RequestServices.GetService&lt;ISettingsProvider&gt;();        int imageCacheIntervalInSeconds = settingsProvider.Get(&#171;ImageCacheIntervalInSeconds&#187;);        if (imageCacheIntervalInSeconds &gt; 0)        {            context.Response.OnStarting(() =&gt;            {                \/\/ add the header only if it hasn&#8217;t been set by a controller already                if (!context.Response.Headers.ContainsKey(&#171;Cache-Control&#187;))                {                    context.Response.Headers.Append(&#171;Cache-Control&#187;, $&#187;public, max-age={imageCacheIntervalInSeconds}&#187;);                }                return Task.CompletedTask;            });        }        await next.Invoke(context);    }}\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 ISettingsProvider (\u043d\u0430\u0448 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a. \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u044f \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c. \u041a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u0441\u043a\u0430\u0437\u0430\u043d\u043e \u0432\u044b\u0448\u0435, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u043a\u0438 \u0438\u0437 \u0431\u0434, \u0444\u0430\u0439\u043b\u0430, \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u0442.\u0434.) \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 ImageCacheIntervalInSeconds. \u0415\u0441\u043b\u0438 \u043e\u043d \u0431\u043e\u043b\u044c\u0448\u0435 0, \u0442\u043e \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 (Response.OnStarting) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a Cache-Control \u0441\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043cpublic, max-age={imageCacheIntervalInSeconds}\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 public, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u0442\u0432\u0435\u0442 \u0432 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u0430\u043a\u0436\u0435 \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u043a\u0441\u0438 \u0438 cdn. \u0415\u0441\u043b\u0438 \u0432 \u0432\u0430\u0448\u0435\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u044d\u0442\u043e \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0445 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432 (no-cache, no-store, private).\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:internal static class ImageCacheMiddlewareExtension{    public static IApplicationBuilder UseImageCacheMiddleware(this IApplicationBuilder builder)    {        return builder.UseMiddleware&lt;ImageCacheMiddleware&gt;();    }}\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 Program.cs:var builder = WebApplication.CreateBuilder(args);&#8230;var app = builder.Build();&#8230;app.UseWhen(    context =&gt; context.Request.Path.ToString().ToLower().Contains(&#171;\/image\/view&#187;),    appBranch =&gt; {        appBranch.UseImageCacheMiddleware();    });\u041e\u0442\u043b\u0438\u0447\u043d\u043e, \u0441\u043b\u043e\u0439 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u044d\u0442\u043e \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 Network \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 &#8212; \u043f\u0435\u0440\u0432\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0434\u0435\u0442 \u0441 \u0443\u0441\u0442\u043d\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c Cache-Control, \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438 \u043e\u0442\u0432\u0435\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c cached.\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u0430\u043c\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c NUnit \u0438 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u0443\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u043b\u0443\u0448\u0435\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f Moq.\u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0443 \u043d\u0430\u0441 \u0434\u0432\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u0430:\u0417\u0430\u043c\u043e\u043a\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043a\u044d\u0448\u0430 \u0438\u0437 \u0431\u0434\u0418\u043d\u0438\u0446\u0438\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u043c\u043e\u043a\u0430\u0442\u044c \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u0432\u044b\u0437\u043e\u0432\u043e\u0432, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u044b\u0437\u043e\u0432 \u043a \u0442\u043e\u043c\u0443 \u0436\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c-\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c:HttpContext.RequestServices.GetService&lt;T&gt;()\u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u0436\u0438\u0437\u043d\u044c, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u043a\u043e\u0434 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f &#8212; \u0432\u044b\u0434\u0435\u043b\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 ISettingsProvider \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:protected virtual T getService&lt;T&gt;(HttpContext ctx){    return ctx.RequestServices.GetService&lt;T&gt;();}\u0437\u0430\u0442\u0435\u043c \u043f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043b\u0430\u0441\u0441-\u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a ImageCacheMiddlewareForTesting \u0441 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430:internal class ImageCacheMiddlewareForTesting: ImageCacheMiddleware{    private ISettingsProvider settingsProvider;    public ImageCacheMiddlewareForTesting(RequestDelegate next, ISettingsProvider settingsProvider) : base(next)    {        this.settingsProvider = settingsProvider;    }    protected override T getService&lt;T&gt;(HttpContext context)    {        if (typeof(T) == typeof(ISettingsProvider))            return (T)this.settingsProvider;        throw new NotSupportedException();    }}\u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442-\u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 (mock) \u0441 \u043d\u0443\u0436\u043d\u044b\u043c \u043d\u0430\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435\u043c.\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0432\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Moq &#8212; \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430, \u0437\u0430\u043f\u0438\u0448\u0435\u043c \u0435\u0433\u043e \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback, \u0438 \u0437\u0430\u0442\u0435\u043c \u0441\u044d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0447\u0430\u043b\u043e \u043e\u0442\u0434\u0430\u0447\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u043f\u0440\u044f\u043c\u044b\u043c \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430. \u0412\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:[TestFixture]public class TestImageCacheMiddleware{    [Test]    public void test_WHEN_cache_lifetime_specified_THEN_it_is_added_to_headers()    {        \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 &#8212; \u0432\u0440\u0435\u043c\u044f \u043a\u044d\u0448\u0430 1 \u0441\u0435\u043a        var settingsProvider = new Mock&lt;ISettingsProvider&gt;();        settingsProvider.Setup(x =&gt; x.Get(It.IsAny&lt;string&gt;())).Returns(1);        var headers = new HeaderDictionary();        var response = new Mock&lt;HttpResponse&gt;();        response.Setup(x =&gt; x.Headers).Returns(headers);        \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback        Func&lt;Task&gt; capturedCallback = null;        response.Setup(r =&gt; r.OnStarting(It.IsAny&lt;Func&lt;Task&gt;&gt;()))            .Callback&lt;Func&lt;Task&gt;&gt;(callback =&gt; capturedCallback = callback);        var ctx = new Mock&lt;HttpContext&gt;();        ctx.Setup(x =&gt; x.Response).Returns(response.Object);        var requestDelegate = new Mock&lt;RequestDelegate&gt;();        var middleware = new ImageCacheMiddlewareForTesting(requestDelegate.Object, settingsProvider.Object);        middleware.Invoke(ctx.Object).GetAwaiter().GetResult();        \/\/ \u0441\u0438\u043c\u0443\u043b\u044f\u0446\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c        if (capturedCallback != null)        {            capturedCallback().GetAwaiter().GetResult();        }        \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u044b\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430        ClassicAssert.AreEqual(1, headers.Count);        ClassicAssert.AreEqual(&#171;public, max-age=1&#187;, headers[&#171;Cache-Control&#187;]);    }    [Test]    public void test_WHEN_cache_lifetime_not_specified_THEN_it_is_not_added_to_headers()    {        \/\/ \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 &#8212; \u0432\u0440\u0435\u043c\u044f \u043a\u044d\u0448\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e        var settingsProvider = new Mock&lt;ISettingsProvider&gt;();        settingsProvider.Setup(x =&gt; x.GetValue(It.IsAny&lt;string&gt;(), 0)).Returns(0);        var headers = new HeaderDictionary();        var response = new Mock&lt;HttpResponse&gt;();        response.Setup(x =&gt; x.Headers).Returns(headers);        \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e capturedCallback        Func&lt;Task&gt; capturedCallback = null;        response.Setup(r =&gt; r.OnStarting(It.IsAny&lt;Func&lt;Task&gt;&gt;()))            .Callback&lt;Func&lt;Task&gt;&gt;(callback =&gt; capturedCallback = callback);        var ctx = new Mock&lt;HttpContext&gt;();        ctx.Setup(x =&gt; x.Response).Returns(response.Object);        var requestDelegate = new Mock&lt;RequestDelegate&gt;();        var middleware = new ImageCacheMiddlewareForTesting(requestDelegate.Object, settingsProvider.Object);        middleware.Invoke(ctx.Object).GetAwaiter().GetResult();        \/\/ \u0441\u0438\u043c\u0443\u043b\u044f\u0446\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c        if (capturedCallback != null)        {            capturedCallback().GetAwaiter().GetResult();        }        \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0435 \u0431\u044b\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430        ClassicAssert.AreEqual(0, headers.Count);    }}\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044e \u043e\u0442\u0434\u0430\u0447\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a, \u0441\u043d\u0438\u0437\u0438\u043b\u0438 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u0436\u0435\u043b\u0435\u0437\u043e \u0438 \u043f\u043e\u043a\u0440\u044b\u043b\u0438 \u043a\u043e\u0434 \u044e\u043d\u0438\u0442-\u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0435\u0433\u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0440\u0435\u0441\u0441\u0430 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c.\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 https:\/\/habr.com\/ru\/articles\/1048484\/<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-483923","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/483923","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=483923"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/483923\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=483923"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=483923"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=483923"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}