Delen via


ASP.NET Core Best Practices

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Door Mike Rousos

Dit artikel bevat richtlijnen voor het maximaliseren van prestaties en betrouwbaarheid van ASP.NET Core-apps.

Aggressief cachen

Caching wordt in verschillende delen van dit artikel besproken. Zie Overzicht van caching in ASP.NET Corevoor meer informatie.

Kritieke codepaden begrijpen

In dit artikel wordt een hotcodepad gedefinieerd als een codepad dat vaak wordt aangeroepen en waar veel van de uitvoeringstijd plaatsvindt. Hot codepaden beperken typisch de schaalbaarheid en de prestaties van apps en worden in verschillende delen van dit artikel besproken.

Voorkomen dat oproepen worden geblokkeerd

ASP.NET Core-apps moeten zijn ontworpen om veel aanvragen tegelijkertijd te verwerken. Met asynchrone API's kan een kleine groep threads duizenden gelijktijdige aanvragen verwerken door niet te wachten op blokkeringsaanroepen. In plaats van te wachten op een langlopende synchrone taak om te voltooien, kan de thread aan een andere aanvraag werken.

Een veelvoorkomend prestatieprobleem in ASP.NET Core-apps blokkeert aanroepen die asynchroon kunnen zijn. Veel synchrone blokkade-aanroepen leiden tot Thread Pool-uitputting en verslechterde reactietijden.

asynchrone uitvoering niet blokkeren door Task.Wait of Task<TResult>.Resultaan te roepen. Verwerf geen vergrendelingen in veelgebruikte codepaden. ASP.NET Core-apps presteren het beste wanneer u code parallel wilt uitvoeren. Bel Task.Run niet en wacht er onmiddellijk op. ASP.NET Core voert al applicatiecode uit op normale Thread Pool-threads, waardoor het aanroepen van Task.Run alleen resulteert in extra en onnodige scheduling van de Thread Pool. Zelfs als de geplande code een thread blokkeert, voorkomt Task.Run dat niet.

  • Maakbelangrijke codepaden asynchroon.
  • gegevenstoegang, I/O en langlopende bewerkings-API's asynchroon aanroepen als er een asynchrone API beschikbaar is.
  • Gebruik nietTask.Run om een synchrone API asynchroon te maken.
  • controller/Razor paginaacties asynchroon maken. De volledige aanroepstack is asynchroon om te profiteren van async/await patronen.
  • Overweeg berichtenbrokers zoals Azure Service Bus- te gebruiken om langlopende aanroepen te offloaden

Een profiler, zoals PerfView-, kan worden gebruikt om threads te vinden die vaak worden toegevoegd aan de Thread-pool. De Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start gebeurtenis geeft een thread aan die is toegevoegd aan de threadgroep.

Grote verzamelingen weergeven over meerdere kleinere pagina's

Een webpagina mag niet grote hoeveelheden gegevens tegelijk laden. Wanneer u een verzameling objecten retourneert, kunt u overwegen of dit kan leiden tot prestatieproblemen. Bepaal of het ontwerp de volgende slechte resultaten kan opleveren:

Voeg paginering toe om de impact van de voorgaande scenario's te verminderen. Met behulp van parameters voor paginaformaat en paginaindex moeten ontwikkelaars de voorkeur geven aan het ontwerp van het retourneren van een gedeeltelijk resultaat. Wanneer een volledig resultaat vereist is, moet paginering worden gebruikt om asynchroon batches met resultaten te vullen om te voorkomen dat serverbronnen worden vergrendeld.

Zie voor meer informatie over paging en het beperken van het aantal geretourneerde records:

IEnumerable<T> of IAsyncEnumerable<T> retourneren

Het retourneren van IEnumerable<T> van een actie resulteert in synchrone verzamelingsiteratie door de serializer. Het resultaat is de blokkering van aanroepen en een potentieel voor threadpool-uitputting. Als u synchrone opsomming wilt voorkomen, gebruikt u ToListAsync voordat u de opsomming retourneert.

