Bagikan melalui


ASP.NET Praktik Terbaik Inti

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Mike Rousos

Artikel ini menyediakan panduan untuk memaksimalkan performa dan keandalan aplikasi ASP.NET Core.

Cache secara agresif

Penembolokan dibahas di beberapa bagian artikel ini. Untuk informasi selengkapnya, lihat Gambaran Umum penembolokan di ASP.NET Core.

Memahami jalur kode panas

Dalam artikel ini, jalur kode panas didefinisikan sebagai jalur kode yang sering disebut dan di mana banyak waktu eksekusi terjadi. Jalur kode panas biasanya membatasi peluasan skala dan performa aplikasi dan dibahas di beberapa bagian artikel ini.

Hindari memblokir panggilan

aplikasi ASP.NET Core harus dirancang untuk memproses banyak permintaan secara bersamaan. API asinkron memungkinkan kumpulan kecil utas untuk menangani ribuan permintaan bersamaan dengan tidak menunggu pemblokiran panggilan. Alih-alih menunggu tugas sinkron yang sudah berjalan lama untuk diselesaikan, utas dapat bekerja pada permintaan lain.

Masalah performa umum di aplikasi ASP.NET Core adalah memblokir panggilan yang bisa asinkron. Banyak pemblokiran panggilan sinkron menyebabkan kurangnya Kumpulan Utas dan waktu respons yang menurun.

Jangan memblokir eksekusi asinkron dengan memanggil Task.Wait atau Task<TResult>.Result. Jangan memperoleh kunci di jalur kode umum. aplikasi ASP.NET Core berkinerja terbaik saat dirancang untuk menjalankan kode secara paralel. Jangan menelepon Task.Run dan segera menunggunya. ASP.NET Core sudah menjalankan kode aplikasi pada utas Kumpulan Utas normal, jadi memanggil Task.Run hanya menghasilkan penjadwalan Kumpulan Utas yang tidak perlu. Bahkan jika kode terjadwal akan memblokir utas, Task.Run tidak mencegahnya.

  • Buat jalur kode panas asinkron.
  • Lakukan panggilan akses data, I/O, dan API operasi jangka panjang secara asinkron jika API asinkron tersedia.
  • Jangan gunakan Task.Run untuk membuat API sinkron asinkron.
  • Buat pengontrol/Razor Tindakan halaman asinkron. Seluruh tumpukan panggilan asinkron untuk mendapatkan manfaat dari pola asinkron/await.
  • Pertimbangkan untuk menggunakan broker pesan seperti Azure Bus Layanan untuk membongkar panggilan yang berjalan lama

Profiler, seperti PerfView, dapat digunakan untuk menemukan utas yang sering ditambahkan ke Kumpulan Utas. Acara Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start tersebut menunjukkan utas yang ditambahkan ke kumpulan utas.

Mengembalikan koleksi besar di beberapa halaman yang lebih kecil

Halaman web tidak boleh memuat data dalam jumlah besar sekaligus. Saat mengembalikan kumpulan objek, pertimbangkan apakah itu dapat menyebabkan masalah performa. Tentukan apakah desain dapat menghasilkan hasil yang buruk berikut:

Tambahkan penomoran halaman untuk mengurangi skenario sebelumnya. Dengan menggunakan ukuran halaman dan parameter indeks halaman, pengembang harus mendukung desain mengembalikan hasil parsial. Ketika hasil lengkap diperlukan, penomoran halaman harus digunakan untuk mengisi batch hasil secara asinkron untuk menghindari penguncian sumber daya server.

Untuk informasi selengkapnya tentang penomoran halaman dan membatasi jumlah rekaman yang dikembalikan, lihat:

Mengembalikan IEnumerable<T> atau IAsyncEnumerable<T>

IEnumerable<T> Mengembalikan dari tindakan menghasilkan perulangan koleksi sinkron oleh serializer. Hasilnya adalah pemblokiran panggilan dan potensi kelaparan kumpulan utas. Untuk menghindari enumerasi sinkron, gunakan ToListAsync sebelum mengembalikan enumerable.

Dimulai dengan ASP.NET Core 3.0, IAsyncEnumerable<T> dapat digunakan sebagai alternatif untuk IEnumerable<T> yang menghitung secara asinkron. Untuk informasi selengkapnya, lihat Jenis pengembalian tindakan pengontrol.

