// 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.Net; using System.Net.Http; using System.Security.Principal; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.WebApiCompatShim; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; namespace System.Web.Http { [UseWebApiRoutes] [UseWebApiActionConventions] [UseWebApiParameterConventions] [UseWebApiOverloading] [Controller] public abstract class ApiController : IDisposable { private ControllerContext _controllerContext; private HttpRequestMessage _request; private IModelMetadataProvider _metadataProvider; private IObjectModelValidator _objectValidator; private IUrlHelper _urlHelper; /// /// Gets the . /// public ActionContext ActionContext => ControllerContext; /// /// Gets or sets the . /// /// The setter is intended for unit testing purposes only. [ControllerContext] public ControllerContext ControllerContext { get { if (_controllerContext == null) { _controllerContext = new ControllerContext(); } return _controllerContext; } set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _controllerContext = value; } } /// /// Gets the http context. /// public HttpContext Context { get { return ControllerContext.HttpContext; } } /// /// Gets the . /// /// The setter is intended for unit testing purposes only. public IModelMetadataProvider MetadataProvider { get { if (_metadataProvider == null) { _metadataProvider = Context?.RequestServices.GetRequiredService(); } return _metadataProvider; } set { _metadataProvider = value; } } /// /// Gets or sets the . /// public IObjectModelValidator ObjectValidator { get { if (_objectValidator == null) { _objectValidator = Context?.RequestServices.GetRequiredService(); } return _objectValidator; } set { _objectValidator = value; } } /// /// Gets model state after the model binding process. This ModelState will be empty before model binding /// happens. /// public ModelStateDictionary ModelState { get { return ControllerContext.ModelState; } } /// /// Gets or sets the HTTP request message. /// /// The setter is intended for unit testing purposes only. public HttpRequestMessage Request { get { if (_request == null && ActionContext != null) { _request = ControllerContext.HttpContext.GetHttpRequestMessage(); } return _request; } set { _request = value; } } /// /// Gets a factory used to generate URLs to other APIs. /// /// The setter is intended for unit testing purposes only. public IUrlHelper Url { get { if (_urlHelper == null) { var factory = Context?.RequestServices.GetRequiredService(); _urlHelper = factory?.GetUrlHelper(ActionContext); } return _urlHelper; } set { _urlHelper = value; } } /// /// Gets or sets the current principal associated with this request. /// public IPrincipal User { get { return Context?.User; } } /// /// Creates a (400 Bad Request). /// /// A . [NonAction] public virtual BadRequestResult BadRequest() { return new BadRequestResult(); } /// /// Creates a (400 Bad Request) with the specified error message. /// /// The user-visible error message. /// A with the specified error message. [NonAction] public virtual BadRequestErrorMessageResult BadRequest(string message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } return new BadRequestErrorMessageResult(message); } /// /// Creates an (400 Bad Request) with the specified model state. /// /// The model state to include in the error. /// An with the specified model state. [NonAction] public virtual InvalidModelStateResult BadRequest(ModelStateDictionary modelState) { if (modelState == null) { throw new ArgumentNullException(nameof(modelState)); } return new InvalidModelStateResult(modelState, includeErrorDetail: false); } /// Creates a (409 Conflict). /// A . [NonAction] public virtual ConflictResult Conflict() { return new ConflictResult(); } /// /// 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. [NonAction] public virtual NegotiatedContentResult Content(HttpStatusCode statusCode, T value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } return new NegotiatedContentResult(statusCode, value); } /// /// Creates a (201 Created) with the specified values. /// /// /// The location at which the content has been created. Must be a relative or absolute URL. /// /// The content value to format in the entity body. /// A with the specified values. [NonAction] public virtual CreatedResult Created(string location, object content) { if (location == null) { throw new ArgumentNullException(nameof(location)); } return new CreatedResult(location, content); } /// /// Creates a (201 Created) with the specified values. /// /// The location at which the content has been created. /// The content value to format in the entity body. /// A with the specified values. [NonAction] public virtual CreatedResult Created(Uri uri, object content) { if (uri == null) { throw new ArgumentNullException(nameof(uri)); } string location; if (uri.IsAbsoluteUri) { location = uri.AbsoluteUri; } else { location = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } return Created(location, content); } /// /// Creates a (201 Created) with the specified values. /// /// The name of the route to use for generating the URL. /// The route data to use for generating the URL. /// The content value to format in the entity body. /// A with the specified values. [NonAction] public virtual CreatedAtRouteResult CreatedAtRoute( string routeName, object routeValues, object content) { if (routeName == null) { throw new ArgumentNullException(nameof(routeName)); } return new CreatedAtRouteResult(routeName, routeValues, content); } /// Creates an (500 Internal Server Error). /// /// A . [NonAction] public virtual InternalServerErrorResult InternalServerError() { return new InternalServerErrorResult(); } /// /// Creates an (500 Internal Server Error) with the specified exception. /// /// The exception to include in the error. /// An with the specified exception. [NonAction] public virtual ExceptionResult InternalServerError(Exception exception) { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } return new ExceptionResult(exception, includeErrorDetail: false); } /// /// Creates an (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. [NonAction] public virtual JsonResult Json(T content) { if (content == null) { throw new ArgumentNullException(nameof(content)); } return new JsonResult(content); } /// /// Creates an (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. [NonAction] public virtual JsonResult Json(T content, JsonSerializerSettings serializerSettings) { if (content == null) { throw new ArgumentNullException(nameof(content)); } if (serializerSettings == null) { throw new ArgumentNullException(nameof(serializerSettings)); } return new JsonResult(content, serializerSettings); } /// /// Creates an (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. [NonAction] public virtual JsonResult Json( T content, JsonSerializerSettings serializerSettings, Encoding encoding) { if (content == null) { throw new ArgumentNullException(nameof(content)); } if (serializerSettings == null) { throw new ArgumentNullException(nameof(serializerSettings)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var result = new JsonResult(content, serializerSettings); result.ContentType = $"application/json; charset={encoding.WebName}"; return result; } /// /// Creates an (404 Not Found). /// /// A . [NonAction] public virtual NotFoundResult NotFound() { return new NotFoundResult(); } /// /// Creates an (200 OK). /// /// An . [NonAction] public virtual OkResult Ok() { return new OkResult(); } /// /// 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. [NonAction] public virtual OkObjectResult Ok(T content) { return new OkObjectResult(content); } /// /// Creates a (302 Found) with the specified value. /// /// The location to which to redirect. /// A with the specified value. [NonAction] public virtual RedirectResult Redirect(string location) { if (location == null) { throw new ArgumentNullException(nameof(location)); } // This is how redirect was implemented in legacy webapi - string URIs are assumed to be absolute. return Redirect(new Uri(location)); } /// /// Creates a (302 Found) with the specified value. /// /// The location to which to redirect. /// A with the specified value. [NonAction] public virtual RedirectResult Redirect(Uri location) { if (location == null) { throw new ArgumentNullException(nameof(location)); } string uri; if (location.IsAbsoluteUri) { uri = location.AbsoluteUri; } else { uri = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } return new RedirectResult(uri); } /// /// 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. [NonAction] public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) { if (routeName == null) { throw new ArgumentNullException(nameof(routeName)); } if (routeValues == null) { throw new ArgumentNullException(nameof(routeValues)); } return new RedirectToRouteResult(routeName, routeValues) { UrlHelper = Url, }; } /// /// Creates a with the specified response. /// /// The HTTP response message. /// A for the specified response. [NonAction] public virtual ResponseMessageResult ResponseMessage(HttpResponseMessage response) { if (response == null) { throw new ArgumentNullException(nameof(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. [NonAction] public virtual StatusCodeResult StatusCode(HttpStatusCode status) { return new StatusCodeResult((int)status); } /// public void Dispose() => Dispose(disposing: true); /// /// Validates the given entity and adds the validation errors to the /// under an empty prefix. /// /// The type of the entity to be validated. /// The entity being validated. public void Validate(TEntity entity) { Validate(entity, keyPrefix: string.Empty); } /// /// Validates the given entity and adds the validation errors to the . /// /// 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 entity, string keyPrefix) { ObjectValidator.Validate( ControllerContext, validationState: null, prefix: keyPrefix, model: entity); } protected virtual void Dispose(bool disposing) { } } }