Blog Dla Programistów C#/.NET

Błyskawiczna aplikacja ASP.NET Core - praktyczne techniki optymalizacji dla architektów

poniedziałek, 27 lipca 2026 Tagi: C#/.NETASP.NET MVCASP.NET CoreASP.NET Web APIProgramowanie

W aplikacjach webowych wydajność to nie kosmetyka, tylko realny wpływ na doświadczenie użytkownika i na to, czy dotrzymasz obietnic z SLA. Wolna aplikacja odbija się na wszystkim: użytkownicy porzucają koszyki, monitoring zaczyna świecić na czerwono, a Ty zamiast rozwijać produkt gasisz pożary. Jako lider czy architekt masz jednak sporo dźwigni, którymi możesz to zmienić i wiele z nich kosztuje niewiele wysiłku, a daje natychmiastowy efekt.

Poniżej 3 obszary, które w praktyce dają najszybszy zwrot: cache'owanie, kompresja odpowiedzi oraz asynchroniczność end-to-end. Do każdego znajdziesz gotowy kod i pułapki, o których warto powiedzieć zespołowi, zanim trafią na produkcję.

Błyskawiczna aplikacja ASP.NET Core - praktyczne techniki optymalizacji dla architektów

1. Wykorzystaj cache'owanie (buforowanie)


Najprostszy sposób na przyspieszenie aplikacji to nieliczenie po raz drugi tego, co już policzyłeś. Zamiast przy każdym żądaniu odpytywać bazę danych czy zewnętrzne API, przechowaj wynik w pamięci podręcznej na określony czas.

ASP.NET Core daje Ci do wyboru cache w pamięci procesu (IMemoryCache) oraz cache rozproszony (IDistributedCache, np. w oparciu o Redis). Wybierasz w zależności od architektury: pamięć lokalna dla pojedynczej instancji, cache rozproszony współdzielony między serwerami, gdy działasz na wielu instancjach za load balancerem. Buforowanie często pobieranych danych - o ile chwilowa "nieświeżość" jest akceptowalna - znacząco odciąża bazę i skraca czas odpowiedzi.

W praktyce najczęściej sięgniesz po wzorzec get-or-create, który sam sprawdza cache i uzupełnia go tylko przy pudle:

public class ProductService
{
private readonly IMemoryCache _cache;
private readonly IProductRepository _repository;

public ProductService(IMemoryCache cache, IProductRepository repository)
{
_cache = cache;
_repository = repository;
}

public Task<Product?> GetProductAsync(int id, CancellationToken ct)
{
return _cache.GetOrCreateAsync($"product_{id}", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
entry.SetSize(1); /* gdy skonfigurowałeś limit rozmiaru cache */
return await _repository.GetByIdAsync(id, ct);
});
}
}

Pamiętaj o zarejestrowaniu usługi (builder.Services.AddMemoryCache();) oraz o dwóch rzeczach, które w cache'u zawsze wracają jak bumerang: strategii wygasania (czas absolutny vs. przesuwany) i inwalidacji - czyli usuwaniu wpisu, gdy dane realnie się zmienią. Cache, którego nie potrafisz unieważnić, prędzej czy później zacznie serwować nieaktualne dane.

Cache'owanie całych odpowiedzi HTTP


Jeśli kosztowna jest sama generacja odpowiedzi (widok, raport, endpoint agregujący dane), a wynik nie zmienia się często - warto buforować całą odpowiedź. Masz tu 2 opcje:
 • [ResponseCache] ustawia nagłówki cache (Cache-Control), pozwalając przeglądarce lub proxy pośredniczącemu przechować kopię. To cache po stronie klienta/pośrednika - serwer wciąż może zostać poproszony o ponowne wygenerowanie.
 • Output caching (od .NET 7) buforuje gotowy wynik po stronie serwera, więc kolejne wywołania nie uruchamiają już logiki:

builder.Services.AddOutputCache();
/* ... */
app.UseOutputCache();

app.MapGet("/produkty", GetProductsAsync)
.CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(1)));

Dzięki temu powtarzające się wywołania tego samego endpointu nie wykonują drogich operacji, zwracają wcześniej przygotowaną odpowiedź prosto z cache.

2. Włącz kompresję odpowiedzi


Mniejsza odpowiedź to szybsze ładowanie u użytkownika. Dobre praktyki są tu jednoznaczne: zmniejszenie rozmiaru odpowiedzi zwykle wyraźnie poprawia responsywność aplikacji. Wystarczy dołożyć middleware kompresji - GZIP (obsługiwany przez wszystkie przeglądarki) oraz nowszy Brotli (najwyższy stopień kompresji). Oba są wspierane natywnie i negocjowane automatycznie: klient w nagłówku Accept-Encoding informuje, co obsługuje, a serwer kompresuje w najlepszy dostępny sposób.

builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true; /* kompresja także dla HTTPS */
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});

var app = builder.Build();

app.UseResponseCompression(); /* wysoko w potoku, przed static files */

Efekt? Treści tekstowe (HTML, CSS, JSON) potrafią zmaleć o 70–80%, a użytkownik odczuje szybsze działanie aplikacji bez żadnych zmian w logice biznesowej. Koszt CPU na kompresję jest zwykle znikomy wobec zysku na czasie transferu.