Vanaf ASP.NET Core 3.0 kan IAsyncEnumerable<T> worden gebruikt als alternatief voor IEnumerable<T> die asynchroon opsommen. Zie voor meer informatie controlleractie-retourtypen.

Grote objecttoewijzingen minimaliseren

De .NET Core garbage collector beheert de toewijzing en het vrijgeven van geheugen automatisch in ASP.NET Core-apps. Automatische geheugenbeheer betekent over het algemeen dat ontwikkelaars zich geen zorgen hoeven te maken over hoe of wanneer geheugen wordt vrijgemaakt. Het opschonen van niet-gereferentieerde objecten kost echter CPU-tijd, dus ontwikkelaars moeten het toewijzen van objecten in hot code pathsminimaliseren. Garbagecollection is vooral duur voor grote objecten (>= 85.000 bytes). Grote objecten worden opgeslagen op de grote object heap en vereisen een volledige (generatie 2) garbagecollection om op te schonen. In tegenstelling tot verzamelingen van generatie 0 en generatie 1 is voor een verzameling van de tweede generatie een tijdelijke schorsing van de uitvoering van apps vereist. Frequente toewijzing en onttoewijzing van grote objecten kunnen inconsistente prestaties veroorzaken.

Aanbevelingen:

  • Overweeg om grote objecten die vaak worden gebruikt, in de cache op te slaan. Het cachen van grote objecten voorkomt dure geheugentoewijzingen.
  • Doe pool buffers door een ArrayPool<T> te gebruiken om grote arrays op te slaan.
  • Wijs niet veel grote, kortlevende objecten toe aan kritieke codepaden.

Geheugenproblemen, zoals de voorgaande, kunnen worden gediagnosticeerd door de statistieken van de garbage collection (GC) in PerfView te bekijken en te analyseren.

  • Onderbrekingstijd voor garbagecollection.
  • Welk percentage van de processortijd wordt besteed aan garbagecollection.
  • Hoeveel garbagecollection's zijn generatie 0, 1 en 2.

Voor meer informatie, zie Vuilnisophaling en Prestaties.

Gegevenstoegang en I/O optimaliseren

Interacties met een gegevensarchief en andere externe services zijn vaak de traagste onderdelen van een ASP.NET Core-app. Het efficiënt lezen en schrijven van gegevens is essentieel voor goede prestaties.

Aanbevelingen:

  • Roep alle API's voor gegevenstoegang asynchroon aan.
  • Haal niet meer gegevens op dan nodig is. Schrijf query's om alleen de gegevens te retourneren die nodig zijn voor de huidige HTTP-aanvraag.
  • Overweeg het cachen van veelgebruikte gegevens die worden opgehaald uit een database of externe service, als het acceptabel is dat de gegevens enigszins verouderd zijn. Gebruik afhankelijk van het scenario een MemoryCache- of een DistributedCache-. Zie Antwoordcaching in ASP.NET Corevoor meer informatie.
  • minimaliseer netwerkverkeer. Het doel is om de vereiste gegevens op te halen in één aanroep in plaats van meerdere aanroepen.
  • Gebruikquery's zonder tracering in Entity Framework Core bij het benaderen van gegevens voor read-only-doeleinden. EF Core kan de resultaten van query's zonder tracering efficiënter retourneren.
  • Filter en aggregeer LINQ-query's (bijvoorbeeld met .Where-, .Select- of .Sum-uitdrukkingen) zodat het filteren wordt uitgevoerd door de database.
  • Houd er rekening mee dat EF Core sommige queryoperators op de client oplost, wat kan leiden tot inefficiënte uitvoering van query's. Voor meer informatie, zie Problemen met de prestaties van clientevaluatie.
  • Gebruik geen projectiequery's voor verzamelingen, wat kan leiden tot het uitvoeren van 'N + 1'-SQL-query's. Zie Optimalisatie van gecorreleerde subquery'svoor meer informatie.

