Osvědčené postupy pro ASP.NET Core

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ě. NevolejteTask.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žívejteTask.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 .

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:

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í do mezipaměti velkých objektů zabraňuje 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žívejtedotazy 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 .Wherepříkazy , .Selectnebo .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í aplikačních Přehledy nebo 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í:

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í:

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žbamina pozadí nebo mimo proces pomocí funkce Azure Functions. 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žívejtepokyny 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 i Production 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 aplikační Přehledy, 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 Kestrelnepodporuje 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 ReadFormAsynca
  • 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 se zbytkem haldy. 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ších).

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 ve výchozím nastavení používá System.Text.Json pro JSserializaci ON. System.Text.Json:

  • Čtení a zápisy 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ělabý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 kontroluje null.
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

HttpContextnení 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 zablokování, chybové ukončení nebo 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 > 1024když 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 nuly nebo refaktoringu stávající aplikace, najdete v článku o modelu Reliable Web App Pattern for.NETYouTube.