Menangani kesalahan di API web ASP.NET Core

Artikel ini menjelaskan cara menangani kesalahan dan menyesuaikan penanganan kesalahan dengan API web ASP.NET Core.

Halaman Pengecualian Pengembang

Halaman Pengecualian Pengembang memperlihatkan jejak tumpukan terperinci untuk kesalahan server. Ini menggunakan DeveloperExceptionPageMiddleware untuk menangkap pengecualian sinkron dan asinkron dari alur HTTP dan untuk menghasilkan respons kesalahan. Misalnya, pertimbangkan tindakan pengontrol berikut, yang melemparkan pengecualian:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Saat Halaman Pengecualian Pengembang mendeteksi pengecualian yang tidak tertangani, Halaman tersebut menghasilkan respons teks biasa default yang mirip dengan contoh berikut:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Jika klien meminta respons berformat HTML, Halaman Pengecualian Pengembang menghasilkan respons yang mirip dengan contoh berikut:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Untuk meminta respons berformat HTML, atur Accept header permintaan HTTP ke text/html.

Peringatan

Jangan aktifkan Halaman Pengecualian Pengembang kecuali aplikasi berjalan di lingkungan Pengembangan. Jangan bagikan informasi pengecualian terperinci secara publik saat aplikasi berjalan dalam produksi. Untuk informasi selengkapnya tentang mengonfigurasi lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core.

Handler pengecualian

Di lingkungan non-pengembangan, gunakan Middleware Penanganan Pengecualian untuk menghasilkan payload kesalahan:

  1. Di Program.cs, panggil UseExceptionHandler untuk menambahkan Middleware Penanganan Pengecualian:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Konfigurasikan tindakan pengontrol untuk merespons /error rute:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Tindakan sebelumnya HandleError mengirimkan payload yang mematuhi RFC 7807 ke klien.

Peringatan

Jangan tandai metode tindakan penangan kesalahan dengan atribut metode HTTP, seperti HttpGet. Kata kerja eksplisit mencegah beberapa permintaan mencapai metode tindakan.

Untuk API web yang menggunakan Swagger / OpenAPI, tandai tindakan handler kesalahan dengan atribut [ApiExplorer Pengaturan] dan atur propertinya IgnoreApi ke true. Konfigurasi atribut ini mengecualikan tindakan penanganan kesalahan dari spesifikasi OpenAPI aplikasi:

[ApiExplorerSettings(IgnoreApi = true)]

Izinkan akses anonim ke metode jika pengguna yang tidak diaturentikasi akan melihat kesalahan.

Middleware Penanganan Pengecualian juga dapat digunakan di lingkungan Pengembangan untuk menghasilkan format payload yang konsisten di semua lingkungan:

  1. Dalam Program.cs, daftarkan instans Middleware Penanganan Pengecualian khusus lingkungan:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    Dalam kode sebelumnya, middleware terdaftar dengan:

    • Rute /error-development di lingkungan Pengembangan.
    • Rute /error di lingkungan non-Pengembangan.

  2. Tambahkan tindakan pengontrol untuk rute Pengembangan dan non-Pengembangan:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Gunakan pengecualian untuk mengubah respons

Konten respons dapat dimodifikasi dari luar pengontrol menggunakan pengecualian kustom dan filter tindakan:

  1. Buat jenis pengecualian terkenal bernama HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Buat filter tindakan bernama HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    Filter sebelumnya menentukan Order nilai bilangan bulat maksimum dikurangi 10. Ini Order memungkinkan filter lain berjalan di akhir alur.

  3. Di Program.cs, tambahkan filter tindakan ke kumpulan filter:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Respons kesalahan kegagalan validasi

Untuk pengontrol API web, MVC merespons dengan ValidationProblemDetails jenis respons saat validasi model gagal. MVC menggunakan hasil InvalidModelStateResponseFactory untuk membangun respons kesalahan untuk kegagalan validasi. Contoh berikut mengganti pabrik default dengan implementasi yang juga mendukung respons pemformatan sebagai XML, di Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Respons kesalahan klien

Hasil kesalahan didefinisikan sebagai hasilnya dengan kode status HTTP 400 atau lebih tinggi. Untuk pengontrol API web, MVC mengubah hasil kesalahan untuk menghasilkan ProblemDetails.

