// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Results;
using System.Web.Http.WebHost.Properties;
namespace System.Web.Http.WebHost
{
/// Provides the default implementation for handling exceptions within Web API web host.
///
/// This class preserves the legacy behavior of catch blocks and is the the default registered IExceptionHandler
/// for web host.
///
internal class WebHostExceptionHandler : IExceptionHandler
{
private readonly IExceptionHandler _innerHandler;
public WebHostExceptionHandler(IExceptionHandler innerHandler)
{
Contract.Assert(innerHandler != null);
_innerHandler = innerHandler;
}
public IExceptionHandler InnerHandler
{
get { return _innerHandler; }
}
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
ExceptionContext exceptionContext = context.ExceptionContext;
Contract.Assert(exceptionContext != null);
if (exceptionContext.CatchBlock ==
WebHostExceptionCatchBlocks.HttpControllerHandlerBufferContent)
{
HandleWebHostBufferedContentException(context);
return TaskHelpers.Completed();
}
return _innerHandler.HandleAsync(context, cancellationToken);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "We already shipped this code; avoiding even minor breaking changes in error handling.")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "All exceptions caught here become error responses")]
private static void HandleWebHostBufferedContentException(ExceptionHandlerContext context)
{
Contract.Assert(context != null);
ExceptionContext exceptionContext = context.ExceptionContext;
Contract.Assert(exceptionContext != null);
Exception exception = exceptionContext.Exception;
Contract.Assert(exception != null);
HttpRequestMessage request = exceptionContext.Request;
if (request == null)
{
throw new ArgumentException(Error.Format(SRResources.TypePropertyMustNotBeNull,
typeof(ExceptionContext).Name, "Request"), "context");
}
HttpResponseMessage response = exceptionContext.Response;
if (response == null)
{
throw new ArgumentException(Error.Format(SRResources.TypePropertyMustNotBeNull,
typeof(ExceptionContext).Name, "Response"), "context");
}
HttpContent responseContent = response.Content;
if (responseContent == null)
{
throw new ArgumentException(Error.Format(SRResources.TypePropertyMustNotBeNull,
typeof(HttpResponseMessage).Name, "Content"), "context");
}
HttpResponseMessage errorResponse;
// Create a 500 response with content containing an explanatory message and
// stack trace, subject to content negotiation and policy for error details.
try
{
MediaTypeHeaderValue mediaType = responseContent.Headers.ContentType;
string messageDetails = (mediaType != null)
? Error.Format(
SRResources.Serialize_Response_Failed_MediaType,
responseContent.GetType().Name,
mediaType)
: Error.Format(
SRResources.Serialize_Response_Failed,
responseContent.GetType().Name);
errorResponse = request.CreateErrorResponse(
HttpStatusCode.InternalServerError,
new InvalidOperationException(messageDetails, exception));
// CreateErrorResponse will choose 406 if it cannot find a formatter,
// but we want our default error response to be 500 always
errorResponse.StatusCode = HttpStatusCode.InternalServerError;
}
catch
{
// Failed creating an HttpResponseMessage for the error response.
// This can happen for missing config, missing conneg service, etc.
// Create an empty error response and return a non-faulted task.
errorResponse = request.CreateResponse(HttpStatusCode.InternalServerError);
}
context.Result = new ResponseMessageResult(errorResponse);
}
}
}