ASP.NET Web API 2'de Genel Hata İşlemeGlobal Error Handling in ASP.NET Web API 2

David Matsontarafından , Rick Andersonby David Matson, Rick Anderson

Bu konu, ASP.NET 4.x için ASP.NET Web API 2'deki genel hata işleme genel görünümünü sağlar.This topic provides an overview of global error handling in ASP.NET Web API 2 for ASP.NET 4.x. Bugün Web API'sinde hataları genel olarak günlüğe kaydetmenin veya işlemenin kolay bir yolu yoktur.Today there's no easy way in Web API to log or handle errors globally. Bazı işlenmemiş özel durumlar özel durum filtreleriaracılığıyla işlenebilir, ancak özel durum filtrelerinin işleyebileceği birkaç özel durum vardır.Some unhandled exceptions can be processed via exception filters, but there are a number of cases that exception filters can't handle. Örneğin:For example:

  1. Denetleyici oluşturuculardan atılan özel durumlar.Exceptions thrown from controller constructors.
  2. İleti işleyicilerinden atılan özel durumlar.Exceptions thrown from message handlers.
  3. Yönlendirme sırasında atılan özel durumlar.Exceptions thrown during routing.
  4. Yanıt içeriği serileştirme sırasında atılan özel durumlar.Exceptions thrown during response content serialization.

Bu özel durumları (mümkünse) günlüğe kaydetmek ve işlemek için basit ve tutarlı bir yol sağlamak istiyoruz.We want to provide a simple, consistent way to log and handle (where possible) these exceptions.

Özel durumları işlemek için iki önemli durum vardır, hata yanıtı gönderebildiğimiz ve tek yapabildiğimiz özel durumu günlüğe kaydetmek tir.There are two major cases for handling exceptions, the case where we are able to send an error response and the case where all we can do is log the exception. İkinci durum için bir örnek, bir özel durum akış yanıt içeriğinin ortasına atıldığında; bu durumda durum kodu, üstbilgi ve kısmi içerik zaten tel üzerinden gitti, bu yüzden sadece bağlantı iptal beri yeni bir yanıt iletisi göndermek için çok geç.An example for the latter case is when an exception is thrown in the middle of streaming response content; in that case it is too late to send a new response message since the status code, headers, and partial content have already gone across the wire, so we simply abort the connection. Özel durum yeni bir yanıt iletisi oluşturmak için ele alınamasa da, özel durumu günlüğe kaydetmeyi yine de destekliyoruz.Even though the exception can't be handled to produce a new response message, we still support logging the exception. Bir hata algılayabildiğimiz durumlarda, aşağıdakilerde gösterildiği gibi uygun bir hata yanıtı döndürebiliriz:In cases where we can detect an error, we can return an appropriate error response as shown in the following:

public IHttpActionResult GetProduct(int id)
{
    var product = products.FirstOrDefault((p) => p.Id == id);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product);
}

Varolan SeçeneklerExisting Options

Özel durum filtrelerineek olarak, ileti işleyicileri bugün 500 düzeyli yanıtların tümlerini gözlemlemek için kullanılabilir, ancak özgün hatayla ilgili bağlamları olmadığı için bu yanıtlara göre hareket etmek zordur.In addition to exception filters, message handlers can be used today to observe all 500-level responses, but acting on those responses is difficult, as they lack context about the original error. İleti işleyicileri de işleyebilir servis talepleri ile ilgili özel durum filtreleri olarak aynı sınırlamalar bazı vardır.Message handlers also have some of the same limitations as exception filters regarding the cases they can handle. Web API'de hata koşullarını yakalayan izleme altyapısı olsa da, izleme altyapısı tanılama amacıyladır ve üretim ortamlarında çalıştırmak için tasarlanmamıştır veya uygun değildir.While Web API does have tracing infrastructure that captures error conditions the tracing infrastructure is for diagnostics purposes and is not designed or suited for running in production environments. Genel özel durum işleme ve günlüğe kaydetme, üretim sırasında çalıştırılabilen ve varolan izleme çözümlerine bağlanabilen hizmetler olmalıdır (örneğin, ELMAH).Global exception handling and logging should be services that can run during production and be plugged into existing monitoring solutions (for example, ELMAH).