Meminimalkan alokasi objek besar

Pengumpul sampah .NET Core mengelola alokasi dan rilis memori secara otomatis di aplikasi ASP.NET Core. Pengumpulan sampah otomatis umumnya berarti pengembang tidak perlu khawatir tentang bagaimana atau kapan memori dibebaskan. Namun, membersihkan objek yang tidak direferensikan membutuhkan waktu CPU, sehingga pengembang harus meminimalkan alokasi objek di jalur kode panas. Pengumpulan sampah sangat mahal pada objek besar (>= 85.000 byte). Objek besar disimpan di tumpukan objek besar dan memerlukan pengumpulan sampah penuh (generasi 2) untuk dibersihkan. Tidak seperti koleksi generasi 0 dan generasi 1, koleksi generasi 2 memerlukan penghentian sementara eksekusi aplikasi. Alokasi dan delokasi objek besar yang sering dapat menyebabkan performa yang tidak konsisten.

Rekomendasi:

  • Pertimbangkan untuk membuat cache objek besar yang sering digunakan. Penembolokan objek besar mencegah alokasi mahal.
  • Lakukan buffer kumpulan dengan menggunakan ArrayPool<T> untuk menyimpan array besar.
  • Jangan mengalokasikan banyak objek besar berumur pendek pada jalur kode panas.

Masalah memori, seperti sebelumnya, dapat didiagnosis dengan meninjau statistik pengumpulan sampah (GC) di PerfView dan memeriksa:

  • Waktu jeda pengumpulan sampah.
  • Berapa persentase waktu prosesor yang dihabiskan dalam pengumpulan sampah.
  • Berapa banyak pengumpulan sampah generasi 0, 1, dan 2.

Untuk informasi selengkapnya, lihat Pengumpulan dan Performa Sampah.

Mengoptimalkan akses data dan I/O

Interaksi dengan penyimpanan data dan layanan jarak jauh lainnya sering menjadi bagian terlambat dari aplikasi ASP.NET Core. Membaca dan menulis data secara efisien sangat penting untuk performa yang baik.

Rekomendasi:

  • Panggil semua API akses data secara asinkron.
  • Jangan mengambil lebih banyak data daripada yang diperlukan. Tulis kueri untuk mengembalikan hanya data yang diperlukan untuk permintaan HTTP saat ini.
  • Pertimbangkan untuk menyimpan data yang sering diakses yang diambil dari database atau layanan jarak jauh jika data yang sedikit kedaluarsa dapat diterima. Bergantung pada skenarionya, gunakan MemoryCache atau DistributedCache. Untuk informasi selengkapnya, lihat Penembolokan respons di ASP.NET Core.
  • Meminimalkan perjalanan pulang pergi jaringan. Tujuannya adalah untuk mengambil data yang diperlukan dalam satu panggilan daripada beberapa panggilan.
  • Gunakan kueri tanpa pelacakan di Entity Framework Core saat mengakses data untuk tujuan baca-saja. EF Core dapat mengembalikan hasil kueri tanpa pelacakan dengan lebih efisien.
  • Lakukan filter dan agregat kueri LINQ (dengan .Where, , .Selectatau .Sum pernyataan, misalnya) sehingga pemfilteran dilakukan oleh database.
  • Pertimbangkan bahwa EF Core menyelesaikan beberapa operator kueri pada klien, yang dapat menyebabkan eksekusi kueri yang tidak efisien. Untuk informasi selengkapnya, lihat Masalah performa evaluasi klien.
  • Jangan gunakan kueri proyeksi pada koleksi, yang dapat mengakibatkan eksekusi kueri SQL "N + 1". Untuk informasi selengkapnya, lihat Pengoptimalan subkueri yang berkorelasi.

Pendekatan berikut dapat meningkatkan performa di aplikasi skala tinggi:

Sebaiknya ukur dampak pendekatan berkinerja tinggi sebelumnya sebelum menerapkan basis kode. Kompleksitas tambahan kueri yang dikompilasi mungkin tidak membenarkan peningkatan performa.

Masalah kueri dapat dideteksi dengan meninjau waktu yang dihabiskan untuk mengakses data dengan Application Insights atau dengan alat pembuatan profil. Sebagian besar database juga membuat statistik tersedia mengenai kueri yang sering dijalankan.

Koneksi HTTP kumpulan dengan HttpClientFactory