Kilka rzeczy, o których warto wiedzieć, zanim wrzucisz to na produkcję:
 • Nie kompresuj tego, co już skompresowane. Obrazy .png/.jpg, pliki .zip czy wideo nie zyskają nic, a niepotrzebnie obciążą procesor.
 • Statykę lepiej kompresować raz, na etapie builda (pre-kompresja) i serwować gotowe pliki .br/.gz, zamiast kompresować je w locie przy każdym żądaniu.
 • EnableForHttps = true ma swój kontekst bezpieczeństwa. Łączenie kompresji z HTTPS bywa wektorem ataków typu BREACH, gdy w odpowiedzi mieszasz sekrety (np. tokeny) z danymi kontrolowanymi przez użytkownika. Dla typowej aplikacji ryzyko jest niskie, ale warto mieć tę świadomość i nie kompresować odpowiedzi zawierających wrażliwe wartości odbite z żądania.

3. Stosuj asynchroniczność end-to-end


ASP.NET Core zaprojektowano do obsługi wielu żądań równocześnie, ale tylko wtedy, gdy Twój kod mu na to pozwala. Metody asynchroniczne sprawiają, że niewielka pula wątków obsłuży tysiące równoległych żądań, bo wątek nie stoi bezczynnie czekając na operację I/O. W tym czasie obsługuje inne zapytania. Blokowanie wątków na operacjach wejścia/wyjścia prowadzi do tzw. głodu wątków (thread starvation) i lawinowego wzrostu czasów odpowiedzi pod obciążeniem.

Zasada jest prosta: async/await w całym łańcuchu wywołań - od warstwy dostępu do danych, przez serwisy, aż po akcje kontrolera. Porównaj:

/* ŹLE – blokuje wątek, ryzyko deadlocka i głodu wątków */
public IActionResult GetOrder(int id)
{
var order = _repository.GetOrderAsync(id).Result;
return Ok(order);
}

/* DOBRZE – asynchronicznie od góry do dołu, z propagacją CancellationToken */
public async Task<IActionResult> GetOrder(int id, CancellationToken ct)
{
var order = await _repository.GetOrderAsync(id, ct);
return Ok(order);
}

Czego unikać:
 • .Result i .Wait() na zadaniach asynchronicznych - wymuszają blokowanie wątku i niweczą korzyści z async (a w niektórych kontekstach grożą zakleszczeniem).
 • Task.Run jako obejście braku metody async - to tylko przerzucenie pracy na inny wątek z dodatkowym narzutem, które niczego nie rozwiązuje.
 • async void poza obsługą zdarzeń - wyjątek z takiej metody potrafi położyć proces.

I jeszcze jedno: przekazuj CancellationToken przez cały łańcuch aż do bazy i klientów HTTP. Gdy użytkownik zerwie połączenie, przerwiesz niepotrzebną już pracę zamiast trzymać zasoby do końca.

Jeśli natomiast operacja jest naprawdę długotrwała albo mocno obciąża CPU, wyciągnij ją poza cykl żądania HTTP - do BackgroundService, kolejki zadań albo dedykowanego workera. Cykl żądania ma zwrócić odpowiedź, a nie mielić dane przez minutę.

Podsumowanie


Optymalizacja ASP.NET Core to zestaw dźwigni o różnym koszcie i różnym zwrocie. Skupiliśmy się na 3, które dają najszybszy efekt: buforowanie danych i odpowiedzi, kompresja przesyłanych danych oraz nieblokujące przetwarzanie żądań. To techniki, które często widać w metrykach niemal od razu.

Gdy te podstawy masz już opanowane, warto zajrzeć głębiej:
 • Efektywne zapytania do bazy - w EF Core sięgaj po AsNoTracking() dla odczytów, projektuj tylko potrzebne kolumny i tęp problem N+1, zanim urośnie.
 • HttpClient przez IHttpClientFactory - zamiast tworzyć go ręcznie i wyczerpywać pulę gniazd (socket exhaustion).
 • Mniej alokacji na gorącej ścieżce - unikaj zbędnych alokacji i obsługi wyjątków w często wykonywanym kodzie. Wyjątki nie są mechanizmem sterowania przepływem.
 • Najnowszy .NET - każda kolejna wersja platformy przynosi usprawnienia wydajności "za darmo", samą aktualizacją.

Chcesz więcej takich konkretów?


Wydajność to temat-rzeka, zawsze czeka kolejny profiler, kolejny benchmark i kolejny moment "aha!", w którym rozumiesz, dlaczego ta jedna metoda zżera 40% czasu żądania. Jeśli chcesz regularnie dostawać takie praktyczne wskazówki o .NET, wydajności i architekturze - konkretne, gotowe do użycia w Twoim kodzie - dołącz do mojej listy VIP:
modestprogrammer.pl/vip

To miejsce dla programistów, którzy nie chcą stać w miejscu: dzielę się tam materiałami, których często nie publikuję nigdzie indziej, i piszę bez lania wody - prosto z doświadczenia. Zapisujesz się w kilka sekund, a zyskujesz stały dopływ wiedzy, która realnie przekłada się na Twoje projekty.

Powodzenia w optymalizowaniu aplikacji i do zobaczenia na liście.

Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
CTO & Founder - FindSolution.pl
Programista C#/.NET. Specjalizuje się w Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Dodaj komentarz
© Copyright 2026 modestprogrammer.pl | Sztuczna Inteligencja | Regulamin | Polityka prywatności. Design by Kazimierz Szpin. Wszelkie prawa zastrzeżone.