Çözüme Genel BakışSolution Overview

İşlenmemiş özel durumları günlüğe kaydetmek ve işlemek için iki yeni kullanıcı değiştirilebilir hizmet , IExceptionLogger ve IExceptionHandler saiyoruz.We provide two new user-replaceable services, IExceptionLogger and IExceptionHandler, to log and handle unhandled exceptions. Hizmetler çok benzer, iki ana farklılıklar ile:The services are very similar, with two main differences:

  1. Birden çok özel durum kaydedicisi, ancak yalnızca tek bir özel durum işleyicisi kaydetmeyi destekliyoruz.We support registering multiple exception loggers but only a single exception handler.
  2. İstisna kaydediciler her zaman çağrılır, bağlantıyı iptal etmek üzere olsak bile.Exception loggers always get called, even if we're about to abort the connection. Özel durum işleyicileri yalnızca hangi yanıt iletisini göndereceğimizi hala seçebildiğimizde çağrılır.Exception handlers only get called when we're still able to choose which response message to send.

Her iki hizmet de, özel durum algılandığı noktadan ilgili bilgileri içeren bir özel durum bağlamına, özellikle HttpRequestMessage, HttpRequestContext,atılan özel durum ve özel durum kaynağına (aşağıdaki ayrıntılar) erişim sağlar.Both services provide access to an exception context containing relevant information from the point where the exception was detected, particularly the HttpRequestMessage, the HttpRequestContext, the thrown exception and the exception source (details below).

Tasarım İlkeleriDesign Principles

  1. Son dakika değişikliği yok Bu işlevsellik küçük bir sürümde eklendiğiniz için, çözümü etkileyen önemli bir kısıtlama, sözleşme yazmak veya davranış için herhangi bir kırılma değişiklik olmamasıdır.No breaking changes Because this functionality is being added in a minor release, one important constraint impacting the solution is that there be no breaking changes, either to type contracts or to behavior. Bu kısıtlama, özel durumları 500 yanıta dönüştüren varolan catch blokları açısından yapmak istediğimiz bazı temizlemeleri ekarte etti.This constraint ruled out some cleanup we would like to have done in terms of existing catch blocks turning exceptions into 500 responses. Bu ek temizleme biz bir sonraki büyük sürümü için düşünebilirsiniz bir şeydir.This additional cleanup is something we might consider for a subsequent major release. Bu sizin için önemli yse, lütfen ASP.NET Web API kullanıcı sesinde oy verin.If this is important to you please vote on it at ASP.NET Web API user voice.
  2. Web API yapıları ile tutarlılık sağlama Web API'nin filtre boru hattı, eyleme özel, denetleyiciye özgü veya küresel kapsamda mantığı uygulama esnekliğiyle çapraz kesme yle başa çıkmanın harika bir yoludur.Maintaining consistency with Web API constructs Web API's filter pipeline is a great way to handle cross-cutting concerns with the flexibility of applying the logic at an action-specific, controller-specific or global scope. Özel durum filtreleri de dahil olmak üzere filtreler, genel kapsamda kaydedilmiş olsa lar bile her zaman eylem ve denetleyici bağlamlarına sahiptir.Filters, including exception filters, always have action and controller contexts, even when registered at the global scope. Bu sözleşme filtreler için anlamlıdır, ancak özel durum filtreleri, hatta genel olarak kapsamlı olanlar, hiçbir eylem veya denetleyici bağlam var ileti işleyicileri, özel durumlar gibi bazı özel durum işleme durumlarda için iyi bir uyum olmadığı anlamına gelir.That contract makes sense for filters, but it means that exception filters, even globally scoped ones, aren't a good fit for some exception handling cases, such as exceptions from message handlers, where no action or controller context exists. Özel durum işleme için filtreler tarafından sağlanan esnek kapsam landırmayı kullanmak istiyorsak, yine de özel durum filtrelerine ihtiyacımız vardır.If we want to use the flexible scoping afforded by filters for exception handling, we still need exception filters. Ancak, denetleyici bağlamı dışında özel durumu ele almamız gerekirse, tam genel hata işleme için ayrı bir yapıya da ihtiyacımız vardır (denetleyici bağlamı ve eylem bağlamı kısıtlamaları olmayan bir şey).But if we need to handle exception outside of a controller context, we also need a separate construct for full global error handling (something without the controller context and action context constraints).