De volgende benaderingen kunnen de prestaties in apps op grote schaal verbeteren:

We raden u aan de impact van de voorgaande high-performance benaderingen te meten voordat u de codebasis doorvoert. De extra complexiteit van gecompileerde query's rechtvaardigt mogelijk niet de prestatieverbetering.

Queryproblemen kunnen worden gedetecteerd door de tijd te bekijken die nodig is om toegang te krijgen tot gegevens met Application Insights- of met profileringshulpprogramma's. De meeste databases maken ook statistieken beschikbaar met betrekking tot vaak uitgevoerde query's.

HTTP-verbindingen poolen met HttpClientFactory

Hoewel HttpClient de IDisposable-interface implementeert, is deze ontworpen voor hergebruik. Gesloten HttpClient instellingen laten sockets voor een korte periode open in de status TIME_WAIT. Als een codepad dat HttpClient objecten maakt en verwijdert vaak wordt gebruikt, kan de app beschikbare sockets uitputten. HttpClientFactory werd geïntroduceerd in ASP.NET Core 2.1 als oplossing voor dit probleem. Het verwerkt het groeperen van HTTP-verbindingen om de prestaties en betrouwbaarheid te optimaliseren. Zie Gebruik HttpClientFactory voor het implementeren van tolerante HTTP-aanvragenvoor meer informatie.

Aanbevelingen:

Gebruikelijke codepaden snel houden

U wilt dat al uw code snel is. Veelgebruikte codepaden zijn het meest essentieel om te optimaliseren. Dit zijn onder andere:

  • Middleware-onderdelen in de aanvraagverwerkingspijplijn van de app, vooral middleware die vroeg in de pijplijn wordt uitgevoerd. Deze onderdelen hebben een grote invloed op de prestaties.
  • Code die wordt uitgevoerd voor elke aanvraag of meerdere keren per aanvraag. Bijvoorbeeld aangepaste logboekregistratie, autorisatiehandlers of initialisatie van tijdelijke services.

Aanbevelingen:

Langlopende taken uitvoeren buiten HTTP-aanvragen

De meeste aanvragen voor een ASP.NET Core-app kunnen worden verwerkt door een controller of paginamodel dat de benodigde services aanroept en een HTTP-antwoord retourneert. Voor sommige aanvragen die betrekking hebben op langlopende taken, is het beter om het hele proces voor aanvraagrespons asynchroon te maken.

Aanbevelingen:

  • Wacht niet tot langlopende taken zijn voltooid bij de normale verwerking van HTTP-verzoeken.
  • Overweeg langlopende aanvragen te verwerken met achtergrondservices of buiten het proces, mogelijk met een Azure Function en/of het gebruik van een berichtenbroker zoals de Azure Service Bus. Het voltooien van werk buiten het proces is vooral nuttig voor CPU-intensieve taken.
  • Gebruik realtime communicatieopties, zoals SignalR, om asynchroon met klanten te communiceren.

Clientbestanden verkleinen

ASP.NET Core-apps met complexe front-ends dienen vaak veel JavaScript-, CSS- of afbeeldingsbestanden. De prestaties van initiële belastingsaanvragen kunnen worden verbeterd door:

  • Bundeling, waarmee meerdere bestanden in één worden gecombineerd.
  • Minificeren, waardoor de grootte van bestanden wordt verkleind door witruimte en opmerkingen te verwijderen.

Aanbevelingen:

  • Gebruik de richtlijnen voor bundeling en minificatie van , waarin compatibele hulpprogramma's worden vermeld en hoe u de environment tag van ASP.NET Core kunt gebruiken om zowel Development als Production omgevingen te verwerken.
  • Overweeg andere hulpprogramma's van derden, zoals Webpack, voor complex clientassetbeheer.

Antwoorden comprimeren

Door de grootte van het antwoord te verkleinen, wordt de reactiesnelheid van een app meestal aanzienlijk verhoogd. Een manier om nettoladinggrootten te verminderen, is door de reacties van een app te comprimeren. Voor meer informatie, zie Antwoordcompressie.