Pembuatan ProblemDetails otomatis untuk kode status kesalahan diaktifkan secara default, tetapi respons kesalahan dapat dikonfigurasi dengan salah satu cara berikut:

  1. Menggunakan layanan detail masalah
  2. Menerapkan ProblemDetailsFactory
  3. Menggunakan ApiBehaviorOptions.ClientErrorMapping

Respons detail masalah default

File berikut Program.cs dihasilkan oleh templat aplikasi web untuk pengontrol API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pertimbangkan pengontrol berikut, yang mengembalikan BadRequest ketika input tidak valid:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

Respons detail masalah dihasilkan dengan kode sebelumnya ketika salah satu kondisi berikut berlaku:

  • Titik /api/values2/divide akhir dipanggil dengan penyebut nol.
  • Titik /api/values2/squareroot akhir dipanggil dengan radicand kurang dari nol.

Isi respons detail masalah default memiliki nilai , , titledan status berikuttype:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

Layanan detail masalah

ASP.NET Core mendukung pembuatan Detail Masalah untuk API HTTP menggunakan IProblemDetailsService. Untuk informasi selengkapnya, lihat layanan Detail masalah.

Kode berikut mengonfigurasi aplikasi untuk menghasilkan respons detail masalah untuk semua respons kesalahan klien HTTP dan server yang belum memiliki konten isi:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

Pertimbangkan pengontrol API dari bagian sebelumnya, yang mengembalikan BadRequest ketika input tidak valid:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

Respons detail masalah dihasilkan dengan kode sebelumnya ketika salah satu kondisi berikut berlaku:

  • Input yang tidak valid disediakan.
  • URI tidak memiliki titik akhir yang cocok.
  • Terjadi pengecualian yang tidak tertangani.

Pembuatan ProblemDetails otomatis untuk kode status kesalahan dinonaktifkan saat properti SuppressMapClientErrors diatur ke true:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Menggunakan kode sebelumnya, ketika pengontrol API mengembalikan BadRequest, status respons HTTP 400 dikembalikan tanpa isi respons. SuppressMapClientErrorsProblemDetails mencegah respons dibuat, bahkan saat memanggil WriteAsync titik akhir PENGONTROL API. WriteAsync dijelaskan kemudian dalam artikel ini.

Bagian berikutnya menunjukkan cara menyesuaikan isi respons detail masalah, menggunakan CustomizeProblemDetails, untuk mengembalikan respons yang lebih membantu. Untuk opsi penyesuaian lainnya, lihat Menyesuaikan detail masalah.

Menyesuaikan detail masalah dengan CustomizeProblemDetails

Kode berikut menggunakan ProblemDetailsOptions untuk mengatur CustomizeProblemDetails:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
        options.CustomizeProblemDetails = (context) =>
        {

            var mathErrorFeature = context.HttpContext.Features
                                                       .Get<MathErrorFeature>();
            if (mathErrorFeature is not null)
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError =>
                    ("Divison by zero is not defined.",
                                          "https://wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                                          "https://wikipedia.org/wiki/Square_root")
                };

                context.ProblemDetails.Type = details.Type;
                context.ProblemDetails.Title = "Bad Input";
                context.ProblemDetails.Detail = details.Detail;
            }
        }
    );

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pengontrol API yang diperbarui:

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Kode berikut berisi MathErrorFeature dan MathErrorType, yang digunakan dengan sampel sebelumnya:

// Custom Http Request Feature
class MathErrorFeature
{
    public MathErrorType MathError { get; set; }
}

// Custom math errors
enum MathErrorType
{
    DivisionByZeroError,
    NegativeRadicandError
}

Respons detail masalah dihasilkan dengan kode sebelumnya ketika salah satu kondisi berikut berlaku:

  • Titik /divide akhir dipanggil dengan penyebut nol.
  • Titik /squareroot akhir dipanggil dengan radicand kurang dari nol.
  • URI tidak memiliki titik akhir yang cocok.

Isi respons detail masalah berisi hal berikut ketika salah satu squareroot titik akhir dipanggil dengan radicand kurang dari nol:

{
  "type": "https://en.wikipedia.org/wiki/Square_root",
  "title": "Bad Input",
  "status": 400,
  "detail": "Negative or complex numbers are not allowed."
}

Menampilkan atau mengunduh kode sampel

Menerapkan ProblemDetailsFactory

MVC menggunakan Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory untuk menghasilkan semua instans ProblemDetails dan ValidationProblemDetails. Pabrik ini digunakan untuk:

