// 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.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Filters;
using System.Web.Http.Metadata;
using System.Web.Http.ModelBinding;
using System.Web.Http.Properties;
using System.Web.Http.Results;
using System.Web.Http.Routing;
using System.Web.Http.Validation;
using Newtonsoft.Json;
namespace System.Web.Http
{
public abstract class ApiController : IHttpController, IDisposable
{
private HttpActionContext _actionContext = new HttpActionContext();
private bool _initialized;
/// Gets the configuration.
/// The setter is intended for unit testing purposes only.
public HttpConfiguration Configuration
{
get { return ControllerContext.Configuration; }
set { ControllerContext.Configuration = value; }
}
/// Gets the controller context.
/// The setter is intended for unit testing purposes only.
public HttpControllerContext ControllerContext
{
get
{
// unit test only
if (ActionContext.ControllerContext == null)
{
ActionContext.ControllerContext = new HttpControllerContext
{
RequestContext = new RequestBackedHttpRequestContext()
};
}
return ActionContext.ControllerContext;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
ActionContext.ControllerContext = value;
}
}
/// Gets the action context.
/// The setter is intended for unit testing purposes only.
public HttpActionContext ActionContext
{
get { return _actionContext; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionContext = value;
}
}
///
/// Gets model state after the model binding process. This ModelState will be empty before model binding happens.
///
/// The setter is intended for unit testing purposes only.
public ModelStateDictionary ModelState
{
get
{
return ActionContext.ModelState;
}
}
/// Gets or sets the HTTP request message.
/// The setter is intended for unit testing purposes only.
public HttpRequestMessage Request
{
get
{
return ControllerContext.Request;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
HttpRequestContext contextOnRequest = value.GetRequestContext();
HttpRequestContext contextOnController = RequestContext;
if (contextOnRequest != null && contextOnRequest != contextOnController)
{
// Prevent unit testers from setting conflicting requests contexts.
throw new InvalidOperationException(SRResources.RequestContextConflict);
}
ControllerContext.Request = value;
value.SetRequestContext(contextOnController);
RequestBackedHttpRequestContext requestBackedContext =
contextOnController as RequestBackedHttpRequestContext;
if (requestBackedContext != null)
{
requestBackedContext.Request = value;
}
}
}
/// Gets the request context.
/// The setter is intended for unit testing purposes only.
public HttpRequestContext RequestContext
{
get
{
return ControllerContext.RequestContext;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
HttpRequestContext oldContext = ControllerContext.RequestContext;
HttpRequestMessage request = Request;
if (request != null)
{
HttpRequestContext contextOnRequest = request.GetRequestContext();
if (contextOnRequest != null && contextOnRequest != oldContext && contextOnRequest != value)
{
// Prevent unit testers from setting conflicting requests contexts.
throw new InvalidOperationException(SRResources.RequestContextConflict);
}
request.SetRequestContext(value);
}
ControllerContext.RequestContext = value;
}
}
/// Gets a factory used to generate URLs to other APIs.
/// The setter is intended for unit testing purposes only.
public UrlHelper Url
{
get { return RequestContext.Url; }
set { RequestContext.Url = value; }
}
/// Gets or sets the current principal associated with this request.
/// The setter is intended for unit testing purposes only.
public IPrincipal User
{
get { return RequestContext.Principal; }
set { RequestContext.Principal = value; }
}
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This method is a coordinator, so this coupling is expected.")]
public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
if (_initialized)
{
// if user has registered a controller factory which produces the same controller instance, we should throw here
throw Error.InvalidOperation(SRResources.CannotSupportSingletonInstance, typeof(ApiController).Name, typeof(IHttpControllerActivator).Name);
}
Initialize(controllerContext);
// We can't be reused, and we know we're disposable, so make sure we go away when
// the request has been completed.
if (Request != null)
{
Request.RegisterForDispose(this);
}
HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
ActionContext.ActionDescriptor = actionDescriptor;
if (Request != null)
{
Request.SetActionDescriptor(actionDescriptor);
}
FilterGrouping filterGrouping = actionDescriptor.GetFilterGrouping();
IActionFilter[] actionFilters = filterGrouping.ActionFilters;
IAuthenticationFilter[] authenticationFilters = filterGrouping.AuthenticationFilters;
IAuthorizationFilter[] authorizationFilters = filterGrouping.AuthorizationFilters;
IExceptionFilter[] exceptionFilters = filterGrouping.ExceptionFilters;
IHttpActionResult result = new ActionFilterResult(actionDescriptor.ActionBinding, ActionContext,
controllerServices, actionFilters);
if (authorizationFilters.Length > 0)
{
result = new AuthorizationFilterResult(ActionContext, authorizationFilters, result);
}
if (authenticationFilters.Length > 0)
{
result = new AuthenticationFilterResult(ActionContext, this, authenticationFilters, result);
}
if (exceptionFilters.Length > 0)
{
IExceptionLogger exceptionLogger = ExceptionServices.GetLogger(controllerServices);
IExceptionHandler exceptionHandler = ExceptionServices.GetHandler(controllerServices);
result = new ExceptionFilterResult(ActionContext, exceptionFilters, exceptionLogger, exceptionHandler,
result);
}
return result.ExecuteAsync(cancellationToken);
}
///
/// Validates the given entity and adds the validation errors to the
/// under the empty prefix, if any.
///
/// The type of the entity to be validated.
/// The entity being validated.
public void Validate<TEntity>(TEntity entity)
{
Validate(entity, keyPrefix: String.Empty);
}
///
/// Validates the given entity and adds the validation errors to the , if any.
///
/// The type of the entity to be validated.
/// The entity being validated.
///
/// The key prefix under which the model state errors would be added in the .
///
public void Validate<TEntity>(TEntity entity, string keyPrefix)
{
if (Configuration == null)
{
throw Error.InvalidOperation(SRResources.TypePropertyMustNotBeNull, typeof(ApiController).Name, "Configuration");
}
IBodyModelValidator validator = Configuration.Services.GetBodyModelValidator();
if (validator != null)
{
ModelMetadataProvider metadataProvider = Configuration.Services.GetModelMetadataProvider();
Contract.Assert(metadataProvider != null, "GetModelMetadataProvider throws on null.");
validator.Validate(entity, typeof(TEntity), metadataProvider, ActionContext, keyPrefix);
}
}
/// Creates a (400 Bad Request).
/// A .
protected internal virtual BadRequestResult BadRequest()
{
return new BadRequestResult(this);
}
///
/// Creates a (400 Bad Request) with the specified error message.
///
/// The user-visible error message.
/// A with the specified error message.
protected internal virtual BadRequestErrorMessageResult BadRequest(string message)
{
return new BadRequestErrorMessageResult(message, this);
}
///
/// Creates an (400 Bad Request) with the specified model state.
///
/// The model state to include in the error.
/// An with the specified model state.
protected internal virtual InvalidModelStateResult BadRequest(ModelStateDictionary modelState)
{
return new InvalidModelStateResult(modelState, this);
}
/// Creates a (409 Conflict).
/// A .
protected internal virtual ConflictResult Conflict()
{
return new ConflictResult(this);
}
/// Creates a with the specified values.
/// The type of content in the entity body.
/// The HTTP status code for the response message.
/// The content value to negotiate and format in the entity body.
/// A with the specified values.
protected internal virtual NegotiatedContentResult<T> Content<T>(HttpStatusCode statusCode, T value)
{
return new NegotiatedContentResult<T>(statusCode, value, this);
}
/// Creates a with the specified values.
/// The type of content in the entity body.
/// The HTTP status code for the response message.
/// The content value to format in the entity body.
/// The formatter to use to format the content.
/// A with the specified values.
protected internal FormattedContentResult<T> Content<T>(HttpStatusCode statusCode, T value,
MediaTypeFormatter formatter)
{
return Content(statusCode, value, formatter, (MediaTypeHeaderValue)null);
}
/// Creates a with the specified values.
/// The type of content in the entity body.
/// The HTTP status code for the response message.
/// The content value to format in the entity body.
/// The formatter to use to format the content.
/// The value for the Content-Type header.
/// A with the specified values.
protected internal FormattedContentResult<T> Content<T>(HttpStatusCode statusCode, T value,
MediaTypeFormatter formatter, string mediaType)
{
return Content(statusCode, value, formatter, new MediaTypeHeaderValue(mediaType));
}
/// Creates a with the specified values.
/// The type of content in the entity body.
/// The HTTP status code for the response message.
/// The content value to format in the entity body.
/// The formatter to use to format the content.
///
/// The value for the Content-Type header, or to have the formatter pick a default
/// value.
///
/// A with the specified values.
protected internal virtual FormattedContentResult<T> Content<T>(HttpStatusCode statusCode, T value,
MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType)
{
return new FormattedContentResult<T>(statusCode, value, formatter, mediaType, this);
}
///
/// Creates a (201 Created) with the specified values.
///
/// The type of content in the entity body.
///
/// The location at which the content has been created. Must be a relative or absolute URL.
///
/// The content value to negotiate and format in the entity body.
/// A with the specified values.
protected internal CreatedNegotiatedContentResult<T> Created<T>(string location, T content)
{
if (location == null)
{
throw new ArgumentNullException("location");
}
return Created<T>(new Uri(location, UriKind.RelativeOrAbsolute), content);
}
///
/// Creates a (201 Created) with the specified values.
///
/// The type of content in the entity body.
/// The location at which the content has been created.
/// The content value to negotiate and format in the entity body.
/// A with the specified values.
protected internal virtual CreatedNegotiatedContentResult<T> Created<T>(Uri location, T content)
{
return new CreatedNegotiatedContentResult<T>(location, content, this);
}
///
/// Creates a (201 Created) with the specified values.
///
/// The type of content in the entity body.
/// The name of the route to use for generating the URL.
/// The route data to use for generating the URL.
/// The content value to negotiate and format in the entity body.
/// A with the specified values.
protected internal CreatedAtRouteNegotiatedContentResult<T> CreatedAtRoute<T>(string routeName,
object routeValues, T content)
{
return CreatedAtRoute<T>(routeName, new HttpRouteValueDictionary(routeValues), content);
}
///
/// Creates a (201 Created) with the specified values.
///
/// The type of content in the entity body.
/// The name of the route to use for generating the URL.
/// The route data to use for generating the URL.
/// The content value to negotiate and format in the entity body.
/// A with the specified values.
protected internal virtual CreatedAtRouteNegotiatedContentResult<T> CreatedAtRoute<T>(string routeName,
IDictionary<string, object> routeValues, T content)
{
return new CreatedAtRouteNegotiatedContentResult<T>(routeName, routeValues, content, this);
}
/// Creates an (500 Internal Server Error).
/// A .
protected internal virtual InternalServerErrorResult InternalServerError()
{
return new InternalServerErrorResult(this);
}
///
/// Creates an (500 Internal Server Error) with the specified exception.
///
/// The exception to include in the error.
/// An with the specified exception.
protected internal virtual ExceptionResult InternalServerError(Exception exception)
{
return new ExceptionResult(exception, this);
}
/// Creates a (200 OK) with the specified value.
/// The type of content in the entity body.
/// The content value to serialize in the entity body.
/// A with the specified value.
protected internal JsonResult<T> Json<T>(T content)
{
return Json<T>(content, new JsonSerializerSettings());
}
/// Creates a (200 OK) with the specified values.
/// The type of content in the entity body.
/// The content value to serialize in the entity body.
/// The serializer settings.
/// A with the specified values.
protected internal JsonResult<T> Json<T>(T content, JsonSerializerSettings serializerSettings)
{
return Json<T>(content, serializerSettings, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true));
}
/// Creates a (200 OK) with the specified values.
/// The type of content in the entity body.
/// The content value to serialize in the entity body.
/// The serializer settings.
/// The content encoding.
/// A with the specified values.
protected internal virtual JsonResult<T> Json<T>(T content, JsonSerializerSettings serializerSettings,
Encoding encoding)
{
return new JsonResult<T>(content, serializerSettings, encoding, this);
}
/// Creates a (404 Not Found).
/// A .
protected internal virtual NotFoundResult NotFound()
{
return new NotFoundResult(this);
}
/// Creates an (200 OK).
/// An .
protected internal virtual OkResult Ok()
{
return new OkResult(this);
}
///
/// Creates an (200 OK) with the specified values.
///
/// The type of content in the entity body.
/// The content value to negotiate and format in the entity body.
/// An with the specified values.
protected internal virtual OkNegotiatedContentResult<T> Ok<T>(T content)
{
return new OkNegotiatedContentResult<T>(content, this);
}
/// Creates a (302 Found) with the specified value.
/// The location to which to redirect.
/// A with the specified value.
protected internal virtual RedirectResult Redirect(string location)
{
if (location == null)
{
throw new ArgumentNullException("location");
}
return Redirect(new Uri(location));
}
/// Creates a (302 Found) with the specified value.
/// The location to which to redirect.
/// A with the specified value.
protected internal virtual RedirectResult Redirect(Uri location)
{
return new RedirectResult(location, this);
}
/// Creates a (302 Found) with the specified values.
/// The name of the route to use for generating the URL.
/// The route data to use for generating the URL.
/// A with the specified values.
protected internal RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
{
return RedirectToRoute(routeName, new HttpRouteValueDictionary(routeValues));
}
/// Creates a (302 Found) with the specified values.
/// The name of the route to use for generating the URL.
/// The route data to use for generating the URL.
/// A with the specified values.
protected internal virtual RedirectToRouteResult RedirectToRoute(string routeName,
IDictionary<string, object> routeValues)
{
return new RedirectToRouteResult(routeName, routeValues, this);
}
/// Creates a with the specified response.
/// The HTTP response message.
/// A for the specified response.
protected internal virtual ResponseMessageResult ResponseMessage(HttpResponseMessage response)
{
return new ResponseMessageResult(response);
}
/// Creates a with the specified status code.
/// The HTTP status code for the response message
/// A with the specified status code.
protected internal virtual StatusCodeResult StatusCode(HttpStatusCode status)
{
return new StatusCodeResult(status, this);
}
///
/// Creates an (401 Unauthorized) with the specified values.
///
/// The WWW-Authenticate challenges.
/// An with the specified values.
protected internal UnauthorizedResult Unauthorized(params AuthenticationHeaderValue[] challenges)
{
return Unauthorized((IEnumerable<AuthenticationHeaderValue>)challenges);
}
///
/// Creates an (401 Unauthorized) with the specified values.
///
/// The WWW-Authenticate challenges.
/// An with the specified values.
protected internal virtual UnauthorizedResult Unauthorized(IEnumerable<AuthenticationHeaderValue> challenges)
{
return new UnauthorizedResult(challenges, this);
}
protected virtual void Initialize(HttpControllerContext controllerContext)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
}
_initialized = true;
ControllerContext = controllerContext;
}
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
#endregion IDisposable
}
}