Osvědčené postupy pro ASP.NET Core
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Autor: Mike Rousos
Tento článek obsahuje pokyny pro maximalizaci výkonu a spolehlivosti aplikací ASP.NET Core.
Agresivně ukládat do mezipaměti
Ukládání do mezipaměti je popsáno v několika částech tohoto článku. Další informace najdete v tématu Přehled ukládání do mezipaměti v ASP.NET Core.
Principy horkých cest kódu
V tomto článku je cesta horkého kódu definována jako cesta kódu, která se často volá a kde dochází k velké části doby provádění. Horké cesty kódu obvykle omezují škálování aplikace na více instancí a výkon a jsou popsány v několika částech tohoto článku.
Vyhněte se blokování volání
ASP.NET aplikace Core by měly být navrženy tak, aby zpracovávaly mnoho požadavků současně. Asynchronní rozhraní API umožňují malému fondu vláken zpracovávat tisíce souběžných požadavků tím, že nečeká na blokující volání. Místo čekání na dokončení dlouhotrvající synchronní úlohy může vlákno fungovat na jiném požadavku.
Běžným problémem s výkonem v aplikacích ASP.NET Core je blokování volání, která by mohla být asynchronní. Mnoho synchronních blokujících volání vede k hladovění fondu vláken a snížení doby odezvy.
Neblokujte asynchronní provádění voláním Task.Wait nebo Task<TResult>.Result.
Nezískajte zámky v běžných cestách kódu. ASP.NET aplikace Core fungují nejlépe, když jsou navrženy tak, aby spouštěly kód paralelně.
Nevolejte Task.Run a okamžitě ho nečekejte. ASP.NET Core už spouští kód aplikace na normálních vláknech fondu vláken, takže volání Task.Run
vede pouze k nadbytečné plánování fondu vláken. I když by naplánovaný kód blokoval vlákno, Task.Run
nezabrání tomu.
- Proveďte asynchronní cesty s horkým kódem.
- Volání přístupu k datům, vstupně-výstupních a dlouhotrvajících rozhraní API operací asynchronně v případě, že je k dispozici asynchronní rozhraní API.
- Nepoužívejte Task.Run k asynchronnímu vytvoření synchronního rozhraní API.
- Proveďte asynchronní akce kontroleru neboRazor stránky. Celý zásobník volání je asynchronní, aby bylo možné využívat vzory async/await .
- Zvažte použití zprostředkovatelů zpráv, jako je Azure Service Bus k přesměrování dlouhotrvajících volání.
Profiler, například PerfView, lze použít k vyhledání vláken často přidávaných do fondu vláken. Událost Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start
označuje vlákno přidané do fondu vláken.
Vrácení velkých kolekcí na více menších stránkách
Webová stránka by neměla načítat velké objemy dat najednou. Při vracení kolekce objektů zvažte, jestli by to mohlo vést k problémům s výkonem. Určete, jestli by návrh mohl vést k následujícím špatným výsledkům:
- OutOfMemoryException nebo vysoká spotřeba paměti
- Hladový fond vláken (viz následující poznámky o IAsyncEnumerable<T>)
- Pomalé doby odezvy
- Časté uvolňování paměti
Přidáním stránkování zmírníte předchozí scénáře. Při použití parametrů velikosti stránky a indexu stránky by vývojáři měli upřednostnění návrhu vrácení částečného výsledku. Pokud je požadován úplný výsledek, stránkování by se mělo použít k asynchronnímu naplnění dávek výsledků, aby nedocházelo k zamykání prostředků serveru.
Další informace o stránkování a omezení počtu vrácených záznamů najdete tady:
Vrácení nebo vrácení IEnumerable<T>
IAsyncEnumerable<T>
Vrácení IEnumerable<T>
z akce vede k synchronní iteraci kolekce serializátorem. Výsledkem je blokování volání a potenciál hladovění fondu vláken. Pokud se chcete vyhnout synchronnímu výčtu, použijte ToListAsync
před vrácením výčtu.
Počínaje ASP.NET Core 3.0 IAsyncEnumerable<T>
lze použít jako alternativu k IEnumerable<T>
výčtu asynchronně. Další informace naleznete v tématu Návratové typy akcí kontroleru.
Minimalizace přidělování velkých objektů
Uvolňování paměti .NET Core spravuje přidělení a uvolnění paměti automaticky v aplikacích ASP.NET Core. Automatické uvolňování paměti obecně znamená, že se vývojáři nemusí starat o to, jak nebo kdy je paměť uvolněna. Čištění neodkazovaných objektů však trvá čas procesoru, takže vývojáři by měli minimalizovat přidělování objektů v horkých cestách kódu. Uvolňování paměti je zvláště nákladné u velkých objektů (>= 85 000 bajtů). Velké objekty jsou uloženy na velké haldě objektů a vyžadují úplné uvolňování paměti (generace 2) k vyčištění. Na rozdíl od 0. generace a 1. generace kolekce vyžaduje dočasné pozastavení spouštění aplikací. Časté přidělování a rušení přidělování velkých objektů může způsobit nekonzistentní výkon.
Doporučení:
- Zvažte ukládání velkých objektů do mezipaměti, které se často používají. Ukládání velkých objektů do mezipaměti brání drahým přidělením.
- Ukládání velkých polí do vyrovnávací paměti fondu.ArrayPool<T>
- Nepřidělujte mnoho krátkodobých velkých objektů na cestách s horkým kódem.
Problémy s pamětí, například předchozí, je možné diagnostikovat kontrolou statistik uvolňování paměti (GC) v nástroji PerfView a zkoumáním:
- Doba pozastavení uvolňování paměti.
- Jaké procento času procesoru se stráví v uvolňování paměti.
- Kolik uvolňování paměti je generace 0, 1 a 2.
Další informace naleznete v tématu Uvolňování paměti a výkon.
Optimalizace přístupu k datům a vstupně-výstupních operací
Interakce s úložištěm dat a dalšími vzdálenými službami jsou často nejpomalejšími částmi aplikace ASP.NET Core. Efektivní čtení a zápis dat je důležité pro dobrý výkon.
Doporučení:
- Volání všech rozhraní API pro přístup k datům asynchronně
- Nenačítejte více dat, než je nutné. Zapište dotazy, které vrátí jenom data potřebná pro aktuální požadavek HTTP.
- Zvažte ukládání často přístupných dat do mezipaměti načtených z databáze nebo vzdálené služby, pokud je přijatelná mírně zaplněná data. V závislosti na scénáři použijte MemoryCache nebo DistributedCache. Další informace najdete v tématu Ukládání odpovědí do mezipaměti v ASP.NET Core.
- Minimalizujte dobu odezvy sítě. Cílem je načíst požadovaná data v jednom volání místo několika volání.
- Při přístupu k datům pro účely jen pro čtení nepoužívejte dotazy bez sledování v Entity Framework Core. EF Core může efektivněji vracet výsledky dotazů bez sledování.
- Vyfiltrujte a agregujte dotazy LINQ (například s
.Where
příkazy ,.Select
nebo.Sum
příkazy), aby filtrování prováděla databáze. - Zvažte , že EF Core některé operátory dotazů na klientovi vyřeší, což může vést k neefektivnímu provádění dotazů. Další informace najdete v tématu Problémy s výkonem vyhodnocení klienta.
- Nepoužívejte projekční dotazy u kolekcí, což může vést k provádění dotazů SQL "N + 1". Další informace najdete v tématu Optimalizace korelovaných poddotazů.
Následující přístupy můžou zlepšit výkon ve vysoce škálovatelných aplikacích:
Před potvrzením základu kódu doporučujeme měřit dopad předchozích přístupů s vysokým výkonem. Další složitost kompilovaných dotazů nemusí ospravedlnit zlepšení výkonu.
Problémy s dotazy je možné zjistit kontrolou času stráveného přístupem k datům pomocí Application Insights nebo pomocí nástrojů pro profilaci. Většina databází také zpřístupní statistiky týkající se často spouštěných dotazů.
Sdružování připojení HTTP pomocí HttpClientFactory
I když HttpClient implementuje IDisposable
rozhraní, je navržené pro opakované použití. Zavřené HttpClient
instance ponechá sokety otevřené ve TIME_WAIT
stavu po krátkou dobu. Pokud se často používá cesta kódu, která vytváří a odstraňuje HttpClient
objekty, aplikace může vyčerpat dostupné sokety. HttpClientFactory
byl představen v ASP.NET Core 2.1 jako řešení tohoto problému. Zpracovává sdružování připojení HTTP za účelem optimalizace výkonu a spolehlivosti. Další informace najdete v tématu Použití HttpClientFactory
k implementaci odolných požadavků HTTP.
Doporučení:
- Nevytvádřujte a nelikvidujte
HttpClient
instance přímo. - K načtení instancí použijte HttpClientFactory
HttpClient
. Další informace najdete v tématu Použití HttpClientFactory k implementaci odolných požadavků HTTP.
Rychlé udržování běžných cest kódu
Chcete, aby byl veškerý kód rychlý. Nejčastějšími cestami kódu, které se nazývají, jsou pro optimalizaci nejdůležitější. Tady jsou některé z nich:
- Komponenty middlewaru v kanálu zpracování požadavků aplikace, zejména middleware běží v rané fázi kanálu. Tyto komponenty mají velký dopad na výkon.
- Kód, který se spouští pro každý požadavek nebo několikrát na požadavek. Například vlastní protokolování, obslužné rutiny autorizace nebo inicializace přechodných služeb.
Doporučení:
- Nepoužívejte vlastní komponenty middlewaru s dlouhotrvajícími úlohami.
- K identifikaci horkých cest kódu použijte nástroje pro profilaci výkonu, jako jsou diagnostické nástroje sady Visual Studio nebo PerfView.
Provádění dlouhotrvajících úloh mimo požadavky HTTP
Většinu požadavků na aplikaci ASP.NET Core může zpracovat kontroler nebo model stránky, který volá nezbytné služby a vrací odpověď HTTP. U některých požadavků, které zahrnují dlouhotrvající úlohy, je lepší, aby celý proces odezvy požadavku byl asynchronní.
Doporučení:
- Nečekejte na dokončení dlouhotrvajících úloh jako součást běžného zpracování požadavků HTTP.
- Zvažte zpracování dlouhotrvajících požadavků se službami na pozadí nebo mimo proces, případně pomocí funkce Azure Nebo pomocí zprostředkovatele zpráv, jako je Azure Service Bus. Dokončení práce mimo proces je obzvláště přínosné pro úlohy náročné na procesor.
- K asynchronní komunikaci s klienty používejte možnosti komunikace v reálném čase, například SignalR.
Minify klientských prostředků
ASP.NET základní aplikace se složitými front-endy často obsluhuje mnoho souborů JavaScriptu, CSS nebo obrázků. Výkon počátečních požadavků na načtení je možné vylepšit pomocí:
- Bundling, který kombinuje více souborů do jednoho.
- Minifikace, která zmenšuje velikost souborů odebráním prázdných znaků a komentářů
Doporučení:
- Používejte pokyny pro sdružování a minifikace, které zmiňují kompatibilní nástroje a ukazují, jak používat značku ASP.NET Core
environment
ke zpracování prostředíDevelopment
iProduction
prostředí. - Zvažte další nástroje třetích stran, jako je webpack, pro komplexní správu klientských prostředků.
Komprimovat odpovědi
Zmenšení velikosti odpovědi obvykle zvyšuje rychlost odezvy aplikace, často výrazně. Jedním ze způsobů, jak snížit velikost datové části, je komprimovat odpovědi aplikace. Další informace naleznete v tématu Komprese odpovědí.
Použití nejnovější verze ASP.NET Core
Každá nová verze ASP.NET Core zahrnuje vylepšení výkonu. Optimalizace v .NET Core a ASP.NET Core znamenají, že novější verze obecně mají vyšší výkon než starší verze. Například .NET Core 2.1 přidala podporu zkompilovaných regulárních výrazů a využila výhod span<T>. ASP.NET Core 2.2 přidala podporu pro HTTP/2. ASP.NET Core 3.0 přidává mnoho vylepšení , která snižují využití paměti a zlepšují propustnost. Pokud je výkon prioritou, zvažte upgrade na aktuální verzi ASP.NET Core.
Minimalizace výjimek
Výjimky by měly být vzácné. Vyvolání a zachycení výjimek je pomalé vzhledem k jiným vzorům toku kódu. Z tohoto důvodu by se výjimky neměly používat k řízení normálního toku programu.
Doporučení:
- Nepoužívejte vyvolání nebo zachytávání výjimek jako způsob normálního toku programu, zejména v horkých cestách kódu.
- Do aplikace zahrňte logiku pro detekci a zpracování podmínek, které by způsobily výjimku.
- Vyvolejte nebo zachyťte výjimky pro neobvyklé nebo neočekávané podmínky.
Diagnostické nástroje aplikací, jako je Application Insights, můžou pomoct identifikovat běžné výjimky v aplikaci, které můžou ovlivnit výkon.
Vyhněte se synchronnímu čtení nebo zápisu v textu HttpRequest/HttpResponse
Všechny vstupně-výstupní operace v ASP.NET Core jsou asynchronní. Servery implementují Stream
rozhraní, které má synchronní i asynchronní přetížení. Asynchronní vlákna by se měla upřednostňovat, aby nedocházelo k blokování vláken ve fondu vláken. Blokování vláken může vést k hladovění fondu vláken.
Nedělejte to: Následující příklad používá ReadToEnd. Blokuje aktuální vlákno a čeká na výsledek. Toto je příklad synchronizace přes asynchronní synchronizaci.
public class BadStreamReaderController : Controller
{
[HttpGet("/contoso")]
public ActionResult<ContosoData> Get()
{
var json = new StreamReader(Request.Body).ReadToEnd();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
V předchozím kódu Get
synchronně čte celé tělo požadavku HTTP do paměti. Pokud se klient pomalu nahrává, aplikace se synchronizuje přes async. Aplikace se synchronizuje přes asynchronní, protože Kestrel nepodporuje synchronní čtení.
Udělejte to: Následující příklad používá ReadToEndAsync a neblokuje vlákno při čtení.
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
var json = await new StreamReader(Request.Body).ReadToEndAsync();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
Předchozí kód asynchronně přečte celé tělo požadavku HTTP do paměti.
Upozorňující
Pokud je požadavek velký, čtení celého textu požadavku HTTP do paměti může vést k podmínce OOM (nedostatek paměti). Objekt OOM může vést k odepření služby. Další informace naleznete v tématu Vyhněte se čtení velkých těla požadavků nebo těla odpovědí do paměti v tomto článku.
Udělejte to: Následující příklad je plně asynchronní pomocí textu požadavku, který není ve vyrovnávací paměti:
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
}
}
Předchozí kód asynchronně de-serializuje tělo požadavku do objektu C#.
Preferovat ReadFormAsync před Request.Form
Používejte HttpContext.Request.ReadFormAsync
místo HttpContext.Request.Form
.
HttpContext.Request.Form
může být bezpečně čteno pouze s následujícími podmínkami:
- Formulář byl přečtena voláním
ReadFormAsync
a - Hodnota formuláře uložené v mezipaměti se čte pomocí
HttpContext.Request.Form
Nedělejte to: Následující příklad používá HttpContext.Request.Form
. HttpContext.Request.Form
používá synchronizaci přes asynchronní a může vést k hladovění fondu vláken.
public class BadReadController : Controller
{
[HttpPost("/form-body")]
public IActionResult Post()
{
var form = HttpContext.Request.Form;
Process(form["id"], form["name"]);
return Accepted();
}
Udělejte to: Následující příklad používá HttpContext.Request.ReadFormAsync
k asynchronnímu čtení těla formuláře.
public class GoodReadController : Controller
{
[HttpPost("/form-body")]
public async Task<IActionResult> Post()
{
var form = await HttpContext.Request.ReadFormAsync();
Process(form["id"], form["name"]);
return Accepted();
}
Vyhněte se čtení velkých těl požadavků nebo těla odpovědí do paměti.
V .NET skončí každé přidělení objektů větší nebo rovno 85 000 bajtů v haldě velkého objektu (LOH). Velké objekty jsou nákladné dvěma způsoby:
- Náklady na přidělení jsou vysoké, protože je nutné vymazat paměť pro nově přidělený velký objekt. CLR zaručuje, že se vymaže paměť pro všechny nově přidělené objekty.
- LOH se shromažďuje s rest haldou. LOH vyžaduje úplné uvolňování paměti nebo kolekci Gen2.
Tento blogový příspěvek popisuje problém stručně:
Když je přidělen velký objekt, označí se jako objekt Gen2. Ne Gen 0 jako pro malé objekty. Důsledky jsou, že pokud v LOH dojde nedostatek paměti, GC vyčistí celou spravovanou haldu, nejen LOH. Proto vyčistí gen 0, Gen 1 a Gen2 včetně LOH. Tomu se říká úplné uvolňování paměti a je to časově nejnáročnější uvolňování paměti. U mnoha aplikací může být přijatelné. Ale rozhodně ne pro vysoce výkonné webové servery, kde je potřeba několik vyrovnávacích pamětí pro zpracování průměrného webového požadavku (čtení ze soketu, dekomprimace, dekódování JSON a další).
Uložení velkého textu požadavku nebo odpovědi do jednoho byte[]
nebo string
:
- Může vést k rychlému výpadku místa v LOH.
- Může způsobit problémy s výkonem aplikace kvůli úplnému spuštění GCS.
Práce s synchronním rozhraním API pro zpracování dat
Při použití serializátoru/de-serializátoru, který podporuje pouze synchronní čtení a zápisy (například Json.NET):
- Data před předáním do serializátoru/de-serializátoru uložíte do vyrovnávací paměti asynchronně.
Upozorňující
Pokud je požadavek velký, může to vést k nedostatku paměti (OOM). Objekt OOM může vést k odepření služby. Další informace naleznete v tématu Vyhněte se čtení velkých těla požadavků nebo těla odpovědí do paměti v tomto článku.
ASP.NET Core 3.0 používá System.Text.Json ve výchozím nastavení serializaci JSON. System.Text.Json:
- Čte a zapisuje JSON asynchronně.
- Je optimalizovaný pro text UTF-8.
- Obvykle je vyšší výkon než
Newtonsoft.Json
.
Neukládejte do pole IHttpContextAccessor.HttpContext.
IHttpContextAccessor.HttpContext vrátí HttpContext
aktivní požadavek při přístupu z vlákna požadavku. Hodnota by neměla být uložena IHttpContextAccessor.HttpContext
v poli nebo proměnné.
Nedělejte to: Následující příklad uloží pole HttpContext
do pole a pokusí se ho použít později.
public class MyBadType
{
private readonly HttpContext _context;
public MyBadType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
Předchozí kód často zachycuje hodnotu null nebo nesprávnou hodnotu HttpContext
v konstruktoru.
Udělejte to: Následující příklad:
- Uloží pole IHttpContextAccessor .
HttpContext
Používá pole ve správný čas a kontrolujenull
.
public class MyGoodType
{
private readonly IHttpContextAccessor _accessor;
public MyGoodType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
Nepřistupujte k httpContext z více vláken
HttpContext
není bezpečný pro přístup z více vláken. HttpContext
Přístup z více vláken paralelně může vést k neočekávanému chování, jako je například zastavení reakce serveru, chybové ukončení a poškození dat.
Nedělejte to: Následující příklad vytvoří tři paralelní požadavky a zaprokoluje cestu příchozího požadavku před a za odchozím požadavkem HTTP. Cesta požadavku je přístupná z více vláken, potenciálně paralelně.
public class AsyncBadSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
var query1 = SearchAsync(SearchEngine.Google, query);
var query2 = SearchAsync(SearchEngine.Bing, query);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
HttpContext.Request.Path);
searchResults = _searchService.Search(engine, query);
_logger.LogInformation("Finishing search query from {path}.",
HttpContext.Request.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}",
HttpContext.Request.Path);
}
return await searchResults;
}
Udělejte to: Následující příklad zkopíruje všechna data z příchozího požadavku před provedením tří paralelních požadavků.
public class AsyncGoodSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
string path = HttpContext.Request.Path;
var query1 = SearchAsync(SearchEngine.Google, query,
path);
var query2 = SearchAsync(SearchEngine.Bing, query, path);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
string path)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
path);
searchResults = await _searchService.SearchAsync(engine, query);
_logger.LogInformation("Finishing search query from {path}.", path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}", path);
}
return await searchResults;
}
Po dokončení požadavku nepoužívejte httpContext.
HttpContext
platí pouze za předpokladu, že v kanálu ASP.NET Core existuje aktivní požadavek HTTP. Celý kanál ASP.NET Core je asynchronní řetězec delegátů, který spouští všechny požadavky. Po dokončení vráceného Task
z tohoto řetězce se HttpContext
recykluje.
Nedělejte to: Následující příklad používá, async void
aby se požadavek HTTP dokončil při prvním await
dosažení:
- Použití
async void
je vždy špatným postupem v aplikacích ASP.NET Core. - Ukázkový kód přistupuje po
HttpResponse
dokončení požadavku HTTP. - Pozdní přístup proces chybově ukončí.
public class AsyncBadVoidController : Controller
{
[HttpGet("/async")]
public async void Get()
{
await Task.Delay(1000);
// The following line will crash the process because of writing after the
// response has completed on a background thread. Notice async void Get()
await Response.WriteAsync("Hello World");
}
}
Udělejte to: Následující příklad vrátí Task
rozhraní, takže požadavek HTTP se nedokončí, dokud se akce nedokončí.
public class AsyncGoodTaskController : Controller
{
[HttpGet("/async")]
public async Task Get()
{
await Task.Delay(1000);
await Response.WriteAsync("Hello World");
}
}
Nezachycujte httpContext ve vláknech na pozadí.
Nedělejte to: Následující příklad ukazuje, že uzavření zachycuje HttpContext
z Controller
vlastnosti. Toto je špatný postup, protože pracovní položka by mohla:
- Spusťte mimo obor požadavku.
- Pokus o přečtení nesprávného souboru
HttpContext
.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
var path = HttpContext.Request.Path;
Log(path);
});
return Accepted();
}
Udělejte to: Následující příklad:
- Zkopíruje data požadovaná v úloze na pozadí během požadavku.
- Neodkazuje na nic z kontroleru.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
string path = HttpContext.Request.Path;
_ = Task.Run(async () =>
{
await Task.Delay(1000);
Log(path);
});
return Accepted();
}
Úlohy na pozadí by se měly implementovat jako hostované služby. Další informace najdete v tématu Úlohy na pozadí s hostovanými službami.
Nezachytávejte služby vložené do kontrolerů ve vláknech na pozadí.
Nedělejte to: Následující příklad ukazuje uzavření, které zachycuje DbContext
z parametru Controller
akce. To je špatná praxe. Pracovní položka se může spustit mimo obor požadavku. Rozsah ContosoDbContext
se vztahuje na požadavek, což vede k .ObjectDisposedException
[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
});
return Accepted();
}
Udělejte to: Následující příklad:
- Vloží obor IServiceScopeFactory pro vytvoření oboru v pracovní položce na pozadí.
IServiceScopeFactory
je jedenton. - Vytvoří nový obor injektáže závislostí ve vlákně na pozadí.
- Neodkazuje na nic z kontroleru.
- Nezachytává
ContosoDbContext
příchozí požadavek.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
await using (var scope = serviceScopeFactory.CreateAsyncScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
Následující zvýrazněný kód:
- Vytvoří obor pro dobu životnosti operace na pozadí a vyřeší z ní služby.
- Používá
ContosoDbContext
se ze správného oboru.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
await using (var scope = serviceScopeFactory.CreateAsyncScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
Po spuštění textu odpovědi neupravujte stavový kód ani hlavičky.
ASP.NET Core neukládá tělo odpovědi HTTP do vyrovnávací paměti. Při prvním zápisu odpovědi:
- Hlavičky se odešlou spolu s tímto blokem textu klientovi.
- Hlavičky odpovědi už není možné změnit.
Nedělejte to: Následující kód se pokusí přidat hlavičky odpovědi po spuštění odpovědi:
app.Use(async (context, next) =>
{
await next();
context.Response.Headers["test"] = "test value";
});
V předchozím kódu vyvolá výjimku, context.Response.Headers["test"] = "test value";
pokud next()
byla zapsána do odpovědi.
Udělejte to: Následující příklad zkontroluje, jestli se před úpravou hlaviček spustila odpověď HTTP.
app.Use(async (context, next) =>
{
await next();
if (!context.Response.HasStarted)
{
context.Response.Headers["test"] = "test value";
}
});
Udělejte to: Následující příklad používá HttpResponse.OnStarting
k nastavení hlaviček před vyprázdněním hlaviček odpovědi do klienta.
Kontrola, jestli odpověď nezačala, umožňuje registraci zpětného volání, která se vyvolá těsně před zápisem hlaviček odpovědi. Kontrola, jestli odpověď nezačala:
- Poskytuje možnost přidávat nebo přepisovat hlavičky za běhu.
- Nevyžaduje znalost dalšího middlewaru v kanálu.
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers["someheader"] = "somevalue";
return Task.CompletedTask;
});
await next();
});
Pokud jste už začali psát do textu odpovědi, nezavolejte next().
Komponenty očekávají, že se budou volat pouze v případě, že je možné zpracovat odpověď a manipulovat s ní.
Použití hostování v procesu se službou IIS
Při vnitroprocesovém hostování aplikace ASP.NET Core běží ve stejném procesu jako příslušný pracovní proces služby IIS. Hostování v procesu poskytuje lepší výkon při hostování mimo proces, protože požadavky se nepřesunou přes adaptér zpětné smyčky. Adaptér zpětné smyčky je síťové rozhraní, které vrací odchozí síťový provoz zpět do stejného počítače. Služba IIS zajišťuje správu procesů s využitím Aktivační služby procesů systému Windows (WAS).
Projekty ve výchozím nastavení využívají model hostování v procesu v ASP.NET Core 3.0 a novějším.
Další informace najdete v tématu Hostitel ASP.NET Core ve Windows se službou IIS.
Nepředpokládáme, že HttpRequest.ContentLength nemá hodnotu null.
HttpRequest.ContentLength
je null, pokud hlavička Content-Length
není přijata. Hodnota Null v tomto případě znamená, že délka textu požadavku není známa; neznamená to, že délka je nula. Vzhledem k tomu, že všechna porovnání s hodnotou null (s výjimkou ==
) vrací hodnotu false, může se například vrátitfalse
, Request.ContentLength > 1024
když je velikost textu požadavku větší než 1024. Nevíme, že to může vést k bezpečnostním otvorům v aplikacích. Možná si myslíte, že chráníte před příliš velkými požadavky, když nejste.
Další informace najdete v této odpovědi StackOverflow.
Spolehlivé vzory webových aplikací
Pokyny k vytvoření moderní, spolehlivé, výkonné, testovatelné, nákladově efektivní a škálovatelné aplikace ASP.NET Core, ať už od začátku nebo refaktoring existující aplikace, najdete v článku o modelu Reliable Web App Pattern for.NET YouTube.