Untuk menyesuaikan respons detail masalah, daftarkan implementasi ProblemDetailsFactory kustom di Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Menggunakan ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Gunakan properti untuk mengonfigurasi konten ProblemDetails respons. Misalnya, kode berikut dalam Program.cs memperbarui Link properti untuk 404 respons:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Sumber daya tambahan

Artikel ini menjelaskan cara menangani kesalahan dan menyesuaikan penanganan kesalahan dengan API web ASP.NET Core.

Halaman Pengecualian Pengembang

Halaman Pengecualian Pengembang memperlihatkan jejak tumpukan terperinci untuk kesalahan server. Ini menggunakan DeveloperExceptionPageMiddleware untuk menangkap pengecualian sinkron dan asinkron dari alur HTTP dan untuk menghasilkan respons kesalahan. Misalnya, pertimbangkan tindakan pengontrol berikut, yang melemparkan pengecualian:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Saat Halaman Pengecualian Pengembang mendeteksi pengecualian yang tidak tertangani, Halaman tersebut menghasilkan respons teks biasa default yang mirip dengan contoh berikut:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Jika klien meminta respons berformat HTML, Halaman Pengecualian Pengembang menghasilkan respons yang mirip dengan contoh berikut:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Untuk meminta respons berformat HTML, atur Accept header permintaan HTTP ke text/html.

Peringatan

Jangan aktifkan Halaman Pengecualian Pengembang kecuali aplikasi berjalan di lingkungan Pengembangan. Jangan bagikan informasi pengecualian terperinci secara publik saat aplikasi berjalan dalam produksi. Untuk informasi selengkapnya tentang mengonfigurasi lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core.

Handler pengecualian

Di lingkungan non-pengembangan, gunakan Middleware Penanganan Pengecualian untuk menghasilkan payload kesalahan:

  1. Di Program.cs, panggil UseExceptionHandler untuk menambahkan Middleware Penanganan Pengecualian:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Konfigurasikan tindakan pengontrol untuk merespons /error rute:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Tindakan sebelumnya HandleError mengirimkan payload yang mematuhi RFC 7807 ke klien.

Peringatan

Jangan tandai metode tindakan penangan kesalahan dengan atribut metode HTTP, seperti HttpGet. Kata kerja eksplisit mencegah beberapa permintaan mencapai metode tindakan.

Untuk API web yang menggunakan Swagger / OpenAPI, tandai tindakan handler kesalahan dengan atribut [ApiExplorer Pengaturan] dan atur propertinya IgnoreApi ke true. Konfigurasi atribut ini mengecualikan tindakan penanganan kesalahan dari spesifikasi OpenAPI aplikasi:

[ApiExplorerSettings(IgnoreApi = true)]

Izinkan akses anonim ke metode jika pengguna yang tidak diaturentikasi akan melihat kesalahan.

Middleware Penanganan Pengecualian juga dapat digunakan di lingkungan Pengembangan untuk menghasilkan format payload yang konsisten di semua lingkungan:

  1. Dalam Program.cs, daftarkan instans Middleware Penanganan Pengecualian khusus lingkungan:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    Dalam kode sebelumnya, middleware terdaftar dengan:

    • Rute /error-development di lingkungan Pengembangan.
    • Rute /error di lingkungan non-Pengembangan.

  2. Tambahkan tindakan pengontrol untuk rute Pengembangan dan non-Pengembangan:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Gunakan pengecualian untuk mengubah respons

Konten respons dapat dimodifikasi dari luar pengontrol menggunakan pengecualian kustom dan filter tindakan:

  1. Buat jenis pengecualian terkenal bernama HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Buat filter tindakan bernama HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    Filter sebelumnya menentukan Order nilai bilangan bulat maksimum dikurangi 10. Ini Order memungkinkan filter lain berjalan di akhir alur.

  3. Di Program.cs, tambahkan filter tindakan ke kumpulan filter:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Respons kesalahan kegagalan validasi

Untuk pengontrol API web, MVC merespons dengan ValidationProblemDetails jenis respons saat validasi model gagal. MVC menggunakan hasil InvalidModelStateResponseFactory untuk membangun respons kesalahan untuk kegagalan validasi. Contoh berikut mengganti pabrik default dengan implementasi yang juga mendukung respons pemformatan sebagai XML, di Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Respons kesalahan klien

