{"id":324397,"date":"2021-06-05T15:00:12","date_gmt":"2021-06-05T15:00:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=324397"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=324397","title":{"rendered":"\u041a\u0430\u043a \u044f \u0441\u0434\u0435\u043b\u0430\u043b Discord \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e .NET Core"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bfd\/714\/b22\/bfd714b22c355c897621c5f392faa5d1.png\" alt=\"\u0411\u0430\u0442\u0440\u0430\u043a \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0430\u0435\u0442 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u0438\u0433\u0440\u043e\u043a\" title=\"\u0411\u0430\u0442\u0440\u0430\u043a \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0430\u0435\u0442 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u0438\u0433\u0440\u043e\u043a\" width=\"1403\" height=\"608\"><figcaption>\u0411\u0430\u0442\u0440\u0430\u043a \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0430\u0435\u0442 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u0438\u0433\u0440\u043e\u043a<\/figcaption><\/figure>\n<p><strong>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/strong><\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b Discord \u0431\u043e\u0442\u0430 \u0434\u043b\u044f World of Warcraft \u0433\u0438\u043b\u044c\u0434\u0438\u0438. \u041e\u043d \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0438\u0433\u0440\u044b \u0438 \u043f\u0438\u0448\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Discord \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u0438\u0433\u0440\u043e\u043a \u0438\u043b\u0438 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u0433\u0438\u043b\u044c\u0434\u0438\u044e \u043f\u043e\u043a\u0438\u043d\u0443\u043b \u0441\u0442\u0430\u0440\u044b\u0439 \u0438\u0433\u0440\u043e\u043a. \u041c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439 \u043c\u044b \u043f\u0440\u043e\u0437\u0432\u0430\u043b\u0438 \u044d\u0442\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u0411\u0430\u0442\u0440\u0430\u043a.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442. \u041f\u043e \u0441\u0443\u0442\u0438 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 .NET Core: \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043b\u043e\u0433\u0438\u043a\u0443, \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 api \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u043f\u043e\u043a\u0440\u043e\u0435\u043c \u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0443\u043f\u0430\u043a\u0443\u0435\u043c \u0432 Docker \u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0432 Heroku. \u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c continuous integration \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Github Actions.<\/p>\n<p><u>\u041e\u0442 \u0432\u0430\u0441 \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0437\u043d\u0430\u043d\u0438\u0439 \u043e\u0431 \u0438\u0433\u0440\u0435<\/u>. \u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0442\u0430\u043a \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0438\u0433\u0440\u044b \u0438 \u0441\u0434\u0435\u043b\u0430\u043b \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445. \u041d\u043e \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0432 Battle.net, \u0442\u043e \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430, \u043e\u0442 \u0432\u0430\u0441 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f \u0445\u043e\u0442\u044f \u0431\u044b \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u043f\u044b\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u0431 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 ASP.NET \u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker.<\/p>\n<p><strong>\u041f\u043b\u0430\u043d<\/strong><\/p>\n<p>\u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0448\u0430\u0433\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u043d\u0430\u0440\u0430\u0449\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b.<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 web api \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c \/check. \u041f\u0440\u0438     \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a \u044d\u0442\u043e\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443 \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u201cHello!\u201d \u0432 Discord \u0447\u0430\u0442.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u0441\u043e\u0441\u0442\u0430\u0432\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438\u043b\u0438 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0432 \u043a\u044d\u0448 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0445 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0441 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \u0441\u043f\u0438\u0441\u043a\u0430.     \u041e\u0431\u043e \u0432\u0441\u0435\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0432 Discord.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c Dockerfile \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430     \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435 Heroku.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0441\u0431\u043e\u0440\u043a\u0443, \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u0432 master<\/p>\n<\/li>\n<\/ol>\n<p><strong>\u0428\u0430\u0433 1. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 Discord<\/strong><\/p>\n<p>\u041d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 ASP.NET Core Web API \u043f\u0440\u043e\u0435\u043a\u0442.  <\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/tutorials\/first-web-api?view=aspnetcore-5.0&amp;tabs=visual-studio-code\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a> &#8212; \u044d\u0442\u043e \u043e\u0434\u043d\u0430 \u0438\u0437 \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c. \u041f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u043d\u0430\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441 Github \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430. \u0412 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 Github.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440<\/p>\n<pre><code class=\"cs\">[ApiController] public class GuildController : ControllerBase {     [HttpGet(\"\/check\")]     public async Task&lt;IActionResult&gt; Check(CancellationToken ct)     {         return Ok();     } }<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f webhook \u043e\u0442 \u0432\u0430\u0448\u0435\u0433\u043e Discord \u0441\u0435\u0440\u0432\u0435\u0440\u0430. Webhook &#8212; \u044d\u0442\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0442\u043e \u044d\u0442\u043e \u0430\u0434\u0440\u0435\u0441 \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0441\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 http \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u0432\u043d\u0443\u0442\u0440\u0438.<\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0432 \u043f\u0443\u043d\u043a\u0442\u0435 integrations \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043b\u044e\u0431\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u0430\u0448\u0435\u0433\u043e Discord \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e4c\/85b\/fc7\/e4c85bfc75be8428033d10d62d0e1b4c.png\" alt=\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 webhook\" title=\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 webhook\" width=\"2324\" height=\"648\"><figcaption>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 webhook<\/figcaption><\/figure>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c webhook \u0432 appsettings.json \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041f\u043e\u0437\u0436\u0435 \u043c\u044b \u0443\u043d\u0435\u0441\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f Heroku. \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 \u0442\u0435\u043c \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 ASP Core \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0437\u0443\u0447\u0438\u0442\u0435 <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/fundamentals\/configuration\/?view=aspnetcore-5.0\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u0443 \u0442\u0435\u043c\u0443<\/a>.<\/p>\n<pre><code class=\"json\">{ \t\"DiscordWebhook\":\"https:\/\/discord.com\/api\/webhooks\/****\/***\" }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 DiscordBroker, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Discord. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043f\u0430\u043f\u043a\u0443 Services \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0442\u0443\u0434\u0430 \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u044d\u0442\u0430 \u043f\u0430\u043f\u043a\u0430 \u043d\u0430\u043c \u0435\u0449\u0435 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<p>\u041f\u043e \u0441\u0443\u0442\u0438 \u044d\u0442\u043e\u0442 \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u0435\u043b\u0430\u0435\u0442 post \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u0438\u0437 webhook \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u043b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p>\n<pre><code class=\"cs\">public class DiscordBroker : IDiscordBroker {     private readonly string _webhook;     private readonly HttpClient _client;      public DiscordBroker(IHttpClientFactory clientFactory, IConfiguration configuration)     {         _client = clientFactory.CreateClient();         _webhook = configuration[\"DiscordWebhook\"];     }      public async Task SendMessage(string message, CancellationToken ct)     {         var request = new HttpRequestMessage         {             Method = HttpMethod.Post,             RequestUri = new Uri(_webhook),             Content = new FormUrlEncodedContent(new[] {new KeyValuePair&lt;string, string&gt;(\"content\", message)})         };          await _client.SendAsync(request, ct);     } }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/fundamentals\/dependency-injection?view=aspnetcore-5.0\" rel=\"noopener noreferrer nofollow\">\u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/a>. IConfiguration \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u044c webhook \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432, \u0430 IHttpClientFactory \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 HttpClient.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044f \u0438\u0437\u0432\u043b\u0435\u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 \u043f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438. \u0414\u0435\u043b\u0430\u0439\u0442\u0435 \u044d\u0442\u043e \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435.<\/p>\n<p>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0447\u0442\u043e \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IDiscordBroker, DiscordBroker&gt;();<\/code><\/pre>\n<p>\u0410 \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c HttpClient, \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b IHttpClientFactory.<\/p>\n<pre><code class=\"cs\">services.AddHttpClient();<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435.<\/p>\n<pre><code class=\"cs\">private readonly IDiscordBroker _discordBroker;  public GuildController(IDiscordBroker discordBroker) {   _discordBroker = discordBroker; }  [HttpGet(\"\/check\")] public async Task&lt;IActionResult&gt; Check(CancellationToken ct) {   await _discordBroker.SendMessage(\"Hello\", ct);   return Ok(); }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0435\u043a\u0442, \u0437\u0430\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \/check \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c \u0447\u0442\u043e \u0432 Discord \u043f\u0440\u0438\u0448\u043b\u043e \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435.<\/p>\n<p><strong>\u0428\u0430\u0433 2. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Battle.net<\/strong><\/p>\n<p>\u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430: \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 battle.net \u0438\u043b\u0438 \u0438\u0437 \u043c\u043e\u0435\u0439 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043d\u0435\u0442 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u0432 battle.net, \u0442\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u0443\u0441\u043e\u043a \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u0433\u0434\u0435 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438.<\/p>\n<p><strong>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435<\/strong><\/p>\n<p>\u0412\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"https:\/\/develop.battle.net\/\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/develop.battle.net\/<\/u><\/a> \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u0430\u043c \u0434\u0432\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0440\u043e\u043a\u0438 BattleNetId \u0438 BattleNetSecret. \u041e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u043d\u0443\u0436\u043d\u044b \u043d\u0430\u043c \u0447\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 api \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u041f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0438\u0445 \u0432 appsettings.<\/p>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/www.nuget.org\/packages\/ArgentPonyWarcraftClient\" rel=\"noopener noreferrer nofollow\">ArgentPonyWarcraftClient<\/a>.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 BattleNetApiClient \u0432 \u043f\u0430\u043f\u043a\u0435 Services.<\/p>\n<pre><code class=\"cs\">public class BattleNetApiClient {    private readonly string _guildName;    private readonly string _realmName;    private readonly IWarcraftClient _warcraftClient;     public BattleNetApiClient(IHttpClientFactory clientFactory, IConfiguration configuration)    {        _warcraftClient = new WarcraftClient(            configuration[\"BattleNetId\"],            configuration[\"BattleNetSecret\"],            Region.Europe,            Locale.ru_RU,            clientFactory.CreateClient()        );        _realmName = configuration[\"RealmName\"];        _guildName = configuration[\"GuildName\"];    } }<\/code><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 WarcraftClient.<br \/>\u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u043a \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0440\u0430\u043d\u0435\u0435. \u0421 \u0435\u0433\u043e \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432 appsettings \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u0432\u0435 \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0438 RealmName \u0438 GuildName. RealmName \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u043c\u0438\u0440\u0430, \u0430 GuildName \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438. \u0418\u0445 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435.<\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 GetGuildMembers \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u0430\u0432 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c WowCharacterToken \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u0431\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0435.<\/p>\n<pre><code class=\"cs\">public async Task&lt;WowCharacterToken[]&gt; GetGuildMembers() {    var roster = await _warcraftClient.GetGuildRosterAsync(_realmName, _guildName, \"profile-eu\");     if (!roster.Success) throw new ApplicationException(\"get roster failed\");     return roster.Value.Members.Select(x =&gt; new WowCharacterToken    {        WowId = x.Character.Id,        Name = x.Character.Name    }).ToArray(); }<\/code><\/pre>\n<pre><code class=\"cs\">public class WowCharacterToken {   public int WowId { get; set; }   public string Name { get; set; } }<\/code><\/pre>\n<p>\u041a\u043b\u0430\u0441\u0441 WowCharacterToken \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043f\u0430\u043f\u043a\u0443 Models.<\/p>\n<p>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c BattleNetApiClient \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IBattleNetApiClient, BattleNetApiClient&gt;();<\/code><\/pre>\n<p><strong>\u0411\u0435\u0440\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438<\/strong><\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c WowCharacterToken \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0435\u0435 \u0432 \u043f\u0430\u043f\u043a\u0443 Models. \u041e\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0435.<\/p>\n<pre><code class=\"cs\">public class WowCharacterToken {   public int WowId { get; set; }   public string Name { get; set; } }<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0432\u043e\u0442 \u0442\u0430\u043a\u043e\u0439 \u043a\u043b\u0430\u0441\u0441<\/p>\n<pre><code class=\"cs\">public class BattleNetApiClient {     private bool _firstTime = true;      public Task&lt;WowCharacterToken[]&gt; GetGuildMembers()     {         if (_firstTime)         {             _firstTime = false;              return Task.FromResult(new[]             {                 new WowCharacterToken                 {                     WowId = 1,                     Name = \"\u0410\u0440\u0442\u0430\u0441\"                 },                 new WowCharacterToken                 {                     WowId = 2,                     Name = \"\u0421\u0438\u043b\u044c\u0432\u0430\u043d\u0430\"                 }             });         }          return Task.FromResult(new[]         {             new WowCharacterToken             {                 WowId = 1,                 Name = \"\u0410\u0440\u0442\u0430\u0441\"             },             new WowCharacterToken             {                 WowId = 3,                 Name = \"\u041d\u0435\u043f\u043e\u0431\u0435\u0434\u0438\u043c\u044b\u0439\"             }         });     } }<\/code><\/pre>\n<p>\u041e\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0437\u0430\u0448\u0438\u0442\u044b\u0439 \u0432 \u043d\u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432. \u041f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u043c\u044b \u0432\u0435\u0440\u043d\u0435\u043c \u043e\u0434\u0438\u043d \u0441\u043f\u0438\u0441\u043e\u043a, \u043f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0434\u0440\u0443\u0433\u043e\u0439. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u043c \u0447\u0442\u043e \u0441\u043c\u043e\u0434\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0447\u0438\u0432\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 api. \u042d\u0442\u043e\u0439 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438 \u0445\u0432\u0430\u0442\u0438\u0442 \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442.<\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0432\u0441\u0435 \u0447\u0442\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IBattleNetApiClient, BattleNetApiClient&gt;();<\/code><\/pre>\n<p><strong>\u0412\u044b\u0432\u0435\u0434\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432 Discord<\/strong><\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u043b\u0438 BattleNetApiClient, \u0438\u043c \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043a\u043e\u043b-\u0432\u043e \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0432 Discord.<\/p>\n<pre><code class=\"cs\">[ApiController] public class GuildController : ControllerBase {   private readonly IDiscordBroker _discordBroker;   private readonly IBattleNetApiClient _battleNetApiClient;    public GuildController(IDiscordBroker discordBroker, IBattleNetApiClient battleNetApiClient)   {      _discordBroker = discordBroker;      _battleNetApiClient = battleNetApiClient;   }    [HttpGet(\"\/check\")]   public async Task&lt;IActionResult&gt; Check(CancellationToken ct)   {      var members = await _battleNetApiClient.GetGuildMembers();      await _discordBroker.SendMessage($\"Members count: {members.Length}\", ct);      return Ok();   } }<\/code><\/pre>\n<p><strong>\u0428\u0430\u0433 3. \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0445 \u0438 \u0443\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0433\u0440\u043e\u043a\u043e\u0432<\/strong><\/p>\n<p>\u041d\u0443\u0436\u043d\u043e \u043d\u0430\u0443\u0447\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0438\u0433\u0440\u043e\u043a\u0438 \u043f\u043e\u044f\u0432\u0438\u043b\u0438\u0441\u044c \u0438\u043b\u0438 \u043f\u0440\u043e\u043f\u0430\u043b\u0438 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u043a api. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0432 InMemory \u043a\u044d\u0448\u0435 (\u0432 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438) \u0438\u043b\u0438 \u0432\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0437\u0430\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0432 InMemory \u043a\u044d\u0448\u0435, \u0442\u043e \u043c\u044b \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0437\u0436\u0435 \u043c\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 Redis \u043a\u0430\u043a \u0430\u0434\u0434\u043e\u043d \u0432 Heroku \u0438 \u0431\u0443\u0434\u0435\u043c \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430.<\/p>\n<p>\u0410 \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c InMemory \u043a\u044d\u0448 \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddMemoryCache(); <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043d\u0430\u0448\u0435\u043c \u0440\u0430\u0441\u043f\u043e\u0440\u044f\u0436\u0435\u043d\u0438\u0438 \u0435\u0441\u0442\u044c <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/performance\/caching\/distributed?view=aspnetcore-5.0\" rel=\"noopener noreferrer nofollow\">IDistributedCache<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440. \u042f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0435\u043b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e , \u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043e\u0431\u0435\u0440\u0442\u043a\u0443. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043a\u043b\u0430\u0441\u0441 GuildRepository \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u0432 \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u043f\u043a\u0443 Repositories.<\/p>\n<pre><code class=\"cs\">public class GuildRepository : IGuildRepository {     private readonly IDistributedCache _cache;     private const string Key = \"wowcharacters\";      public GuildRepository(IDistributedCache cache)     {         _cache = cache;     }      public async Task&lt;WowCharacterToken[]&gt; GetCharacters(CancellationToken ct)     {         var value = await _cache.GetAsync(Key, ct);          if (value == null) return Array.Empty&lt;WowCharacterToken&gt;();          return await Deserialize(value);     }      public async Task SaveCharacters(WowCharacterToken[] characters, CancellationToken ct)     {         var value = await Serialize(characters);          await _cache.SetAsync(Key, value, ct);     }          private static async Task&lt;byte[]&gt; Serialize(WowCharacterToken[] tokens)     {         var binaryFormatter = new BinaryFormatter();         await using var memoryStream = new MemoryStream();         binaryFormatter.Serialize(memoryStream, tokens);         return memoryStream.ToArray();     }      private static async Task&lt;WowCharacterToken[]&gt; Deserialize(byte[] bytes)     {         await using var memoryStream = new MemoryStream();         var binaryFormatter = new BinaryFormatter();         memoryStream.Write(bytes, 0, bytes.Length);         memoryStream.Seek(0, SeekOrigin.Begin);         return (WowCharacterToken[]) binaryFormatter.Deserialize(memoryStream);     } }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c.<\/p>\n<pre><code class=\"cs\">public class GuildService {     private readonly IBattleNetApiClient _battleNetApiClient;     private readonly IGuildRepository _repository;     public GuildService(IBattleNetApiClient battleNetApiClient, IGuildRepository repository)     {         _battleNetApiClient = battleNetApiClient;         _repository = repository;     }     public async Task&lt;Report&gt; Check(CancellationToken ct)     {         var newCharacters = await _battleNetApiClient.GetGuildMembers();         var savedCharacters = await _repository.GetCharacters(ct);         await _repository.SaveCharacters(newCharacters, ct);         if (!savedCharacters.Any())             return new Report             {                 JoinedMembers = Array.Empty&lt;WowCharacterToken&gt;(),                 DepartedMembers = Array.Empty&lt;WowCharacterToken&gt;(),                 TotalCount = newCharacters.Length             };         var joined = newCharacters.Where(x =&gt; savedCharacters.All(y =&gt; y.WowId != x.WowId)).ToArray();         var departed = savedCharacters.Where(x =&gt; newCharacters.All(y =&gt; y.Name != x.Name)).ToArray();         return new Report         {             JoinedMembers = joined,             DepartedMembers = departed,             TotalCount = newCharacters.Length         };     } }<\/code><\/pre>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u043e\u0434\u0435\u043b\u044c Report. \u0415\u0435 \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043f\u0430\u043f\u043a\u0443 Models.<\/p>\n<pre><code class=\"cs\">public class Report {    public WowCharacterToken[] JoinedMembers { get; set; }    public WowCharacterToken[] DepartedMembers { get; set; }    public int TotalCount { get; set; } }<\/code><\/pre>\n<p>\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u043c GuildService \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435.<\/p>\n<pre><code class=\"cs\">[HttpGet(\"\/check\")] public async Task&lt;IActionResult&gt; Check(CancellationToken ct) {    var report = await _guildService.Check(ct);     return new JsonResult(report, new JsonSerializerOptions    {       Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic)    }); }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u0432 Discord \u043a\u0430\u043a\u0438\u0435 \u0438\u0433\u0440\u043e\u043a\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0438\u0441\u044c \u0438\u043b\u0438 \u043f\u043e\u043a\u0438\u043d\u0443\u043b\u0438 \u0433\u0438\u043b\u044c\u0434\u0438\u044e.<\/p>\n<pre><code class=\"cs\">if (joined.Any() || departed.Any()) {    foreach (var c in joined)       await _discordBroker.SendMessage(          $\":smile: **{c.Name}** \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438\",          ct);    foreach (var c in departed)       await _discordBroker.SendMessage(          $\":smile: **{c.Name}** \u043f\u043e\u043a\u0438\u043d\u0443\u043b \u0433\u0438\u043b\u044c\u0434\u0438\u044e\",          ct); }<\/code><\/pre>\n<p>\u042d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0432 GuildService \u0432 \u043a\u043e\u043d\u0435\u0446 \u043c\u0435\u0442\u043e\u0434\u0430 Check. \u041f\u0438\u0441\u0430\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u043d\u0435 \u0441\u0442\u043e\u0438\u0442, \u0443 \u043d\u0435\u0433\u043e \u0434\u0440\u0443\u0433\u043e\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u0412 \u0441\u0430\u043c\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0435 \u043c\u044b \u0434\u0435\u043b\u0430\u043b\u0438 \u0442\u0430\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Discord \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0435\u0449\u0435 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u043e GuildService.<\/p>\n<p>\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u0435 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0432\u044b \u0432\u0438\u0434\u0435\u043b\u0438 \u0447\u0442\u043e \u044f \u0432\u044b\u0432\u0435\u043b \u0431\u043e\u043b\u044c\u0448\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0435. \u0415\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0435\u0441\u043b\u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 <a href=\"https:\/\/www.nuget.org\/packages\/ArgentPonyWarcraftClient\" rel=\"noopener noreferrer nofollow\">ArgentPonyWarcraftClient<\/a><\/p>\n<pre><code class=\"cs\">await _warcraftClient.GetCharacterProfileSummaryAsync(_realmName, name.ToLower(), Namespace);<\/code><\/pre>\n<p>\u042f \u0440\u0435\u0448\u0438\u043b \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u044c\u044e \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043e\u0434\u0430 \u0432 BattleNetApiClient, \u0447\u0442\u043e\u0431\u044b \u0441\u0442\u0430\u0442\u044c\u044f \u043d\u0435 \u0440\u0430\u0437\u0440\u043e\u0441\u043b\u0430\u0441\u044c \u0434\u043e \u0431\u0435\u0437\u0443\u043c\u043d\u044b\u0445 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u0432.<\/p>\n<p><strong>Unit \u0442\u0435\u0441\u0442\u044b<\/strong><\/p>\n<p>\u0423 \u043d\u0430\u0441 \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043a\u043b\u0430\u0441\u0441 GuildService \u0441 \u043d\u0435\u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c\u0441\u044f \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u0421\u0442\u043e\u0438\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438 \u0434\u043b\u044f BattleNetApiClient, GuildRepository \u0438 DiscordBroker. \u042f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0438\u043b \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u043b\u044f \u044d\u0442\u0438\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0445 \u0444\u0435\u0439\u043a\u0438.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u043b\u044f Unit \u0442\u0435\u0441\u0442\u043e\u0432. \u0417\u0430\u0432\u0435\u0434\u0438\u0442\u0435 \u0432 \u043d\u0435\u043c \u043f\u0430\u043f\u043a\u0443 Fakes \u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0442\u0440\u0438 \u0444\u0435\u0439\u043a\u0430.<\/p>\n<pre><code class=\"cs\">public class DiscordBrokerFake : IDiscordBroker {    public List&lt;string&gt; SentMessages { get; } = new();    public Task SendMessage(string message, CancellationToken ct)    {       SentMessages.Add(message);       return Task.CompletedTask;    } }<\/code><\/pre>\n<pre><code class=\"cs\">public class GuildRepositoryFake : IGuildRepository {     public List&lt;WowCharacterToken&gt; Characters { get; } = new();      public Task&lt;WowCharacterToken[]&gt; GetCharacters(CancellationToken ct)     {         return Task.FromResult(Characters.ToArray());     }      public Task SaveCharacters(WowCharacterToken[] characters, CancellationToken ct)     {         Characters.Clear();         Characters.AddRange(characters);         return Task.CompletedTask;     } }<\/code><\/pre>\n<pre><code class=\"cs\">public class BattleNetApiClientFake : IBattleNetApiClient {    public List&lt;WowCharacterToken&gt; GuildMembers { get; } = new();    public List&lt;WowCharacter&gt; Characters { get; } = new();    public Task&lt;WowCharacterToken[]&gt; GetGuildMembers()    {       return Task.FromResult(GuildMembers.ToArray());    } }<\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0444\u0435\u0439\u043a\u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0437\u0430\u0434\u0430\u0442\u044c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u0432. \u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0436\u0435 \u0446\u0435\u043b\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/Moq\/moq4\/wiki\/Quickstart\" rel=\"noopener noreferrer nofollow\">Moq<\/a>. \u041d\u043e \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0430\u043c\u043e\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0444\u0435\u0439\u043a\u043e\u0432.<\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0442\u0435\u0441\u0442 \u043d\u0430 GuildService \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"cs\">[Test] public async Task SaveNewMembers_WhenCacheIsEmpty() {    var wowCharacterToken = new WowCharacterToken    {       WowId = 100,       Name = \"Sam\"    };        var battleNetApiClient = new BattleNetApiApiClientFake();    battleNetApiClient.GuildMembers.Add(wowCharacterToken);     var guildRepositoryFake = new GuildRepositoryFake();     var guildService = new GuildService(battleNetApiClient, null, guildRepositoryFake);     var changes = await guildService.Check(CancellationToken.None);     changes.JoinedMembers.Length.Should().Be(0);    changes.DepartedMembers.Length.Should().Be(0);    changes.TotalCount.Should().Be(1);    guildRepositoryFake.Characters.Should().BeEquivalentTo(wowCharacterToken);  }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u043d\u043e \u0438\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f, \u0442\u0435\u0441\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0447\u0442\u043e \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432, \u0435\u0441\u043b\u0438 \u043a\u044d\u0448 \u043f\u0443\u0441\u0442. \u0417\u0430\u043c\u0435\u0442\u044c\u0442\u0435, \u0432 \u043a\u043e\u043d\u0446\u0435 \u0442\u0435\u0441\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 Should, Be&#8230; \u042d\u0442\u043e \u043c\u0435\u0442\u043e\u0434\u044b \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <a href=\"https:\/\/github.com\/fluentassertions\/fluentassertions\" rel=\"noopener noreferrer nofollow\">FluentAssertions<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043c\u043e\u0433\u0430\u044e\u0442 \u043d\u0430\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c Assertion \u0431\u043e\u043b\u0435\u0435 \u0447\u0438\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u043c.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0431\u0430\u0437\u0430 \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432. \u042f \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u0432\u0430\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0438\u0434\u0435\u044e, \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u0432\u0430\u043c.<\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0433\u043e\u0442\u043e\u0432. \u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0443\u043c\u0430\u0442\u044c \u043e \u0435\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438.<\/p>\n<p><strong>\u0428\u0430\u0433 4. \u041f\u0440\u0438\u0432\u0435\u0442 Docker \u0438 Heroku!<\/strong><\/p>\n<p>\u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435 <a href=\"http:\/\/heroku.com\" rel=\"noopener noreferrer nofollow\">Heroku<\/a>. Heroku \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c .NET \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438, \u043d\u043e \u043e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c Docker \u043e\u0431\u0440\u0430\u0437\u044b.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 Docker \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432 \u043a\u043e\u0440\u043d\u0435 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f Dockerfile \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c<\/p>\n<pre><code class=\"bash\">FROM mcr.microsoft.com\/dotnet\/sdk:5.0 AS builder WORKDIR \/sources COPY *.sln . COPY .\/src\/peon.csproj .\/src\/ COPY .\/tests\/tests.csproj .\/tests\/ RUN dotnet restore COPY . . RUN dotnet publish --output \/app\/ --configuration Release FROM mcr.microsoft.com\/dotnet\/core\/aspnet:3.1 WORKDIR \/app COPY --from=builder \/app . CMD [\"dotnet\", \"peon.dll\"]<\/code><\/pre>\n<p>peon.dll \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0435\u0433\u043e Solution. Peon \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043a\u0430\u043a \u0431\u0430\u0442\u0440\u0430\u043a.<\/p>\n<p>\u041e \u0442\u043e\u043c \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 Docker \u0438 Heroku \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/devcenter.heroku.com\/articles\/build-docker-images-heroku-yml\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>. \u041d\u043e \u044f \u0432\u0441\u0435 \u0436\u0435 \u043e\u043f\u0438\u0448\u0443 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439.<\/p>\n<p>\u0412\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0432 Heroku, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c Heroku CLI.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 heroku \u0438 \u0441\u0432\u044f\u0436\u0438\u0442\u0435 \u0435\u0433\u043e \u0441 \u0432\u0430\u0448\u0438\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u043c.<\/p>\n<pre><code class=\"bash\">heroku git:remote -a project_name<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b heroku.yml \u0432 \u043f\u0430\u043f\u043a\u0435 \u0441 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c. \u0423 \u043d\u0435\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435:<\/p>\n<pre><code>build:   docker:     web: Dockerfile<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0435\u0440\u0435\u0434\u0443 \u043a\u043e\u043c\u0430\u043d\u0434:<\/p>\n<pre><code class=\"bash\"># \u0417\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u043c\u0441\u044f \u0432 heroku registry heroku container:login  # \u0421\u043e\u0431\u0435\u0440\u0435\u043c \u0438 \u0437\u0430\u043f\u0443\u0448\u0438\u043c \u043e\u0431\u0440\u0430\u0437 \u0432 registry heroku container:push web  # \u0417\u0430\u0440\u0435\u043b\u0438\u0437\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438\u0437 \u043e\u0431\u0440\u0430\u0437\u0430 heroku container:release web<\/code><\/pre>\n<p>\u041c\u043e\u0436\u0435\u0442\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code>heroku open<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 Heroku, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 Redis \u0434\u043b\u044f \u043a\u044d\u0448\u0430. \u041a\u0430\u043a \u0432\u044b \u043f\u043e\u043c\u043d\u0438\u0442\u0435 InMemory \u043a\u044d\u0448 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e Heroku \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0430\u0434\u0434\u043e\u043d <a href=\"https:\/\/elements.heroku.com\/addons\/rediscloud\" rel=\"noopener noreferrer nofollow\">RedisCloud<\/a>.<\/p>\n<p>\u0421\u0442\u0440\u043e\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f Redis \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f <em>REDISCLOUD_URL<\/em>. \u041e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u0432 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0435 Heroku.<\/p>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u0443 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0432 \u043a\u043e\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Caching.StackExchangeRedis\" rel=\"noopener noreferrer nofollow\">Microsoft.Extensions.Caching.StackExchangeRedis<\/a>.<\/p>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c Redis \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043b\u044f IDistributedCache \u0432 Startup.<\/p>\n<pre><code>services.AddStackExchangeRedisCache(o =&gt; {    o.InstanceName = \"PeonCache\";    var redisCloudUrl = Environment.GetEnvironmentVariable(\"REDISCLOUD_URL\");    if (string.IsNullOrEmpty(redisCloudUrl))    {       throw new ApplicationException(\"redis connection string was not found\");    }    var (endpoint, password) = RedisUtils.ParseConnectionString(redisCloudUrl);    o.ConfigurationOptions = new ConfigurationOptions    {       EndPoints = {endpoint},       Password = password    }; });<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043a\u043e\u0434\u0435 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e REDISCLOUD_URL \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0438\u0437\u0432\u043b\u0435\u043a\u043b\u0438 \u0430\u0434\u0440\u0435\u0441 \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043b\u0430\u0441\u0441\u0430 RedisUtils. \u0415\u0433\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u044f \u0441\u0430\u043c:<\/p>\n<pre><code class=\"cs\">public static class RedisUtils {    public static (string endpoint, string password) ParseConnectionString(string connectionString)    {       var bodyPart = connectionString.Split(\":\/\/\")[1];       var authPart = bodyPart.Split(\"@\")[0];       var password = authPart.Split(\":\")[1];       var endpoint = bodyPart.Split(\"@\")[1];       return (endpoint, password);    } }<\/code><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 Unit \u0442\u0435\u0441\u0442.<\/p>\n<pre><code class=\"cs\">[Test] public void ParseConnectionString() {    const string example = \"redis:\/\/user:password@url:port\";    var (endpoint, password) = RedisUtils.ParseConnectionString(example);    endpoint.Should().Be(\"url:port\");    password.Should().Be(\"password\"); }<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u0447\u0442\u043e \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u043b\u0438, GuildRepository \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043a\u044d\u0448 \u043d\u0435 \u0432 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u043f\u0430\u043c\u044f\u0442\u044c, \u0430 \u0432 Redis. \u041d\u0430\u043c \u0434\u0430\u0436\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0432 \u043a\u043e\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0439\u0442\u0435 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p><strong>\u0428\u0430\u0433 5. \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0446\u0438\u043a\u043b\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435<\/strong><\/p>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u043e\u0441\u0442\u0430\u0432\u0430 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u043b\u0430 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u0430\u0436\u0434\u044b\u0435 15 \u043c\u0438\u043d\u0443\u0442.<\/p>\n<p>\u0415\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 <a href=\"https:\/\/cron-job.org\/\" rel=\"noopener noreferrer nofollow\">https:\/\/cron-job.org<\/a>. \u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0430\u0442\u044c get \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \/check \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u044b\u0435 N \u043c\u0438\u043d\u0443\u0442. <\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Hosted Services. \u0412 <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/fundamentals\/host\/hosted-services\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0435\u0435\u0441\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u0432 ASP.NET Core \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u0423\u0447\u0442\u0438\u0442\u0435, \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0442\u0430\u0440\u0438\u0444 \u0432 Heroku \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442 \u0447\u0442\u043e \u0432\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0441\u044b\u043f\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043a \u043d\u0435\u043c\u0443 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0435 \u0434\u0435\u043b\u0430\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. Hosted Service \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u0441\u043d\u0435\u0442. \u0412 \u044d\u0442\u043e\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0435 \u0432\u0430\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0442\u0430\u0440\u0438\u0444. \u041a\u0441\u0442\u0430\u0442\u0438, \u0442\u0430\u043a \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043c\u043e\u0439 \u0431\u043e\u0442.<\/p>\n<p>\u0422\u0440\u0435\u0442\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u044d\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 Cron \u0430\u0434\u0434\u043e\u043d\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <a href=\"https:\/\/devcenter.heroku.com\/articles\/scheduler\" rel=\"noopener noreferrer nofollow\">Heroku Scheduler<\/a>. \u041c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0439\u0442\u0438 \u044d\u0442\u0438\u043c \u043f\u0443\u0442\u0435\u043c \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c cron job \u0432 Heroku.<\/p>\n<p><strong>\u0428\u0430\u0433 6. \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u0431\u043e\u0440\u043a\u0430, \u043f\u0440\u043e\u0433\u043e\u043d \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f<\/strong><\/p>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u0437\u0430\u0439\u0434\u0438\u0442\u0435 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 Heroku.<\/p>\n<p>\u0422\u0430\u043c \u0435\u0441\u0442\u044c \u043f\u0443\u043d\u043a\u0442 Deploy. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0442\u0430\u043c \u0441\u0432\u043e\u0439 Github \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0438 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 Automatic deploys \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u0432 master.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b3b\/ed1\/27b\/b3bed127b599be1bbc10a13e758c818e.png\" width=\"1241\" height=\"536\"><figcaption><\/figcaption><\/figure>\n<p>\u041f\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0433\u0430\u043b\u043e\u0447\u043a\u0443 \u0443 \u043f\u0443\u043d\u043a\u0442\u0430 <em>Wait for CI to pass before deploy<\/em>. \u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0447\u0442\u043e\u0431\u044b Heroku \u0434\u043e\u0436\u0438\u0434\u0430\u043b\u0441\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0438 \u043f\u0440\u043e\u0433\u043e\u043d\u043a\u0438 \u0442\u0435\u0441\u0442\u043e\u0432. \u0415\u0441\u043b\u0438 \u0442\u0435\u0441\u0442\u044b \u043f\u043e\u043a\u0440\u0430\u0441\u043d\u0435\u044e\u0442, \u0442\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0435 \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0441\u0431\u043e\u0440\u043a\u0443 \u0438 \u043f\u0440\u043e\u0433\u043e\u043d\u043a\u0443 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 Github Actions.<\/p>\n<p>\u0417\u0430\u0439\u0434\u0438\u0442\u0435 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043f\u0443\u043d\u043a\u0442 Actions. \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 workflow \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 <strong>.NET<\/strong><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c0c\/250\/4e9\/c0c2504e90f75c46d3c59d8bdc62cc6e.png\" width=\"757\" height=\"656\"><figcaption><\/figcaption><\/figure>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u0444\u0430\u0439\u043b dotnet.yml. \u041e\u043d \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u0431\u043e\u0440\u043a\u0438.<\/p>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435 \u043f\u043e \u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443, \u0437\u0430\u0434\u0430\u043d\u0438\u0435 build \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043f\u0443\u0448\u0430 \u0432 \u0432\u0435\u0442\u043a\u0443 master.<\/p>\n<pre><code>on:   push:     branches: [ master ]   pull_request:     branches: [ master ]<\/code><\/pre>\n<p>\u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0441\u0430\u043c\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u043d\u0430\u0441 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442. \u0415\u0441\u043b\u0438 \u0432\u044b \u0432\u0447\u0438\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u0432 \u0442\u043e \u0447\u0442\u043e \u0442\u0430\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0442\u043e \u0443\u0432\u0438\u0434\u0438\u0442\u0435 \u0447\u0442\u043e \u0442\u0430\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0437\u0430\u043f\u0443\u0441\u043a \u043a\u043e\u043c\u0430\u043d\u0434 dotnet build \u0438 dotnet test.<\/p>\n<pre><code>    steps:     - uses: actions\/checkout@v2     - name: Setup .NET       uses: actions\/setup-dotnet@v1       with:         dotnet-version: 5.0.x     - name: Restore dependencies       run: dotnet restore     - name: Build       run: dotnet build --no-restore     - name: Test       run: dotnet test --no-build --verbosity normal<\/code><\/pre>\n<p>\u041c\u0435\u043d\u044f\u0442\u044c \u0432 \u044d\u0442\u043e\u043c \u0444\u0430\u0439\u043b\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u0432\u0441\u0435 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438.<\/p>\n<p>\u0417\u0430\u043f\u0443\u0448\u0442\u0435 \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0432 master \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0447\u0442\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f. \u041a\u0441\u0442\u0430\u0442\u0438, \u043e\u043d\u043e \u0443\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e workflow.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7f9\/1b8\/907\/7f91b89075291f3cff1bb712157ef14e.png\" width=\"1281\" height=\"398\"><figcaption><\/figcaption><\/figure>\n<p>\u041e\u0442\u043b\u0438\u0447\u043d\u043e! \u0412\u043e\u0442 \u043c\u044b \u0438 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 .NET Core \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442\u0441\u044f \u0432 Heroku. \u0423 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a \u0434\u043b\u044f \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f: \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u0440\u043e\u043a\u0430\u0447\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b, \u043f\u043e\u0432\u0435\u0441\u0438\u0442\u044c \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0438. \u0442. \u0434.<\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u0434\u043a\u0438\u043d\u0443\u043b\u0430 \u0432\u0430\u043c \u043f\u0430\u0440\u0443 \u043d\u043e\u0432\u044b\u0445 \u0438\u0434\u0435\u0439 \u0438 \u0442\u0435\u043c \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f. \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435. \u0423\u0434\u0430\u0447\u0438 \u0432\u0430\u043c \u0432 \u0432\u0430\u0448\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445!<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d5f\/95a\/7bc\/d5f95a7bccbce5db0b7e1cca321d4dba.png\" width=\"237\" height=\"230\"><figcaption><\/figcaption><\/figure>\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\/post\/561200\/\"> https:\/\/habr.com\/ru\/post\/561200\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<figure class=\"full-width\"><figcaption>\u0411\u0430\u0442\u0440\u0430\u043a \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0430\u0435\u0442 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u0438\u0433\u0440\u043e\u043a<\/figcaption><\/figure>\n<p><strong>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/strong><\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b Discord \u0431\u043e\u0442\u0430 \u0434\u043b\u044f World of Warcraft \u0433\u0438\u043b\u044c\u0434\u0438\u0438. \u041e\u043d \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0438\u0433\u0440\u044b \u0438 \u043f\u0438\u0448\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Discord \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u043a \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u043b\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u0438\u0433\u0440\u043e\u043a \u0438\u043b\u0438 \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u0433\u0438\u043b\u044c\u0434\u0438\u044e \u043f\u043e\u043a\u0438\u043d\u0443\u043b \u0441\u0442\u0430\u0440\u044b\u0439 \u0438\u0433\u0440\u043e\u043a. \u041c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439 \u043c\u044b \u043f\u0440\u043e\u0437\u0432\u0430\u043b\u0438 \u044d\u0442\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u0411\u0430\u0442\u0440\u0430\u043a.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442. \u041f\u043e \u0441\u0443\u0442\u0438 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 .NET Core: \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043b\u043e\u0433\u0438\u043a\u0443, \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 api \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u043f\u043e\u043a\u0440\u043e\u0435\u043c \u0442\u0435\u0441\u0442\u0430\u043c\u0438, \u0443\u043f\u0430\u043a\u0443\u0435\u043c \u0432 Docker \u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u0432 Heroku. \u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c continuous integration \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Github Actions.<\/p>\n<p><u>\u041e\u0442 \u0432\u0430\u0441 \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0437\u043d\u0430\u043d\u0438\u0439 \u043e\u0431 \u0438\u0433\u0440\u0435<\/u>. \u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0442\u0430\u043a \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0438\u0433\u0440\u044b \u0438 \u0441\u0434\u0435\u043b\u0430\u043b \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445. \u041d\u043e \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0432 Battle.net, \u0442\u043e \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430, \u043e\u0442 \u0432\u0430\u0441 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f \u0445\u043e\u0442\u044f \u0431\u044b \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u043f\u044b\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u0431 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 ASP.NET \u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker.<\/p>\n<p><strong>\u041f\u043b\u0430\u043d<\/strong><\/p>\n<p>\u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0448\u0430\u0433\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u043d\u0430\u0440\u0430\u0449\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b.<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 web api \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c \/check. \u041f\u0440\u0438     \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a \u044d\u0442\u043e\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443 \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u201cHello!\u201d \u0432 Discord \u0447\u0430\u0442.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u0441\u043e\u0441\u0442\u0430\u0432\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438\u043b\u0438 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0432 \u043a\u044d\u0448 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0445 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0441 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \u0441\u043f\u0438\u0441\u043a\u0430.     \u041e\u0431\u043e \u0432\u0441\u0435\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0432 Discord.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c Dockerfile \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430     \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435 Heroku.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0441\u0431\u043e\u0440\u043a\u0443, \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u0432 master<\/p>\n<\/li>\n<\/ol>\n<p><strong>\u0428\u0430\u0433 1. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 Discord<\/strong><\/p>\n<p>\u041d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 ASP.NET Core Web API \u043f\u0440\u043e\u0435\u043a\u0442.  <\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/tutorials\/first-web-api?view=aspnetcore-5.0&amp;tabs=visual-studio-code\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a> &#8212; \u044d\u0442\u043e \u043e\u0434\u043d\u0430 \u0438\u0437 \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c. \u041f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u043d\u0430\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441 Github \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430. \u0412 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 Github.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440<\/p>\n<pre><code class=\"cs\">[ApiController] public class GuildController : ControllerBase {     [HttpGet(\"\/check\")]     public async Task&lt;IActionResult&gt; Check(CancellationToken ct)     {         return Ok();     } }<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f webhook \u043e\u0442 \u0432\u0430\u0448\u0435\u0433\u043e Discord \u0441\u0435\u0440\u0432\u0435\u0440\u0430. Webhook &#8212; \u044d\u0442\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0442\u043e \u044d\u0442\u043e \u0430\u0434\u0440\u0435\u0441 \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0441\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 http \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u0432\u043d\u0443\u0442\u0440\u0438.<\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0432 \u043f\u0443\u043d\u043a\u0442\u0435 integrations \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043b\u044e\u0431\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u0430\u0448\u0435\u0433\u043e Discord \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<figure class=\"full-width\"><figcaption>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 webhook<\/figcaption><\/figure>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c webhook \u0432 appsettings.json \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041f\u043e\u0437\u0436\u0435 \u043c\u044b \u0443\u043d\u0435\u0441\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f Heroku. \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 \u0442\u0435\u043c \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 ASP Core \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0437\u0443\u0447\u0438\u0442\u0435 <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/fundamentals\/configuration\/?view=aspnetcore-5.0\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u0443 \u0442\u0435\u043c\u0443<\/a>.<\/p>\n<pre><code class=\"json\">{ \t\"DiscordWebhook\":\"https:\/\/discord.com\/api\/webhooks\/****\/***\" }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 DiscordBroker, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Discord. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043f\u0430\u043f\u043a\u0443 Services \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0442\u0443\u0434\u0430 \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u044d\u0442\u0430 \u043f\u0430\u043f\u043a\u0430 \u043d\u0430\u043c \u0435\u0449\u0435 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<p>\u041f\u043e \u0441\u0443\u0442\u0438 \u044d\u0442\u043e\u0442 \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u0435\u043b\u0430\u0435\u0442 post \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u0438\u0437 webhook \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u043b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p>\n<pre><code class=\"cs\">public class DiscordBroker : IDiscordBroker {     private readonly string _webhook;     private readonly HttpClient _client;      public DiscordBroker(IHttpClientFactory clientFactory, IConfiguration configuration)     {         _client = clientFactory.CreateClient();         _webhook = configuration[\"DiscordWebhook\"];     }      public async Task SendMessage(string message, CancellationToken ct)     {         var request = new HttpRequestMessage         {             Method = HttpMethod.Post,             RequestUri = new Uri(_webhook),             Content = new FormUrlEncodedContent(new[] {new KeyValuePair&lt;string, string&gt;(\"content\", message)})         };          await _client.SendAsync(request, ct);     } }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/aspnet\/core\/fundamentals\/dependency-injection?view=aspnetcore-5.0\" rel=\"noopener noreferrer nofollow\">\u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/a>. IConfiguration \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u044c webhook \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432, \u0430 IHttpClientFactory \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 HttpClient.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044f \u0438\u0437\u0432\u043b\u0435\u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 \u043f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438. \u0414\u0435\u043b\u0430\u0439\u0442\u0435 \u044d\u0442\u043e \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435.<\/p>\n<p>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0447\u0442\u043e \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IDiscordBroker, DiscordBroker&gt;();<\/code><\/pre>\n<p>\u0410 \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c HttpClient, \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b IHttpClientFactory.<\/p>\n<pre><code class=\"cs\">services.AddHttpClient();<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435.<\/p>\n<pre><code class=\"cs\">private readonly IDiscordBroker _discordBroker;  public GuildController(IDiscordBroker discordBroker) {   _discordBroker = discordBroker; }  [HttpGet(\"\/check\")] public async Task&lt;IActionResult&gt; Check(CancellationToken ct) {   await _discordBroker.SendMessage(\"Hello\", ct);   return Ok(); }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0435\u043a\u0442, \u0437\u0430\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \/check \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c \u0447\u0442\u043e \u0432 Discord \u043f\u0440\u0438\u0448\u043b\u043e \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435.<\/p>\n<p><strong>\u0428\u0430\u0433 2. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Battle.net<\/strong><\/p>\n<p>\u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430: \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 battle.net \u0438\u043b\u0438 \u0438\u0437 \u043c\u043e\u0435\u0439 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043d\u0435\u0442 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u0432 battle.net, \u0442\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u0443\u0441\u043e\u043a \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u0433\u0434\u0435 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438.<\/p>\n<p><strong>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435<\/strong><\/p>\n<p>\u0412\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"https:\/\/develop.battle.net\/\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/develop.battle.net\/<\/u><\/a> \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u0430\u043c \u0434\u0432\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0440\u043e\u043a\u0438 BattleNetId \u0438 BattleNetSecret. \u041e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u043d\u0443\u0436\u043d\u044b \u043d\u0430\u043c \u0447\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 api \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u041f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0438\u0445 \u0432 appsettings.<\/p>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/www.nuget.org\/packages\/ArgentPonyWarcraftClient\" rel=\"noopener noreferrer nofollow\">ArgentPonyWarcraftClient<\/a>.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 BattleNetApiClient \u0432 \u043f\u0430\u043f\u043a\u0435 Services.<\/p>\n<pre><code class=\"cs\">public class BattleNetApiClient {    private readonly string _guildName;    private readonly string _realmName;    private readonly IWarcraftClient _warcraftClient;     public BattleNetApiClient(IHttpClientFactory clientFactory, IConfiguration configuration)    {        _warcraftClient = new WarcraftClient(            configuration[\"BattleNetId\"],            configuration[\"BattleNetSecret\"],            Region.Europe,            Locale.ru_RU,            clientFactory.CreateClient()        );        _realmName = configuration[\"RealmName\"];        _guildName = configuration[\"GuildName\"];    } }<\/code><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 WarcraftClient.<br \/>\u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u043a \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0440\u0430\u043d\u0435\u0435. \u0421 \u0435\u0433\u043e \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0430\u0445.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432 appsettings \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u0432\u0435 \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0438 RealmName \u0438 GuildName. RealmName \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u043c\u0438\u0440\u0430, \u0430 GuildName \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438. \u0418\u0445 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435.<\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 GetGuildMembers \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u0430\u0432 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c WowCharacterToken \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u0431\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0435.<\/p>\n<pre><code class=\"cs\">public async Task&lt;WowCharacterToken[]&gt; GetGuildMembers() {    var roster = await _warcraftClient.GetGuildRosterAsync(_realmName, _guildName, \"profile-eu\");     if (!roster.Success) throw new ApplicationException(\"get roster failed\");     return roster.Value.Members.Select(x =&gt; new WowCharacterToken    {        WowId = x.Character.Id,        Name = x.Character.Name    }).ToArray(); }<\/code><\/pre>\n<pre><code class=\"cs\">public class WowCharacterToken {   public int WowId { get; set; }   public string Name { get; set; } }<\/code><\/pre>\n<p>\u041a\u043b\u0430\u0441\u0441 WowCharacterToken \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043f\u0430\u043f\u043a\u0443 Models.<\/p>\n<p>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c BattleNetApiClient \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IBattleNetApiClient, BattleNetApiClient&gt;();<\/code><\/pre>\n<p><strong>\u0411\u0435\u0440\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438<\/strong><\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c WowCharacterToken \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0435\u0435 \u0432 \u043f\u0430\u043f\u043a\u0443 Models. \u041e\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0433\u0440\u043e\u043a\u0435.<\/p>\n<pre><code class=\"cs\">public class WowCharacterToken {   public int WowId { get; set; }   public string Name { get; set; } }<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0432\u043e\u0442 \u0442\u0430\u043a\u043e\u0439 \u043a\u043b\u0430\u0441\u0441<\/p>\n<pre><code class=\"cs\">public class BattleNetApiClient {     private bool _firstTime = true;      public Task&lt;WowCharacterToken[]&gt; GetGuildMembers()     {         if (_firstTime)         {             _firstTime = false;              return Task.FromResult(new[]             {                 new WowCharacterToken                 {                     WowId = 1,                     Name = \"\u0410\u0440\u0442\u0430\u0441\"                 },                 new WowCharacterToken                 {                     WowId = 2,                     Name = \"\u0421\u0438\u043b\u044c\u0432\u0430\u043d\u0430\"                 }             });         }          return Task.FromResult(new[]         {             new WowCharacterToken             {                 WowId = 1,                 Name = \"\u0410\u0440\u0442\u0430\u0441\"             },             new WowCharacterToken             {                 WowId = 3,                 Name = \"\u041d\u0435\u043f\u043e\u0431\u0435\u0434\u0438\u043c\u044b\u0439\"             }         });     } }<\/code><\/pre>\n<p>\u041e\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0437\u0430\u0448\u0438\u0442\u044b\u0439 \u0432 \u043d\u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432. \u041f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u043c\u044b \u0432\u0435\u0440\u043d\u0435\u043c \u043e\u0434\u0438\u043d \u0441\u043f\u0438\u0441\u043e\u043a, \u043f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0434\u0440\u0443\u0433\u043e\u0439. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u043c \u0447\u0442\u043e \u0441\u043c\u043e\u0434\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0447\u0438\u0432\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 api. \u042d\u0442\u043e\u0439 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438 \u0445\u0432\u0430\u0442\u0438\u0442 \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442.<\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0432\u0441\u0435 \u0447\u0442\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0432 Startup.<\/p>\n<pre><code class=\"cs\">services.AddScoped&lt;IBattleNetApiClient, BattleNetApiClient&gt;();<\/code><\/pre>\n<p><strong>\u0412\u044b\u0432\u0435\u0434\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432 Discord<\/strong><\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u043b\u0438 BattleNetApiClient, \u0438\u043c \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043a\u043e\u043b-\u0432\u043e \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0432 Discord.<\/p>\n<pre><code class=\"cs\">[ApiController] public class GuildController : ControllerBase {   private readonly IDiscordBroker _discordBroker;   private readonly IBattleNetApiClient _battleNetApiClient;    public GuildController(IDiscordBroker discordBroker, IBattleNetApiClient battleNetApiClient)   {      _discordBroker = discordBroker;      _battleNetApiClient = battleNetApiClient;   }    [HttpGet(\"\/check\")]   public async Task&lt;IActionResult&gt; Check(CancellationToken ct)   {      var members = await _battleNetApiClient.GetGuildMembers();      await _discordBroker.SendMessage($\"Members count: {members.Length}\", ct);      return Ok();   } }<\/code><\/pre>\n<p><strong>\u0428\u0430\u0433 3. \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0445 \u0438 \u0443\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0433\u0440\u043e\u043a\u043e\u0432<\/strong><\/p>\n<p>\u041d\u0443\u0436\u043d\u043e \u043d\u0430\u0443\u0447\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0438\u0433\u0440\u043e\u043a\u0438 \u043f\u043e\u044f\u0432\u0438\u043b\u0438\u0441\u044c \u0438\u043b\u0438 \u043f\u0440\u043e\u043f\u0430\u043b\u0438 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u043a api. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0432 InMemory \u043a\u044d\u0448\u0435 (\u0432 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438) \u0438\u043b\u0438 \u0432\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0437\u0430\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0432 InMemory \u043a\u044d\u0448\u0435, \u0442\u043e \u043c\u044b \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0435<\/p>\n<\/p>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-324397","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/324397","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=324397"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/324397\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=324397"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=324397"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=324397"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}