Ne Zaman KullanılırWhen to Use

  • Özel durum kaydediciler, Web API tarafından yakalanan tüm işlenmemiş özel durum ları görmenin çözümüdür.Exception loggers are the solution to seeing all unhandled exception caught by Web API.
  • Özel durum işleyicileri, Web API tarafından yakalanan işlenmemiş özel durumlara olası tüm yanıtları özelleştirmek için çözümdür.Exception handlers are the solution for customizing all possible responses to unhandled exceptions caught by Web API.
  • Özel durum filtreleri, belirli bir eylem veya denetleyiciyle ilgili işlenmemiş özel durumları işlemek için en kolay çözümdür.Exception filters are the easiest solution for processing the subset unhandled exceptions related to a specific action or controller.

Servis DetaylarıService Details

Özel durum kaydedici ve işleyici hizmet arabirimleri, ilgili bağlamları alarak basit async yöntemleridir:The exception logger and handler service interfaces are simple async methods taking the respective contexts:

public interface IExceptionLogger
{
   Task LogAsync(ExceptionLoggerContext context, 
                 CancellationToken cancellationToken);
}

public interface IExceptionHandler
{
   Task HandleAsync(ExceptionHandlerContext context, 
                    CancellationToken cancellationToken);
}

Ayrıca, bu arabirimlerin her ikisi için de temel sınıflar salıyoruz.We also provide base classes for both of these interfaces. Çekirdek (eşitleme veya eşitleme) yöntemlerini geçersiz kılmak, önerilen zamanlarda günlüğe kaydetmek veya işlemek için gereken tek şeydir.Overriding the core (sync or async) methods is all that is required to log or handle at the recommended times. Günlüğe kaydetme ExceptionLogger için, temel sınıf çekirdek günlüğe kaydetme yönteminin her özel durum için yalnızca bir kez çağrılmasını sağlar (daha sonra çağrı yığınını daha da yukarı yaysa ve tekrar yakalanırsa bile).For logging, the ExceptionLogger base class will ensure that the core logging method is only called once for each exception (even if it later propagates further up the call stack and is caught again). ExceptionHandler Taban sınıf, eski iç içe geçme bloklarını yoksayarak yalnızca çağrı yığınının üst kısmındaki özel durumlar için çekirdek işleme yöntemini çağırır.The ExceptionHandler base class will call the core handling method only for exceptions at the top of the call stack, ignoring legacy nested catch blocks. (Bu temel sınıfların basitleştirilmiş sürümleri aşağıdaki ekte yer almaktadır.) Her IExceptionLogger IExceptionHandler ikisi de ve bir ExceptionContextüzerinden özel durum hakkında bilgi almak.(Simplified versions of these base classes are in the appendix below.) Both IExceptionLogger and IExceptionHandler receive information about the exception via an ExceptionContext.

public class ExceptionContext
{
   public Exception Exception { get; set; }

   public HttpRequestMessage Request { get; set; }

   public HttpRequestContext RequestContext { get; set; }

   public HttpControllerContext ControllerContext { get; set; }

   public HttpActionContext ActionContext { get; set; }

   public HttpResponseMessage Response { get; set; }

   public string CatchBlock { get; set; }

   public bool IsTopLevelCatchBlock { get; set; }
}

