// 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.Properties; namespace System.Web.Http.Results { /// Represents an action result that performs content negotiation. /// The type of content in the entity body. public class NegotiatedContentResult : IHttpActionResult { private readonly HttpStatusCode _statusCode; private readonly T _content; private readonly IDependencyProvider _dependencies; /// /// Initializes a new instance of the class with the values provided. /// /// The HTTP status code for the response message. /// The content value to negotiate and format in the entity body. /// 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 NegotiatedContentResult(HttpStatusCode statusCode, T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable formatters) : this(statusCode, content, new DirectDependencyProvider(contentNegotiator, request, formatters)) { } /// /// Initializes a new instance of the class with the values provided. /// /// The HTTP status code for the response message. /// The content value to negotiate and format in the entity body. /// The controller from which to obtain the dependencies needed for execution. public NegotiatedContentResult(HttpStatusCode statusCode, T content, ApiController controller) : this(statusCode, content, new ApiControllerDependencyProvider(controller)) { } private NegotiatedContentResult(HttpStatusCode statusCode, T content, IDependencyProvider dependencies) { Contract.Assert(dependencies != null); _statusCode = statusCode; _content = content; _dependencies = dependencies; } /// Gets the HTTP status code for the response message. public HttpStatusCode StatusCode { get { return _statusCode; } } /// Gets the content value to negotiate and format in the entity body. public T Content { get { return _content; } } /// 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() { return Execute(_statusCode, _content, _dependencies.ContentNegotiator, _dependencies.Request, _dependencies.Formatters); } internal static HttpResponseMessage Execute(HttpStatusCode statusCode, T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable formatters) { Contract.Assert(contentNegotiator != null); Contract.Assert(request != null); Contract.Assert(formatters != null); // Run content negotiation. ContentNegotiationResult result = contentNegotiator.Negotiate(typeof(T), request, formatters); HttpResponseMessage response = new HttpResponseMessage(); try { if (result == null) { // A null result from content negotiation indicates that the response should be a 406. response.StatusCode = HttpStatusCode.NotAcceptable; } else { response.StatusCode = statusCode; Contract.Assert(result.Formatter != null); // At this point mediaType should be a cloned value. (The content negotiator is responsible for // returning a new copy.) response.Content = new ObjectContent(content, result.Formatter, result.MediaType); } response.RequestMessage = request; } catch { response.Dispose(); throw; } return response; } /// 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 { IContentNegotiator ContentNegotiator { get; } HttpRequestMessage Request { get; } IEnumerable Formatters { get; } } internal sealed class DirectDependencyProvider : IDependencyProvider { private readonly IContentNegotiator _contentNegotiator; private readonly HttpRequestMessage _request; private readonly IEnumerable _formatters; public DirectDependencyProvider(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"); } _contentNegotiator = contentNegotiator; _request = request; _formatters = formatters; } 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 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) { 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(contentNegotiator, request, formatters); } } } } }