De nieuwste ASP.NET Core-release gebruiken

Elke nieuwe release van ASP.NET Core bevat prestatieverbeteringen. Optimalisaties in .NET Core en ASP.NET Core betekenen dat nieuwere versies over het algemeen beter presteren dan oudere versies. .NET Core 2.1 heeft bijvoorbeeld ondersteuning toegevoegd voor gecompileerde reguliere expressies en baat gehad bij Span<T->. ASP.NET Core 2.2 heeft ondersteuning toegevoegd voor HTTP/2. ASP.NET Core 3.0 voegt veel verbeteringen toe die het geheugengebruik verminderen en de doorvoer verbeteren. Als de prestaties een prioriteit hebben, kunt u overwegen om een upgrade uit te voeren naar de huidige versie van ASP.NET Core.

Uitzonderingen minimaliseren

Uitzonderingen moeten zeldzaam zijn. Het genereren en vangen van uitzonderingen is traag ten opzichte van andere codestroompatronen. Daarom mogen uitzonderingen niet worden gebruikt om de normale programmastroom te beheren.

Aanbevelingen:

  • gebruik geen het genereren of vangen van uitzonderingen als een middel van normale programmastroom, met name in hotcodepaden.
  • Neem logica op in de app om voorwaarden te detecteren en te verwerken die een uitzondering zouden veroorzaken.
  • Maak uitzonderingen voor ongebruikelijke of onverwachte omstandigheden of vang ze op.

Diagnostische hulpprogramma's voor apps, zoals Application Insights, kunnen helpen bij het identificeren van veelvoorkomende uitzonderingen in een app die van invloed kunnen zijn op de prestaties.

Synchrone lees- of schrijfbewerkingen op httpRequest/HttpResponse-hoofdtekst voorkomen

Alle I/O in ASP.NET Core is asynchroon. Servers implementeren de Stream interface, die zowel synchrone als asynchrone overbelastingen heeft. De asynchrone threads moeten de voorkeur hebben om blokkering van threadpoolthreads te voorkomen. Het blokkeren van threads kan leiden tot starvatie van threadgroepen.

Doe dit niet: In het volgende voorbeeld wordt de ReadToEndgebruikt. De huidige thread wordt geblokkeerd om te wachten op het resultaat. Dit is een voorbeeld van synchronisatie via asynchrone.

public class BadStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public ActionResult<ContosoData> Get()
    {
        var json = new StreamReader(Request.Body).ReadToEnd();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }
}

In de voorgaande code leest Get synchroon de volledige HOOFDtekst van de HTTP-aanvraag in het geheugen. Als de client langzaam uploadt, doet de app synchronisatie via asynchroon. De app wordt gesynchroniseerd via asynchroon omdat KestrelNIET synchrone leesbewerkingen ondersteunt.

Dit doet u: In het volgende voorbeeld wordt ReadToEndAsync gebruikt en wordt de thread niet geblokkeerd tijdens het lezen.

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);
    }

}

De voorgaande code leest asynchroon de volledige HOOFDtekst van de HTTP-aanvraag in het geheugen.

Waarschuwing

Als de aanvraag groot is, kan het lezen van de volledige HTTP-aanvraagbody in het geheugen leiden tot een OOM-voorwaarde (onvoldoende geheugen). OOM kan resulteren in een Denial Of Service. Zie Vermijd het lezen van grote aanvraagteksten of antwoordteksten in het geheugen in dit artikel voor meer informatie.

Dit doet u: Het volgende voorbeeld is volledig asynchroon met behulp van een niet-gebufferde aanvraagbody:

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    }
}

Met de voorgaande code wordt de aanvraagbody asynchroon gedeserialiseerd in een C#-object.

Geef de voorkeur aan ReadFormAsync boven Request.Form