Hasil kesalahan didefinisikan sebagai hasilnya dengan kode status HTTP 400 atau lebih tinggi. Untuk pengontrol API web, MVC mengubah hasil kesalahan untuk menghasilkan ProblemDetails.

Respons kesalahan dapat dikonfigurasi dengan salah satu cara berikut:

  1. Menerapkan ProblemDetailsFactory
  2. Menggunakan ApiBehaviorOptions.ClientErrorMapping

Menerapkan ProblemDetailsFactory

MVC menggunakan Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory untuk menghasilkan semua instans ProblemDetails dan ValidationProblemDetails. Pabrik ini digunakan untuk:

Untuk menyesuaikan respons detail masalah, daftarkan implementasi ProblemDetailsFactory kustom di Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Menggunakan ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Gunakan properti untuk mengonfigurasi konten ProblemDetails respons. Misalnya, kode berikut dalam Program.cs memperbarui Link properti untuk 404 respons:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Middleware Kustom untuk menangani pengecualian

Default dalam penanganan pengecualian middleware berfungsi dengan baik untuk sebagian besar aplikasi. Untuk aplikasi yang memerlukan penanganan pengecualian khusus, pertimbangkan untuk menyesuaikan middleware penanganan pengecualian.

Menghasilkan payload ProblemDetails untuk pengecualian

ASP.NET Core tidak menghasilkan payload kesalahan standar saat terjadi pengecualian yang tidak tertangani. Untuk skenario di mana diinginkan untuk mengembalikan respons ProblemDetails standar kepada klien, middleware ProblemDetails dapat digunakan untuk memetakan pengecualian dan 404 respons terhadap payload ProblemDetails. Middleware penanganan pengecualian juga dapat digunakan untuk mengembalikan ProblemDetails payload untuk pengecualian yang tidak tertangani.

Sumber daya tambahan

Artikel ini menjelaskan cara menangani dan menyesuaikan penanganan kesalahan dengan API web ASP.NET Core.

Melihat atau mengunduh kode sampel (Cara mengunduh)

Halaman Pengecualian Pengembang

Halaman Pengecualian Pengembang adalah alat yang berguna untuk mendapatkan jejak tumpukan terperinci untuk kesalahan server. Ini menggunakan DeveloperExceptionPageMiddleware untuk menangkap pengecualian sinkron dan asinkron dari alur HTTP dan untuk menghasilkan respons kesalahan. Untuk mengilustrasikan, pertimbangkan tindakan pengontrol berikut:

[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
    if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException(
            $"We don't offer a weather forecast for {city}.", nameof(city));
    }
    
    return GetWeather().First();
}

Jalankan perintah berikut curl untuk menguji tindakan sebelumnya:

curl -i https://localhost:5001/weatherforecast/chicago

Halaman Pengecualian Pengembang menampilkan respons teks biasa jika klien tidak meminta output berformat HTML. Output berikut muncul:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT

System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
   at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1

Untuk menampilkan respons berformat HTML, atur Accept header permintaan HTTP ke text/html jenis media. Misalnya:

curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago

Pertimbangkan kutipan berikut dari respons HTTP:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

Respons berformat HTML menjadi berguna saat menguji melalui alat seperti Postman. Tangkapan layar berikut menunjukkan teks biasa dan respons berformat HTML di Postman:

Test the Developer Exception Page in Postman.

Peringatan

Aktifkan Halaman Pengecualian Pengembang hanya saat aplikasi berjalan di lingkungan Pengembangan. Jangan bagikan informasi pengecualian terperinci secara publik saat aplikasi berjalan dalam produksi. Untuk informasi selengkapnya tentang mengonfigurasi lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core.

Jangan tandai metode tindakan penangan kesalahan dengan atribut metode HTTP, seperti HttpGet. Kata kerja eksplisit mencegah beberapa permintaan mencapai metode tindakan. Izinkan akses anonim ke metode jika pengguna yang tidak diaturentikasi akan melihat kesalahan.

Handler pengecualian

Di lingkungan non-pengembangan, Middleware Penanganan Pengecualian dapat digunakan untuk menghasilkan payload kesalahan:

  1. Di Startup.Configure, panggil UseExceptionHandler untuk menggunakan middleware:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  2. Konfigurasikan tindakan pengontrol untuk merespons /error rute:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