Meskipun HttpClient mengimplementasikan IDisposable antarmuka, antarmuka ini dirancang untuk digunakan kembali. Instans tertutup HttpClient membiarkan soket terbuka dalam TIME_WAIT status untuk waktu yang singkat. Jika jalur kode yang membuat dan membuang HttpClient objek sering digunakan, aplikasi mungkin menghabiskan soket yang tersedia. HttpClientFactory diperkenalkan di ASP.NET Core 2.1 sebagai solusi untuk masalah ini. Ini menangani pengumpulan koneksi HTTP untuk mengoptimalkan performa dan keandalan. Untuk informasi selengkapnya, lihat Menggunakan HttpClientFactory untuk menerapkan permintaan HTTP yang tangguh.

Rekomendasi:

Pertahankan jalur kode umum dengan cepat

Anda ingin semua kode Anda menjadi cepat. Jalur kode yang sering disebut adalah yang paling penting untuk dioptimalkan. Ini termasuk:

  • Komponen middleware dalam alur pemrosesan permintaan aplikasi, terutama middleware berjalan di awal alur. Komponen-komponen ini berdampak besar pada performa.
  • Kode yang dijalankan untuk setiap permintaan atau beberapa kali per permintaan. Misalnya, pengelogan kustom, penangan otorisasi, atau inisialisasi layanan sementara.

Rekomendasi:

  • Jangan gunakan komponen middleware kustom dengan tugas yang berjalan lama.
  • Gunakan alat pembuatan profil performa, seperti Alat Diagnostik Visual Studio atau PerfView), untuk mengidentifikasi jalur kode panas.

Menyelesaikan Tugas yang berjalan lama di luar permintaan HTTP

Sebagian besar permintaan ke aplikasi ASP.NET Core dapat ditangani oleh pengontrol atau model halaman yang memanggil layanan yang diperlukan dan mengembalikan respons HTTP. Untuk beberapa permintaan yang melibatkan tugas jangka panjang, lebih baik membuat seluruh proses respons permintaan asinkron.

Rekomendasi:

  • Jangan menunggu tugas jangka panjang selesai sebagai bagian dari pemrosesan permintaan HTTP biasa.
  • Pertimbangkan untuk menangani permintaan jangka panjang dengan layanan latar belakang atau di luar proses mungkin dengan Azure Function dan/atau menggunakan broker pesan seperti Azure Bus Layanan. Menyelesaikan pekerjaan di luar proses sangat bermanfaat untuk tugas intensif CPU.
  • Gunakan opsi komunikasi real time, seperti SignalR, untuk berkomunikasi dengan klien secara asinkron.

Minifikasi aset klien

ASP.NET aplikasi Core dengan front-end kompleks sering melayani banyak file JavaScript, CSS, atau gambar. Performa permintaan beban awal dapat ditingkatkan dengan:

  • Bundling, yang menggabungkan beberapa file menjadi satu.
  • Penambangan, yang mengurangi ukuran file dengan menghapus spasi kosong dan komentar.

Rekomendasi:

Memadatkan respons

Mengurangi ukuran respons biasanya meningkatkan respons aplikasi, sering kali secara dramatis. Salah satu cara untuk mengurangi ukuran payload adalah dengan mengompresi respons aplikasi. Untuk informasi selengkapnya, lihat Kompresi respons.

Menggunakan rilis ASP.NET Core terbaru

Setiap rilis baru ASP.NET Core mencakup peningkatan performa. Pengoptimalan di .NET Core dan ASP.NET Core berarti bahwa versi yang lebih baru umumnya mengungguli versi lama. Misalnya, .NET Core 2.1 menambahkan dukungan untuk ekspresi reguler yang dikompilasi dan diuntungkan dari Span<T>. ASP.NET Core 2.2 menambahkan dukungan untuk HTTP/2. ASP.NET Core 3.0 menambahkan banyak peningkatan yang mengurangi penggunaan memori dan meningkatkan throughput. Jika performa adalah prioritas, pertimbangkan untuk meningkatkan ke versi ASP.NET Core saat ini.

Meminimalkan pengecualian

Pengecualian harus jarang terjadi. Pengecualian pelemparan dan penangkapan lambat relatif terhadap pola aliran kode lainnya. Karena itu, pengecualian tidak boleh digunakan untuk mengontrol alur program normal.