Çerçeve bir özel durum kaydedici veya özel durum işleyicisi çağırır, her zaman bir Exception ve bir Request.When the framework calls an exception logger or an exception handler, it will always provide an Exception and a Request. Birim testi dışında, aynı zamanda RequestContexther zaman bir sağlayacaktır .Except for unit testing, it will also always provide a RequestContext. Nadiren bir ControllerContext ve ActionContext (yalnızca özel durum filtreleri için catch bloğundan ararken) sağlar.It will rarely provide a ControllerContext and ActionContext (only when calling from the catch block for exception filters). Çok nadiren bir Response(sadece bazı IIS durumlarda zaman yanıt yazmaya çalışırken ortasında) sağlayacaktır.It will very rarely provide a Response(only in certain IIS cases when in the middle of trying to write the response). Bu özelliklerden bazıları olabileceğinden, null özel durum sınıfının null üyelerine erişmeden önce denetlemenin tüketiciye bağlı olduğunu unutmayın.CatchBlockNote that because some of these properties may be null it is up to the consumer to check for null before accessing members of the exception class.CatchBlock catch bloğunun özel durumu gördüğünü belirten bir dizedir.is a string indicating which catch block saw the exception. Catch block dizeleri aşağıdaki gibidir:The catch block strings are as follows:

  • HttpServer (SendAsync yöntemi)HttpServer (SendAsync method)

  • HttpControllerDispatcher (SendAsync yöntemi)HttpControllerDispatcher (SendAsync method)

  • HttpBatchHandler (SendAsync yöntemi)HttpBatchHandler (SendAsync method)

  • IExceptionFilter (ApiController'ın ExecuteAsync'deki özel durum filtresi denetim hattını işlemesi)IExceptionFilter (ApiController's processing of the exception filter pipeline in ExecuteAsync)

  • OWIN ev sahibi:OWIN host:

    • HttpMessageHandlerAdapter.BufferResponseContentAsync (çıktı arabelleğe alma için)HttpMessageHandlerAdapter.BufferResponseContentAsync (for buffering output)
    • HttpMessageHandlerAdapter.CopyResponseContentAsync (akış çıktısı için)HttpMessageHandlerAdapter.CopyResponseContentAsync (for streaming output)
  • Web barındırma:Web host:

    • httpControllerHandler.WriteBufferedResponseContentAsync (çıktı arabelleğe alma için)HttpControllerHandler.WriteBufferedResponseContentAsync (for buffering output)
    • httpControllerHandler.WriteStreamedResponseContentAsync (akış çıktısı için)HttpControllerHandler.WriteStreamedResponseContentAsync (for streaming output)
    • httpControllerHandler.WriteErrorResponseContentAsync (arabelleğe alma modu altında hata kurtarma hataları için)HttpControllerHandler.WriteErrorResponseContentAsync (for failures in error recovery under buffered output mode)

Catch block dizeleri listesi statik salt özellikleri ile de kullanılabilir.The list of catch block strings is also available via static readonly properties. (Çekirdek catch blok dizesi statik ExceptionCatchBlocks üzerindedir; geri kalanı OWIN ve web ana bilgisayar için her biri statik bir sınıfta görünür).IsTopLevelCatchBlock(The core catch block string are on the static ExceptionCatchBlocks; the remainder appear on one static class each for OWIN and web host).IsTopLevelCatchBlock yalnızca çağrı yığınının üst kısmında ki özel durumları işleme nin önerilen deseni takip etmek için yararlıdır.is helpful for following the recommended pattern of handling exceptions only at the top of the call stack. İç içe geçme bloğu oluştuğu her yerde özel durumları 500 yanıta dönüştürmek yerine, özel durum işleyicisi, ana bilgisayar tarafından görülmek üzere olana kadar özel durumların yayılmasına izin verebilir.Rather than turning exceptions into 500 responses anywhere a nested catch block occurs, an exception handler can let exceptions propagate until they are about to be seen by the host.

Ek ExceptionContextolarak, bir logger tam ExceptionLoggerContextüzerinden bilgi bir parça daha alır:In addition to the ExceptionContext, a logger gets one more piece of information via the full ExceptionLoggerContext:

public class ExceptionLoggerContext
{
   public ExceptionContext ExceptionContext { get; set; }
   public bool CanBeHandled { get; set; }
}

İkinci özellik, CanBeHandledbir logger işlenemez bir özel durum tanımlamak için izin verir.The second property, CanBeHandled, allows a logger to identify an exception that cannot be handled. Bağlantı iptal etmek üzereyken ve yeni yanıt iletisi gönderilemiyorsa, kaydediciler çağrılacak, ancak işleyici çağrılmaz ve günbazı bu senaryoyu bu özellikten tanımlayabilir.When the connection is about to be aborted and no new response message can be sent, the loggers will be called but the handler will not be called, and the loggers can identify this scenario from this property.

Ek ExceptionContextolarak, bir işleyici özel durum işlemek için ExceptionHandlerContext tam ayarlayabilirsiniz bir özellik daha alır:In additional to the ExceptionContext, a handler gets one more property it can set on the full ExceptionHandlerContext to handle the exception:

public class ExceptionHandlerContext
{
   public ExceptionContext ExceptionContext { get; set; }
   public IHttpActionResult Result { get; set; }
}

Bir özel durum işleyicisi, Result özelliği bir eylem sonucuna ayarlayarak bir özel durum işlettiğini gösterir (örneğin, bir Özel Durum Sonucu, InternalServerErrorResult, StatusCodeResult, veya özel bir sonuç).An exception handler indicates that it has handled an exception by setting the Result property to an action result (for example, an ExceptionResult, InternalServerErrorResult, StatusCodeResult, or a custom result). Result Özellik null ise, özel durum işlenir ve özgün özel durum yeniden atılır.If the Result property is null, the exception is unhandled and the original exception will be re-thrown.

Arama yığınının üst kısmındaki istisnalar için, yanıtın API arayanlar için uygun olduğundan emin olmak için fazladan bir adım attık.For exceptions at the top of the call stack, we took an extra step to ensure the response is appropriate for API callers. Özel durum ana bilgisayara yayılırsa, arayan normal bir şekilde HTML olan ve genellikle uygun bir API hata yanıtı olmayan yanıt veren sarı ölüm ekranını veya başka bir ana bilgisayarı görür.If the exception propagates up to the host, the caller would see the yellow screen of death or some other host provided response which is typically HTML and not usually an appropriate API error response. Bu gibi durumlarda, Sonuç null olmayan başlar ve yalnızca özel bir özel durum null işleyicisi açıkça geri ayarlar (işlenmemiş) özel durum ana bilgisayara yayılır.In these cases, the Result starts out non-null, and only if a custom exception handler explicitly sets it back to null (unhandled) will the exception propagate to the host. Bu gibi durumlarda ayar Result iki senaryo için yararlı olabilir: nullSetting Result to null in such cases can be useful for two scenarios:

  1. OWIN, Web API'sinden önce/dışında kayıtlı ara yazılımları işleyen özel istisnalarla Birlikte Web API'yi barındırıdadır.OWIN hosted Web API with custom exception handling middleware registered before/outside Web API.
  2. Ölümün sarı ekranının aslında işlenmemiş bir özel durum için yararlı bir yanıt olduğu bir tarayıcı üzerinden yerel hata ayıklama.Local debugging via a browser, where the yellow screen of death is actually a helpful response for an unhandled exception.

Hem özel durum kaydediciler hem de özel durum işleyicileri için, logger veya işleyicinin kendisi bir özel durum atarsa kurtarmak için hiçbir şey yapmayız.For both exception loggers and exception handlers, we don't do anything to recover if the logger or handler itself throws an exception. (İstisnanın yayılmasına izin vermek dışında, daha iyi bir yaklaşımınız varsa bu sayfanın alt kısmında geri bildirim bırakın.) İstisna kaydediciler ve işleyicileri için sözleşme, istisnalar arayanlar kadar yaymak izin vermemek gerektiğini; aksi takdirde, özel durum genellikle bir HTML hatası (ASP gibi) sonuçlanan ana bilgisayara tüm yol, yayılır. NET'in sarı ekranı) istemciye geri gönderiliyor (genellikle JSON veya XML bekleyen API arayanlar için tercih edilen seçenek değildir).(Other than letting the exception propagate, leave feedback at the bottom of this page if you have a better approach.) The contract for exception loggers and handlers is that they should not let exceptions propagate up to their callers; otherwise, the exception will just propagate, often all the way to the host resulting in an HTML error (like ASP.NET's yellow screen) being sent back to the client (which usually isn't the preferred option for API callers that expect JSON or XML).