Tindakan sebelumnya Error mengirimkan payload yang mematuhi RFC 7807 ke klien.

Middleware Penanganan Pengecualian juga dapat memberikan output yang dinegosiasikan konten yang lebih rinci di lingkungan pengembangan lokal. Gunakan langkah-langkah berikut untuk menghasilkan format payload yang konsisten di seluruh lingkungan pengembangan dan produksi:

  1. Dalam Startup.Configure, daftarkan instans Middleware Penanganan Pengecualian khusus lingkungan:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    

    Dalam kode sebelumnya, middleware terdaftar dengan:

    • Rute /error-local-development di lingkungan Pengembangan.
    • Rute /error di lingkungan yang bukan Pengembangan.

  2. Terapkan perutean atribut ke tindakan pengontrol:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            if (webHostEnvironment.EnvironmentName != "Development")
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
            return Problem(
                detail: context.Error.StackTrace,
                title: context.Error.Message);
        }
    
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

    Kode sebelumnya memanggil ControllerBase.Problem untuk membuat ProblemDetails respons.

Gunakan pengecualian untuk mengubah respons

Konten respons dapat dimodifikasi dari luar pengontrol. Dalam ASP.NET 4.x Web API, salah satu cara untuk melakukan ini adalah menggunakan jenis .HttpResponseException ASP.NET Core tidak menyertakan jenis yang setara. Dukungan untuk HttpResponseException dapat ditambahkan dengan langkah-langkah berikut:

  1. Buat jenis pengecualian terkenal bernama HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Buat filter tindakan bernama HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException exception)
            {
                context.Result = new ObjectResult(exception.Value)
                {
                    StatusCode = exception.Status,
                };
                context.ExceptionHandled = true;
            }
        }
    }
    

    Filter sebelumnya menentukan Order nilai bilangan bulat maksimum dikurangi 10. Ini Order memungkinkan filter lain berjalan di akhir alur.

  3. Di Startup.ConfigureServices, tambahkan filter tindakan ke kumpulan filter:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    

Respons kesalahan kegagalan validasi

Untuk pengontrol API web, MVC merespons dengan ValidationProblemDetails jenis respons saat validasi model gagal. MVC menggunakan hasil InvalidModelStateResponseFactory untuk membangun respons kesalahan untuk kegagalan validasi. Contoh berikut menggunakan pabrik untuk mengubah jenis respons default menjadi SerializableError di Startup.ConfigureServices:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });

Respons kesalahan klien

Hasil kesalahan didefinisikan sebagai hasilnya dengan kode status HTTP 400 atau lebih tinggi. Untuk pengontrol API web, MVC mengubah hasil kesalahan menjadi hasil dengan ProblemDetails.

Respons kesalahan dapat dikonfigurasi dengan salah satu cara berikut:

  1. Menerapkan ProblemDetailsFactory
  2. Menggunakan ApiBehaviorOptions.ClientErrorMapping

Menerapkan ProblemDetailsFactory

MVC menggunakan Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory untuk menghasilkan semua instans ProblemDetails dan ValidationProblemDetails. Pabrik ini digunakan untuk:

Untuk menyesuaikan respons detail masalah, daftarkan implementasi ProblemDetailsFactory kustom di Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection serviceCollection)
{
    services.AddControllers();
    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

Menggunakan ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Gunakan properti untuk mengonfigurasi konten ProblemDetails respons. Misalnya, kode berikut dalam Startup.ConfigureServices memperbarui type properti untuk 404 respons:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
        options.DisableImplicitFromServicesParameters = true;
    });

Middleware Kustom untuk menangani pengecualian

Default dalam penanganan pengecualian middleware berfungsi dengan baik untuk sebagian besar aplikasi. Untuk aplikasi yang memerlukan penanganan pengecualian khusus, pertimbangkan untuk menyesuaikan middleware penanganan pengecualian.

Menghasilkan payload ProblemDetails untuk pengecualian

ASP.NET Core tidak menghasilkan payload kesalahan standar saat terjadi pengecualian yang tidak tertangani. Untuk skenario di mana diinginkan untuk mengembalikan respons ProblemDetails standar kepada klien, middleware ProblemDetails dapat digunakan untuk memetakan pengecualian dan 404 respons terhadap payload ProblemDetails. Middleware penanganan pengecualian juga dapat digunakan untuk mengembalikan ProblemDetails payload untuk pengecualian yang tidak tertangani.