Rekomendasi:

  • Jangan gunakan pengecualian pelemparan atau penangkapan sebagai sarana alur program normal, terutama di jalur kode panas.
  • Sertakan logika dalam aplikasi untuk mendeteksi dan menangani kondisi yang akan menyebabkan pengecualian.
  • Lakukan lemparan atau tangkap pengecualian untuk kondisi yang tidak biasa atau tidak terduga.

Alat diagnostik aplikasi, seperti Application Insights, dapat membantu mengidentifikasi pengecualian umum dalam aplikasi yang dapat memengaruhi performa.

Hindari baca atau tulis sinkron di isi HttpRequest/HttpResponse

Semua I/O di ASP.NET Core tidak sinkron. Server mengimplementasikan Stream antarmuka, yang memiliki kelebihan beban sinkron dan asinkron. Yang asinkron harus lebih disukai untuk menghindari pemblokiran utas kumpulan utas. Pemblokiran utas dapat menyebabkan kelaparan kumpulan utas.

Jangan lakukan ini: Contoh berikut menggunakan ReadToEnd. Ini memblokir utas saat ini untuk menunggu hasilnya. Ini adalah contoh sinkronisasi melalui asinkron.

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

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

Dalam kode sebelumnya, Get secara sinkron membaca seluruh isi permintaan HTTP ke dalam memori. Jika klien perlahan-lahan mengunggah, aplikasi melakukan sinkronisasi melalui asinkron. Aplikasi ini menyinkronkan asinkron karena Kestrel TIDAK mendukung bacaan sinkron.

Lakukan ini: Contoh berikut menggunakan dan tidak memblokir utas ReadToEndAsync saat membaca.

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

}

Kode sebelumnya secara asinkron membaca seluruh isi permintaan HTTP ke dalam memori.

Peringatan

Jika permintaan besar, membaca seluruh isi permintaan HTTP ke dalam memori dapat menyebabkan kondisi kehabisan memori (OOM). OOM dapat mengakibatkan Penolakan Layanan. Untuk informasi selengkapnya, lihat Menghindari membaca badan permintaan besar atau isi respons ke dalam memori di artikel ini.

Lakukan ini: Contoh berikut sepenuhnya asinkron menggunakan isi permintaan yang tidak di-buffer:

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

Kode sebelumnya secara asinkron mendeserialisasikan isi permintaan ke dalam objek C#.

Lebih suka ReadFormAsync daripada Request.Form

Gunakan HttpContext.Request.ReadFormAsync alih-alih HttpContext.Request.Form. HttpContext.Request.Form dapat dibaca dengan aman hanya dengan kondisi berikut:

  • Formulir telah dibaca oleh panggilan ke ReadFormAsync, dan
  • Nilai formulir yang di-cache sedang dibaca menggunakan HttpContext.Request.Form

Jangan lakukan ini: Contoh berikut menggunakan HttpContext.Request.Form. HttpContext.Request.Formmenggunakan sinkronisasi melalui asinkron dan dapat menyebabkan kelaparan kumpulan utas.

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

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

        return Accepted();
    }

Lakukan ini: Contoh berikut menggunakan HttpContext.Request.ReadFormAsync untuk membaca isi formulir secara asinkron.

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

Hindari membaca badan permintaan besar atau badan respons ke dalam memori

Di .NET, setiap alokasi objek lebih besar dari atau sama dengan 85.000 byte berakhir di tumpukan objek besar (LOH). Objek besar mahal dengan dua cara:

  • Biaya alokasi tinggi karena memori untuk objek besar yang baru dialokasikan harus dibersihkan. CLR menjamin bahwa memori untuk semua objek yang baru dialokasikan dihapus.
  • LOH dikumpulkan dengan rest timbunan. LOH membutuhkan pengumpulan sampah penuh atau pengumpulan Gen2.

Posting blog ini menjelaskan masalahnya dengan tepat:

Saat objek besar dialokasikan, objek ditandai sebagai objek Gen 2. Bukan Gen 0 seperti untuk objek kecil. Konsekuensinya adalah bahwa jika Anda kehabisan memori di LOH, GC membersihkan seluruh tumpukan yang dikelola, tidak hanya LOH. Sehingga membersihkan Gen 0, Gen 1 dan Gen 2 termasuk LOH. Ini disebut pengumpulan sampah penuh dan merupakan pengumpulan sampah yang paling memakan waktu. Untuk banyak aplikasi, aplikasi dapat diterima. Tetapi jelas bukan untuk server web berkinerja tinggi, di mana beberapa buffer memori besar diperlukan untuk menangani permintaan web rata-rata (baca dari soket, dekompresi, dekode JSON, dan banyak lagi).