Gebruik HttpContext.Request.ReadFormAsync in plaats van HttpContext.Request.Form. HttpContext.Request.Form kan alleen veilig worden gelezen onder de volgende voorwaarden:

  • Het formulier is gelezen door een aanroep naar ReadFormAsyncen
  • De formulierwaarde in de cache wordt gelezen met behulp van HttpContext.Request.Form

Doe dit niet: In het volgende voorbeeld wordt HttpContext.Request.Formgebruikt. HttpContext.Request.Form gebruikt synchronisatie over asynchroon en kan leiden tot uitputting van de threadpool.

public class BadReadController : Controller
{
    [HttpPost("/form-body")]
    public IActionResult Post()
    {
        var form =  HttpContext.Request.Form;

        Process(form["id"], form["name"]);

        return Accepted();
    }

Dit doet u: In het volgende voorbeeld wordt HttpContext.Request.ReadFormAsync gebruikt om de hoofdtekst van het formulier asynchroon te lezen.

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();
    }

Vermijd het lezen van grote aanvraagteksten of antwoordteksten in het geheugen

In .NET komt elke objecttoewijzing groter dan of gelijk aan 85.000 bytes in de grote object heap (LOH). Grote objecten zijn op twee manieren duur:

  • De toewijzingskosten zijn hoog omdat het geheugen voor een nieuw toegewezen groot object moet worden leeggemaakt. De CLR garandeert dat de geheugenruimte voor alle nieuw toegewezen objecten wordt vrijgemaakt.
  • LOH wordt verzameld met de rest van de hoop. LOH vereist een volledige geheugenopruiming of Gen2-opruiming.

In dit blogbericht wordt het probleem beknopt beschreven:

Wanneer een groot object wordt toegewezen, wordt het gemarkeerd als Gen 2-object. Niet Gen 0 voor kleine objecten. De gevolgen zijn dat als je een geheugen tekort in LOH hebt, GC de hele beheerde heap opschoont, niet alleen LOH. Dus het schoont Gen 0, Gen 1 en Gen 2 inclusief LOH op. Dit wordt een volledige vuilopruiming genoemd en is de meest tijdrovende vuilopruiming. Voor veel toepassingen kan het acceptabel zijn. Maar zeker niet voor krachtige webservers, waarbij er weinig grote geheugenbuffers nodig zijn om een gemiddelde webaanvraag af te handelen (lezen uit een socket, decomprimeren, JSON decoderen en meer).

Het opslaan van een grote aanvraag- of antwoordtekst in één byte[] of string:

  • Dit kan ertoe leiden dat er snel geen ruimte meer is in de LOH.
  • Kan prestatieproblemen voor de app veroorzaken vanwege volledige GC-runs.

Werken met een synchrone gegevensverwerkings-API

Wanneer u een serializer/de-serializer gebruikt die alleen synchrone lees- en schrijfbewerkingen ondersteunt (bijvoorbeeld Json.NET):

  • Buffer de gegevens asynchroon in het geheugen voordat deze worden doorgegeven aan de serializer/de-serializer.

Waarschuwing

Als de aanvraag groot is, kan dit leiden tot een out of memory (onvoldoende geheugen) situatie. OOM kan resulteren in een Denial Of Service. Zie Vermijd het lezen van grote aanvraagteksten of antwoordteksten in het geheugen in dit artikel voor meer informatie.

ASP.NET Core 3.0 gebruikt standaard System.Text.Json voor JSON-serialisatie. System.Text.Json:

  • Leest en schrijft JSON asynchroon.
  • Is geoptimaliseerd voor UTF-8-tekst.
  • Dit zijn doorgaans hogere prestaties dan Newtonsoft.Json.

IHttpContextAccessor.HttpContext niet opslaan in een veld

De IHttpContextAccessor.HttpContext retourneert de HttpContext van de actieve aanvraag wanneer deze wordt geopend vanuit de aanvraagthread. De IHttpContextAccessor.HttpContext mag niet in een veld of variabele worden opgeslagen.

Doe dit niet: In het volgende voorbeeld wordt de HttpContext opgeslagen in een veld en wordt vervolgens later geprobeerd deze te gebruiken.

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");
        }
    }
}