ÖrneklerExamples

Özel Durum Kaydedici'nin İzini İzlemeTracing Exception Logger

Aşağıdaki özel durum kaydedicisi, yapılandırılmış İzleme kaynaklarına (Visual Studio'daki Hata Ayıklama çıkış penceresi dahil) özel durum verilerini gönderir.The exception logger below sends exception data to configured Trace sources (including the Debug output window in Visual Studio).

class TraceExceptionLogger : ExceptionLogger
{
    public override void LogCore(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Özel Hata İletiSi Özel Durum IşleyicisiCustom Error Message Exception Handler

Aşağıdaki özel durum işleyicisi, desteğe başvurmak için bir e-posta adresi de dahil olmak üzere istemcilere özel bir hata yanıtı üretir.The exception handler below produces a custom error response to clients, including an email address for contacting support.

class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong." +
                      "Please contact support@contoso.com so we can try to fix it."
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

Özel Durum Filtrelerini KaydetmeRegistering Exception Filters

Projenizi oluşturmak için "ASP.NET MVC 4 Web Uygulaması" proje şablonunu kullanıyorsanız, WebApiConfig Web API yapılandırma kodunuzu sınıfın içine, App_Start klasörüne koyun:If you use the "ASP.NET MVC 4 Web Application" project template to create your project, put your Web API configuration code inside the WebApiConfig class, in the App_Start folder:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());

        // Other configuration code...
    }
}