Menyimpan isi permintaan atau respons besar ke dalam satu byte[] atau string:

  • Dapat mengakibatkan kehabisan ruang dengan cepat di LOH.
  • Dapat menyebabkan masalah performa untuk aplikasi karena GC penuh berjalan.

Bekerja dengan API pemrosesan data sinkron

Saat menggunakan serializer/de-serializer yang hanya mendukung baca dan tulis sinkron (misalnya, Json.NET):

  • Buffer data ke dalam memori secara asinkron sebelum meneruskannya ke serializer/de-serializer.

Peringatan

Jika permintaan besar, permintaan tersebut dapat menyebabkan kondisi kehabisan memori (OOM). OOM dapat mengakibatkan Penolakan Layanan. Untuk informasi selengkapnya, lihat Menghindari membaca badan permintaan besar atau isi respons ke dalam memori di artikel ini.

ASP.NET Core 3.0 menggunakan System.Text.Json secara default untuk serialisasi JSON. System.Text.Json:

  • Membaca dan menulis JSON secara asinkron.
  • Dioptimalkan untuk teks UTF-8.
  • Biasanya performa lebih tinggi daripada Newtonsoft.Json.

Jangan simpan IHttpContextAccessor.HttpContext dalam bidang

IHttpContextAccessor.HttpContext mengembalikan permintaan aktif saat diakses dari utas HttpContext permintaan. IHttpContextAccessor.HttpContext tidak boleh disimpan dalam bidang atau variabel.

Jangan lakukan ini: Contoh berikut menyimpan HttpContext dalam bidang lalu mencoba menggunakannya nanti.

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

Kode sebelumnya sering mengambil null atau salah HttpContext di konstruktor.

Lakukan ini: Contoh berikut:

  • Menyimpan dalam IHttpContextAccessor bidang.
  • Menggunakan bidang pada HttpContext waktu yang benar dan memeriksa .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");
        }
    }
}

Jangan akses HttpContext dari beberapa utas

HttpContexttidak aman untuk utas. HttpContext Mengakses dari beberapa utas secara paralel dapat mengakibatkan perilaku tak terduga seperti server berhenti merespons, crash, dan kerusakan data.

Jangan lakukan ini: Contoh berikut membuat tiga permintaan paralel dan mencatat jalur permintaan masuk sebelum dan sesudah permintaan HTTP keluar. Jalur permintaan diakses dari beberapa utas, berpotensi paralel.

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

Lakukan ini: Contoh berikut menyalin semua data dari permintaan masuk sebelum membuat tiga permintaan paralel.

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

Jangan gunakan HttpContext setelah permintaan selesai

HttpContext hanya berlaku selama ada permintaan HTTP aktif di alur ASP.NET Core. Seluruh alur ASP.NET Core adalah rantai delegasi asinkron yang menjalankan setiap permintaan. Ketika dikembalikan Task dari rantai ini selesai, HttpContext didaur ulang.

Jangan lakukan ini: Contoh berikut menggunakan async void yang membuat permintaan HTTP selesai ketika yang pertama await tercapai:

  • Menggunakan async void selalu merupakan praktik buruk di aplikasi ASP.NET Core.
  • Contoh kode mengakses HttpResponse setelah permintaan HTTP selesai.
  • Akses terlambat merusak proses.
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");
    }
}

Lakukan ini: Contoh berikut mengembalikan Task ke kerangka kerja, sehingga permintaan HTTP tidak selesai sampai tindakan selesai.

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

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

Jangan ambil HttpContext di utas latar belakang

Jangan lakukan ini: Contoh berikut menunjukkan penutupan HttpContext menangkap dari Controller properti . Ini adalah praktik yang buruk karena item kerja bisa:

  • Jalankan di luar cakupan permintaan.
  • Mencoba untuk membaca yang salah 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();
}

Lakukan ini: Contoh berikut:

  • Menyalin data yang diperlukan dalam tugas latar belakang selama permintaan.
  • Tidak mereferensikan apa pun dari pengontrol.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