De voorgaande code legt vaak een null- of onjuiste HttpContext vast in de constructor.

Ga als volgt te werk: Het volgende voorbeeld:

  • Slaat de IHttpContextAccessor op in een veld.
  • Gebruikt het HttpContext veld op het juiste tijdstip en controleert op 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");
        }
    }
}

Geen toegang krijgen tot HttpContext vanuit meerdere threads

HttpContext is niet threadveilig. Toegang tot HttpContext vanuit meerdere threads parallel kan leiden tot onverwacht gedrag, zoals de server om te stoppen met reageren, crashes en beschadiging van gegevens.

Doe dit niet: In het volgende voorbeeld worden drie parallelle aanvragen uitgevoerd en wordt het binnenkomende aanvraagpad vóór en na de uitgaande HTTP-aanvraag in een logboek opgeslagen. Het aanvraagpad wordt geopend vanuit meerdere threads, mogelijk parallel.

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;
    }

Dit doet u: In het volgende voorbeeld worden alle gegevens uit de binnenkomende aanvraag gekopieerd voordat u de drie parallelle aanvragen maakt.

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;
    }

Gebruik de HttpContext niet nadat de aanvraag is voltooid

HttpContext is alleen geldig zolang er een actieve HTTP-aanvraag is in de ASP.NET Core-pijplijn. De volledige ASP.NET Core-pijplijn is een asynchrone keten van gemachtigden die elke aanvraag uitvoeren. Wanneer de Task uit deze keten terugkeert en is voltooid, wordt de HttpContext gerecycled.

Doe dit niet: in het volgende voorbeeld wordt async void gebruikt, waardoor de HTTP-aanvraag is voltooid wanneer de eerste await is bereikt:

  • Het gebruik van async void is ALTIJD een slechte praktijk in ASP.NET Core-applicaties.
  • De voorbeeldcode opent de HttpResponse nadat de HTTP-aanvraag is voltooid.
  • Late toegang veroorzaakt een crash van het proces.
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");
    }
}

Dit doet u: Het volgende voorbeeld retourneert een Task naar het framework, zodat de HTTP-aanvraag pas wordt voltooid als de actie is voltooid.

public class AsyncGoodTaskController : Controller
{
    [HttpGet("/async")]
    public async Task Get()
    {
        await Task.Delay(1000);

        await Response.WriteAsync("Hello World");
    }
}

Leg de HttpContext niet vast in achtergrondthreads

Doe dit niet: Het volgende voorbeeld laat zien dat een closure de HttpContext van de eigenschap Controller vastlegt. Dit is een slechte gewoonte omdat het werkitem het volgende kan doen:

  • Uitvoeren buiten het aanvraagbereik.
  • Probeer de verkeerde HttpContextte lezen.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        var path = HttpContext.Request.Path;
        Log(path);
    });

    return Accepted();
}

Doe dit: Het volgende voorbeeld:

  • Kopieert de gegevens die nodig zijn in de achtergrondtaak tijdens de aanvraag.
  • Verwijst niet naar iets van de controller.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

Achtergrondtaken moeten worden geïmplementeerd als gehoste services. Zie Achtergrondtaken met gehoste servicesvoor meer informatie.

Leg geen services vast die in de controllers zijn geïnjecteerd op achtergrondthreads.

Doe dit niet: In het volgende voorbeeld ziet u een closure die de DbContext vastlegt vanuit de actieparameter Controller. Dit is een slechte gewoonte. Het werkitem kan buiten het aanvraagbereik worden uitgevoerd. De ContosoDbContext is beperkt tot de aanvraag, wat resulteert in een 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();
}

Doe dit: Het volgende voorbeeld:

  • Injecteert een IServiceScopeFactory om een bereik in het achtergrondwerkitem te maken. IServiceScopeFactory is een singleton.
  • Hiermee maakt u een nieuw bereik voor afhankelijkheidsinjectie in de achtergrondthread.
  • Verwijst niet naar iets van de controller.
  • Legt de ContosoDbContext niet vast van de binnenkomende aanvraag.
