// 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.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.Properties;
namespace System.Web.Http.Results
{
///
/// Represents an action result that returns a response and
/// performs content negotiation on an based on an .
///
public class ExceptionResult : IHttpActionResult
{
private readonly Exception _exception;
private readonly IDependencyProvider _dependencies;
/// Initializes a new instance of the class.
/// The exception to include in the error.
///
/// if the error should include exception messages; otherwise, .
///
/// The content negotiator to handle content negotiation.
/// The request message which led to this result.
/// The formatters to use to negotiate and format the content.
public ExceptionResult(Exception exception, bool includeErrorDetail, IContentNegotiator contentNegotiator,
HttpRequestMessage request, IEnumerable formatters)
: this(exception, new DirectDependencyProvider(includeErrorDetail, contentNegotiator, request,
formatters))
{
}
/// Initializes a new instance of the class.
/// The exception to include in the error.
/// The controller from which to obtain the dependencies needed for execution.
public ExceptionResult(Exception exception, ApiController controller)
: this(exception, new ApiControllerDependencyProvider(controller))
{
}
private ExceptionResult(Exception exception, IDependencyProvider dependencies)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}
Contract.Assert(dependencies != null);
_exception = exception;
_dependencies = dependencies;
}
/// Gets the exception to include in the error.
public Exception Exception
{
get { return _exception; }
}
/// Gets a value indicating whether the error should include exception messages.
public bool IncludeErrorDetail
{
get { return _dependencies.IncludeErrorDetail; }
}
/// Gets the content negotiator to handle content negotiation.
public IContentNegotiator ContentNegotiator
{
get { return _dependencies.ContentNegotiator; }
}
/// Gets the request message which led to this result.
public HttpRequestMessage Request
{
get { return _dependencies.Request; }
}
/// Gets the formatters to use to negotiate and format the content.
public IEnumerable Formatters
{
get { return _dependencies.Formatters; }
}
///
public virtual Task ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
HttpError error = new HttpError(_exception, _dependencies.IncludeErrorDetail);
return NegotiatedContentResult.Execute(HttpStatusCode.InternalServerError, error,
_dependencies.ContentNegotiator, _dependencies.Request, _dependencies.Formatters);
}
/// Defines a provider for dependencies that are not always directly available.
///
/// This abstraction supports the unit testing scenario of creating the result without creating a content
/// negotiator, request message, or formatters. (The ApiController provider implementation does lazy evaluation
/// to make that scenario work.)
///
internal interface IDependencyProvider
{
bool IncludeErrorDetail { get; }
IContentNegotiator ContentNegotiator { get; }
HttpRequestMessage Request { get; }
IEnumerable Formatters { get; }
}
internal sealed class DirectDependencyProvider : IDependencyProvider
{
private readonly bool _includeErrorDetail;
private readonly IContentNegotiator _contentNegotiator;
private readonly HttpRequestMessage _request;
private readonly IEnumerable _formatters;
public DirectDependencyProvider(bool includeErrorDetail, IContentNegotiator contentNegotiator,
HttpRequestMessage request, IEnumerable formatters)
{
if (contentNegotiator == null)
{
throw new ArgumentNullException("contentNegotiator");
}
if (request == null)
{
throw new ArgumentNullException("request");
}
if (formatters == null)
{
throw new ArgumentNullException("formatters");
}
_includeErrorDetail = includeErrorDetail;
_contentNegotiator = contentNegotiator;
_request = request;
_formatters = formatters;
}
public bool IncludeErrorDetail
{
get { return _includeErrorDetail; }
}
public IContentNegotiator ContentNegotiator
{
get { return _contentNegotiator; }
}
public HttpRequestMessage Request
{
get { return _request; }
}
public IEnumerable Formatters
{
get { return _formatters; }
}
}
internal sealed class ApiControllerDependencyProvider : IDependencyProvider
{
private readonly ApiController _controller;
private IDependencyProvider _resolvedDependencies;
public ApiControllerDependencyProvider(ApiController controller)
{
if (controller == null)
{
throw new ArgumentNullException("controller");
}
_controller = controller;
}
public bool IncludeErrorDetail
{
get
{
EnsureResolved();
return _resolvedDependencies.IncludeErrorDetail;
}
}
public IContentNegotiator ContentNegotiator
{
get
{
EnsureResolved();
return _resolvedDependencies.ContentNegotiator;
}
}
public HttpRequestMessage Request
{
get
{
EnsureResolved();
return _resolvedDependencies.Request;
}
}
public IEnumerable Formatters
{
get
{
EnsureResolved();
return _resolvedDependencies.Formatters;
}
}
private void EnsureResolved()
{
if (_resolvedDependencies == null)
{
HttpRequestContext requestContext = _controller.RequestContext;
Contract.Assert(requestContext != null);
bool includeErrorDetail = requestContext.IncludeErrorDetail;
HttpConfiguration configuration = _controller.Configuration;
if (configuration == null)
{
throw new InvalidOperationException(
SRResources.HttpControllerContext_ConfigurationMustNotBeNull);
}
ServicesContainer services = configuration.Services;
Contract.Assert(services != null);
IContentNegotiator contentNegotiator = services.GetContentNegotiator();
if (contentNegotiator == null)
{
throw new InvalidOperationException(Error.Format(
SRResources.HttpRequestMessageExtensions_NoContentNegotiator, typeof(IContentNegotiator)));
}
HttpRequestMessage request = _controller.Request;
if (request == null)
{
throw new InvalidOperationException(SRResources.ApiController_RequestMustNotBeNull);
}
IEnumerable formatters = configuration.Formatters;
Contract.Assert(formatters != null);
_resolvedDependencies = new DirectDependencyProvider(includeErrorDetail, contentNegotiator, request,
formatters);
}
}
}
}
}