Tugas latar belakang harus diimplementasikan sebagai layanan yang dihosting. Untuk informasi selengkapnya, lihat Tugas latar belakang dengan layanan yang dihosting.

Jangan mengambil layanan yang disuntikkan ke pengontrol pada utas latar belakang

Jangan lakukan ini: Contoh berikut menunjukkan penutupan yang mengambil DbContext dari Controller parameter tindakan. Ini adalah praktik yang buruk. Item kerja dapat berjalan di luar cakupan permintaan. ContosoDbContext dilingkup ke permintaan, menghasilkan 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();
}

Lakukan ini: Contoh berikut:

  • Menyuntikkan IServiceScopeFactory untuk membuat cakupan dalam item kerja latar belakang. IServiceScopeFactory adalah singleton.
  • Membuat cakupan injeksi dependensi baru di utas latar belakang.
  • Tidak mereferensikan apa pun dari pengontrol.
  • Tidak mengambil ContosoDbContext dari permintaan masuk.
[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();
}

Kode yang disorot berikut:

  • Membuat cakupan untuk masa pakai operasi latar belakang dan menyelesaikan layanan darinya.
  • ContosoDbContext Menggunakan dari cakupan yang benar.
[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();
}

Jangan ubah kode status atau header setelah isi respons dimulai

ASP.NET Core tidak menyangga isi respons HTTP. Pertama kali respons ditulis:

  • Header dikirim bersama dengan potongan isi tersebut ke klien.
  • Tidak mungkin lagi mengubah header respons.

Jangan lakukan ini: Kode berikut mencoba menambahkan header respons setelah respons dimulai:

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

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

Dalam kode sebelumnya, context.Response.Headers["test"] = "test value"; akan melemparkan pengecualian jika next() telah ditulis ke respons.

Lakukan ini: Contoh berikut memeriksa apakah respons HTTP telah dimulai sebelum memodifikasi header.

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

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

Lakukan ini: Contoh berikut menggunakan HttpResponse.OnStarting untuk mengatur header sebelum header respons dihapus ke klien.

Memeriksa apakah respons belum dimulai memungkinkan mendaftarkan panggilan balik yang akan dipanggil tepat sebelum header respons ditulis. Memeriksa apakah respons belum dimulai:

  • Menyediakan kemampuan untuk menambahkan atau menimpa header tepat pada waktunya.
  • Tidak memerlukan pengetahuan tentang middleware berikutnya dalam alur.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Jangan panggil next() jika Anda sudah mulai menulis ke isi respons

Komponen hanya berharap untuk dipanggil jika mungkin bagi mereka untuk menangani dan memanipulasi respons.

Menggunakan Hosting dalam proses dengan IIS

Menggunakan hosting dalam proses, aplikasi ASP.NET Core berjalan dalam proses yang sama dengan proses pekerja IIS-nya. Hosting dalam proses memberikan peningkatan performa melalui hosting di luar proses karena permintaan tidak diproksi melalui adaptor loopback. Adaptor loopback adalah antarmuka jaringan yang mengembalikan lalu lintas jaringan keluar kembali ke komputer yang sama. IIS menangani manajemen proses dengan Windows Process Activation Service (WAS).

Proyek default ke model hosting dalam proses di ASP.NET Core 3.0 dan yang lebih baru.

Untuk informasi selengkapnya, lihat Host ASP.NET Core di Windows dengan IIS

Jangan asumsikan bahwa HttpRequest.ContentLength tidak null

HttpRequest.ContentLength null jika Content-Length header tidak diterima. Null dalam hal ini berarti panjang isi permintaan tidak diketahui; bukan berarti panjangnya adalah nol. Karena semua perbandingan dengan null (kecuali ==) mengembalikan false, perbandingan Request.ContentLength > 1024, misalnya, mungkin kembali false ketika ukuran isi permintaan lebih dari 1024. Tidak mengetahui hal ini dapat menyebabkan lubang keamanan di aplikasi. Anda mungkin berpikir bahwa Anda melindungi dari permintaan yang terlalu besar saat tidak.

Untuk informasi selengkapnya, lihat jawaban StackOverflow ini.

Pola aplikasi web yang andal

Lihat video dan artikel Reliable Web App Pattern for.NET YouTube untuk panduan tentang membuat aplikasi modern, andal, berkinerja, dapat diuji, hemat biaya, dan dapat diskalakan ASP.NET Core, baik dari awal atau refaktor aplikasi yang ada.