Ek: Taban Sınıf BilgileriAppendix: Base Class Details

public class ExceptionLogger : IExceptionLogger
{
    public virtual Task LogAsync(ExceptionLoggerContext context, 
                                 CancellationToken cancellationToken)
    {
        if (!ShouldLog(context))
        {
            return Task.FromResult(0);
        }

        return LogAsyncCore(context, cancellationToken);
    }

    public virtual Task LogAsyncCore(ExceptionLoggerContext context, 
                                     CancellationToken cancellationToken)
    {
        LogCore(context);
        return Task.FromResult(0);
    }

    public virtual void LogCore(ExceptionLoggerContext context)
    {
    }

    public virtual bool ShouldLog(ExceptionLoggerContext context)
    {
        IDictionary exceptionData = context.ExceptionContext.Exception.Data;

        if (!exceptionData.Contains("MS_LoggedBy"))
        {
            exceptionData.Add("MS_LoggedBy", new List<object>());
        }

        ICollection<object> loggedBy = ((ICollection<object>)exceptionData[LoggedByKey]);

        if (!loggedBy.Contains(this))
        {
            loggedBy.Add(this);
            return true;
        }
        else
        {
            return false;
        }
    }
}

public class ExceptionHandler : IExceptionHandler
{
    public virtual Task HandleAsync(ExceptionHandlerContext context, 
                                    CancellationToken cancellationToken)
    {
        if (!ShouldHandle(context))
        {
            return Task.FromResult(0);
        }

        return HandleAsyncCore(context, cancellationToken);
    }

    public virtual Task HandleAsyncCore(ExceptionHandlerContext context, 
                                       CancellationToken cancellationToken)
    {
        HandleCore(context);
        return Task.FromResult(0);
    }

    public virtual void HandleCore(ExceptionHandlerContext context)
    {
    }

    public virtual bool ShouldHandle(ExceptionHandlerContext context)
    {
        return context.ExceptionContext.IsOutermostCatchBlock;
    }
}