[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();
}

De volgende gemarkeerde code:

  • Hiermee creëert u een scope voor de levensduur van de achtergrondbewerking en haalt u services daaruit op.
  • Gebruikt ContosoDbContext uit het juiste bereik.
[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();
}

Wijzig de statuscode of headers niet nadat de hoofdtekst van het antwoord is gestart

ASP.NET Core buffert de hoofdtekst van het HTTP-antwoord niet. De eerste keer dat het antwoord wordt geschreven:

  • De headers worden samen met dat segment van de hoofdtekst naar de client verzonden.
  • Het is niet meer mogelijk om antwoordheaders te wijzigen.

Doe dit niet: De volgende code probeert antwoordheaders toe te voegen nadat het antwoord al is gestart:

app.Use(async (context, next) =>
{
    await next();

    context.Response.Headers["test"] = "test value";
});

In de voorgaande code genereert context.Response.Headers["test"] = "test value"; een uitzondering als next() naar het antwoord heeft geschreven.

Dit doet u: In het volgende voorbeeld wordt gecontroleerd of het HTTP-antwoord is gestart voordat u de headers wijzigt.

app.Use(async (context, next) =>
{
    await next();

    if (!context.Response.HasStarted)
    {
        context.Response.Headers["test"] = "test value";
    }
});

Dit doet u: In het volgende voorbeeld wordt HttpResponse.OnStarting gebruikt om de headers in te stellen voordat de antwoordheaders naar de client worden leeggemaakt.

Door te controleren of de respons nog niet is gestart, wordt het mogelijk om een callback te registreren die net voordat de responsheaders worden geschreven, wordt opgeroepen. Controleren of de reactie nog niet is begonnen:

  • Biedt de mogelijkheid om headers net op tijd toe te voegen of te overschrijven.
  • Hiervoor is geen kennis van de volgende middleware in de pijplijn vereist.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Roep next() niet aan als u al bent begonnen met schrijven naar de responsebody.

Onderdelen verwachten alleen te worden aangeroepen als ze het antwoord kunnen verwerken en manipuleren.

In-process hosting gebruiken met IIS

Met in-process hosting wordt een ASP.NET Core-app uitgevoerd in hetzelfde proces als het IIS-werkproces. In-process hosting biedt verbeterde prestaties ten opzichte van out-of-process hosting, omdat aanvragen niet worden geproxied via de loopback-adapter. De loopback-adapter is een netwerkinterface die uitgaand netwerkverkeer retourneert naar dezelfde computer. IIS verwerkt procesbeheer met de WAS-(Windows Process Activation Service).

Projecten zijn standaard ingesteld op het in-process hostingmodel in ASP.NET Core 3.0 en hoger.

Zie Host ASP.NET Core in Windows met IIS- voor meer informatie

Neem niet aan dat HttpRequest.ContentLength niet null is

HttpRequest.ContentLength is null als de Content-Length header niet wordt ontvangen. Null in dat geval betekent dat de lengte van de aanvraagbody niet bekend is; Het betekent niet dat de lengte nul is. Omdat alle vergelijkingen met null (behalve ==) onwaar retourneren, kan de vergelijking Request.ContentLength > 1024bijvoorbeeld false retourneren wanneer de grootte van de aanvraagtekst groter is dan 1024. Als u dit niet weet, kan dit leiden tot beveiligingsgaten in apps. Misschien denkt u dat u zich beveiligt tegen te grote aanvragen als u dat niet doet.

Zie dit StackOverflow-antwoordvoor meer informatie.

Patronen voor bedrijfsweb-apps

Zie Enterprise-web-app-patronenvoor hulp bij het maken van een betrouwbare, veilige, uitvoerbare, testbare en schaalbare ASP.NET Core-app. Er is een volledige voorbeeldweb-app van productiekwaliteit beschikbaar waarmee de patronen worden geïmplementeerd.