Adding Api Explorer
This commit is contained in:
parent
6600e68fc0
commit
3cd6d3e060
15
Mvc.sln
15
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22115.0
|
VisualStudioVersion = 14.0.22013.1
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -80,6 +80,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "XmlSerializerWebSite", "tes
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\WebSites\UrlHelperWebSite\UrlHelperWebSite.kproj", "{A192E504-2881-41DC-90D1-B7F1DD1134E8}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\WebSites\UrlHelperWebSite\UrlHelperWebSite.kproj", "{A192E504-2881-41DC-90D1-B7F1DD1134E8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.kproj", "{61061528-071E-424E-965A-07BCC2F02672}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -410,6 +412,16 @@ Global
|
||||||
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|x86.ActiveCfg = Release|Any CPU
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -448,5 +460,6 @@ Global
|
||||||
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
|
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
|
|
||||||
|
namespace MvcSample.Web
|
||||||
|
{
|
||||||
|
[Route("ApiExplorer")]
|
||||||
|
public class ApiExplorerController : Controller
|
||||||
|
{
|
||||||
|
[Activate]
|
||||||
|
public IApiDescriptionGroupCollectionProvider Provider { get; set; }
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult All()
|
||||||
|
{
|
||||||
|
var descriptions = Provider.ApiDescriptionGroups.Items;
|
||||||
|
return View(descriptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
.api-description {
|
||||||
|
margin: 10px 0px 10px 0px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description hr {
|
||||||
|
border: 0px;
|
||||||
|
border-top: 1px solid #D44B4B;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "Admin API")]
|
||||||
|
[Route("api/Admin/Products")]
|
||||||
|
public class ProductsAdminController : Controller
|
||||||
|
{
|
||||||
|
[HttpPut]
|
||||||
|
public void AddProduct([FromBody] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}")]
|
||||||
|
public void UpdateProduct([FromBody] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/Stock")]
|
||||||
|
public void SetQuantityInStock(int id, int quantity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/Price")]
|
||||||
|
public void SetPrice(int id, decimal price)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Produces("application/json", "application/xml")]
|
||||||
|
[HttpGet("{id}/Orders")]
|
||||||
|
public IEnumerable<ProductOrderConfirmation> GetOrders(DateTime? fromData = null, DateTime? toDate = null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "Public API")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[Route("api/Products")]
|
||||||
|
public class ProductsController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public Product GetById(int id)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Search/{name}")]
|
||||||
|
public IEnumerable<Product> SearchByName(string name)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Produces("application/json", Type = typeof(ProductOrderConfirmation))]
|
||||||
|
[HttpPut("{id}/Buy")]
|
||||||
|
public IActionResult Buy(int projectId, int quantity = 1)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
public class ProductOrderConfirmation
|
||||||
|
{
|
||||||
|
public Product Product { get; set; }
|
||||||
|
|
||||||
|
public decimal PricePerUnit { get; set; }
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
public decimal TotalPrice { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
@using Microsoft.AspNet.Mvc.Description
|
||||||
|
@model IReadOnlyList<ApiDescriptionGroup>
|
||||||
|
|
||||||
|
@section header
|
||||||
|
{
|
||||||
|
<link rel="stylesheet" href="~/Content/api-description.css" />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style="padding: 50px 0px 0px 0px">
|
||||||
|
|
||||||
|
@foreach (var group in Model)
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<h1>Group: @group.GroupName</h1>
|
||||||
|
|
||||||
|
@foreach (var item in group.Items)
|
||||||
|
{
|
||||||
|
await Html.RenderPartialAsync("_ApiDescription", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
@using Microsoft.AspNet.Mvc.Description
|
||||||
|
@model ApiDescription
|
||||||
|
|
||||||
|
<div class="api-description">
|
||||||
|
<h4>@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")</h4>
|
||||||
|
<hr />
|
||||||
|
<h5>For action: @Model.ActionDescriptor.DisplayName</h5>
|
||||||
|
<p>Return Type: @(Model.ResponseType?.FullName ?? "Unknown Type")</p>
|
||||||
|
|
||||||
|
@if (Model.ParameterDescriptions.Count > 0)
|
||||||
|
{
|
||||||
|
<p>Parameters:</p>
|
||||||
|
<ul>
|
||||||
|
@foreach (var parameter in Model.ParameterDescriptions)
|
||||||
|
{
|
||||||
|
<li>@parameter.Name - @parameter.Type.FullName - @parameter.Source.ToString()</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.SupportedResponseFormats.Count > 0)
|
||||||
|
{
|
||||||
|
<p>Response Formats:</p>
|
||||||
|
<ul>
|
||||||
|
@foreach(var response in Model.SupportedResponseFormats)
|
||||||
|
{
|
||||||
|
<li>@response.MediaType.RawValue - @response.Formatter.GetType().Name</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public ActionDescriptor()
|
public ActionDescriptor()
|
||||||
{
|
{
|
||||||
|
Properties = new Dictionary<object, object>();
|
||||||
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,5 +35,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// A friendly name for this action.
|
/// A friendly name for this action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual string DisplayName { get; set; }
|
public virtual string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores arbitrary metadata properties associated with the <see cref="ActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<object, object> Properties { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="ActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ActionDescriptorExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a property from the <see cref="ActionDescriptor.Properties"/> collection
|
||||||
|
/// using the provided value of <typeparamref name="T"/> as the key.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property.</typeparam>
|
||||||
|
/// <param name="actionDescriptor">The action descriptor.</param>
|
||||||
|
/// <returns>The property or the default value of <typeparamref name="T"/>.</returns>
|
||||||
|
public static T GetProperty<T>([NotNull] this ActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
object value;
|
||||||
|
if (actionDescriptor.Properties.TryGetValue(typeof(T), out value))
|
||||||
|
{
|
||||||
|
return (T)value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of an property in the <see cref="ActionDescriptor.Properties"/> collection using
|
||||||
|
/// the provided value of <typeparamref name="T"/> as the key.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property.</typeparam>
|
||||||
|
/// <param name="actionDescriptor">The action descriptor.</param>
|
||||||
|
/// <param name="value">The value of the property.</param>
|
||||||
|
public static void SetProperty<T>([NotNull] this ActionDescriptor actionDescriptor, [NotNull] T value)
|
||||||
|
{
|
||||||
|
actionDescriptor.Properties[typeof(T)] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an API exposed by this application.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiDescription
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApiDescription()
|
||||||
|
{
|
||||||
|
Properties = new Dictionary<object, object>();
|
||||||
|
ParameterDescriptions = new List<ApiParameterDescription>();
|
||||||
|
SupportedResponseFormats = new List<ApiResponseFormat>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ActionDescriptor"/> for this api.
|
||||||
|
/// </summary>
|
||||||
|
public ActionDescriptor ActionDescriptor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The group name for this api.
|
||||||
|
/// </summary>
|
||||||
|
public string GroupName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The supported HTTP method for this api, or null if all HTTP methods are supported.
|
||||||
|
/// </summary>
|
||||||
|
public string HttpMethod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of <see cref="ApiParameterDescription"/> for this api.
|
||||||
|
/// </summary>
|
||||||
|
public List<ApiParameterDescription> ParameterDescriptions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores arbitrary metadata properties associated with the <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<object, object> Properties { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative url path template (relative to application root) for this api.
|
||||||
|
/// </summary>
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ModelMetadata"/> for the <see cref="ResponseType"/> or null.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Will be null if <see cref="ResponseType"/> is null.
|
||||||
|
/// </remarks>
|
||||||
|
public ModelMetadata ResponseModelMetadata { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CLR data type of the response or null.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Will be null if the action returns no response, or if the response type is unclear. Use
|
||||||
|
/// <see cref="ProducesAttribute"/> on an action method to specify a response type.
|
||||||
|
/// </remarks>
|
||||||
|
public Type ResponseType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of possible formats for a response.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Will be empty if the action returns no response, or if the response type is unclear. Use
|
||||||
|
/// <see cref="ProducesAttribute"/> on an action method to specify a response type.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<ApiResponseFormat> SupportedResponseFormats { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents data used to build an <see cref="ApiDescription"/>, stored as part of the
|
||||||
|
/// <see cref="ActionDescriptor.Properties"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiDescriptionActionData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ApiDescription.GroupName"/> of <see cref="ApiDescription"/> objects for the associated
|
||||||
|
/// action.
|
||||||
|
/// </summary>
|
||||||
|
public string GroupName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider
|
||||||
|
{
|
||||||
|
private readonly IActionDescriptorsCollectionProvider _actionDescriptorCollectionProvider;
|
||||||
|
private readonly INestedProviderManager<ApiDescriptionProviderContext> _apiDescriptionProvider;
|
||||||
|
|
||||||
|
private ApiDescriptionGroupCollection _apiDescriptionGroups;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ApiDescriptionGroupCollectionProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionDescriptorCollectionProvider">
|
||||||
|
/// The <see cref="IActionDescriptorsCollectionProvider"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="apiDescriptionProvider">
|
||||||
|
/// The <see cref="INestedProviderManager{ApiDescriptionProviderContext}"/>.
|
||||||
|
/// </param>
|
||||||
|
public ApiDescriptionGroupCollectionProvider(
|
||||||
|
IActionDescriptorsCollectionProvider actionDescriptorCollectionProvider,
|
||||||
|
INestedProviderManager<ApiDescriptionProviderContext> apiDescriptionProvider)
|
||||||
|
{
|
||||||
|
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
||||||
|
_apiDescriptionProvider = apiDescriptionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ApiDescriptionGroupCollection ApiDescriptionGroups
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var actionDescriptors = _actionDescriptorCollectionProvider.ActionDescriptors;
|
||||||
|
if (_apiDescriptionGroups == null || _apiDescriptionGroups.Version != actionDescriptors.Version)
|
||||||
|
{
|
||||||
|
_apiDescriptionGroups = GetCollection(actionDescriptors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _apiDescriptionGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiDescriptionGroupCollection GetCollection(ActionDescriptorsCollection actionDescriptors)
|
||||||
|
{
|
||||||
|
var context = new ApiDescriptionProviderContext(actionDescriptors.Items);
|
||||||
|
_apiDescriptionProvider.Invoke(context);
|
||||||
|
|
||||||
|
var groups = context.Results
|
||||||
|
.GroupBy(d => d.GroupName)
|
||||||
|
.Select(g => new ApiDescriptionGroup(g.Key, g.ToArray()))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ApiDescriptionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a property from the <see cref="ApiDescription.Properties"/> collection
|
||||||
|
/// using the provided value of <typeparamref name="T"/> as the key.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property.</typeparam>
|
||||||
|
/// <param name="apiDescription">The <see cref="ApiDescription"/>.</param>
|
||||||
|
/// <returns>The property or the default value of <typeparamref name="T"/>.</returns>
|
||||||
|
public static T GetProperty<T>([NotNull] this ApiDescription apiDescription)
|
||||||
|
{
|
||||||
|
object value;
|
||||||
|
if (apiDescription.Properties.TryGetValue(typeof(T), out value))
|
||||||
|
{
|
||||||
|
return (T)value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of an property in the <see cref="ApiDescription.Properties"/> collection using
|
||||||
|
/// the provided value of <typeparamref name="T"/> as the key.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the property.</typeparam>
|
||||||
|
/// <param name="apiDescription">The <see cref="ApiDescription"/>.</param>
|
||||||
|
/// <param name="value">The value of the property.</param>
|
||||||
|
public static void SetProperty<T>([NotNull] this ApiDescription apiDescription, [NotNull] T value)
|
||||||
|
{
|
||||||
|
apiDescription.Properties[typeof(T)] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a group of related apis.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiDescriptionGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ApiDescriptionGroup"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupName">The group name.</param>
|
||||||
|
/// <param name="items">A collection of <see cref="ApiDescription"/> items for this group.</param>
|
||||||
|
public ApiDescriptionGroup(string groupName, IReadOnlyList<ApiDescription> items)
|
||||||
|
{
|
||||||
|
GroupName = groupName;
|
||||||
|
Items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The group name.
|
||||||
|
/// </summary>
|
||||||
|
public string GroupName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of <see cref="ApiDescription"/> items for this group.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ApiDescription> Items { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A cached collection of <see cref="ApiDescriptionGroup" />.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiDescriptionGroupCollection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ApiDescriptionGroupCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The list of <see cref="ApiDescriptionGroup"/>.</param>
|
||||||
|
/// <param name="version">The unique version of discovered groups.</param>
|
||||||
|
public ApiDescriptionGroupCollection([NotNull] IReadOnlyList<ApiDescriptionGroup> items, int version)
|
||||||
|
{
|
||||||
|
Items = items;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the list of <see cref="IReadOnlyList{ApiDescriptionGroup}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ApiDescriptionGroup> Items { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the unique version of the current items.
|
||||||
|
/// </summary>
|
||||||
|
public int Version { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A context object for <see cref="ApiDescription"/> providers.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiDescriptionProviderContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ApiDescriptionProviderContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actions">The list of actions.</param>
|
||||||
|
public ApiDescriptionProviderContext([NotNull] IReadOnlyList<ActionDescriptor> actions)
|
||||||
|
{
|
||||||
|
Actions = actions;
|
||||||
|
|
||||||
|
Results = new List<ApiDescription>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of actions.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ActionDescriptor> Actions { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of resulting <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public List<ApiDescription> Results { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the visibility and group name for an <see cref="ApiDescription"/> of the associated
|
||||||
|
/// controller class or action method.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class ApiExplorerSettingsAttribute :
|
||||||
|
Attribute,
|
||||||
|
IApiDescriptionGroupNameProvider,
|
||||||
|
IApiDescriptionVisibilityProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GroupName { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IgnoreApi { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
public class ApiParameterDescription
|
||||||
|
{
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
|
|
||||||
|
public ModelMetadata ModelMetadata { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ParameterDescriptor ParameterDescriptor { get; set; }
|
||||||
|
|
||||||
|
public ApiParameterSource Source { get; set; }
|
||||||
|
|
||||||
|
public Type Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
// This is a placeholder - see #886
|
||||||
|
public enum ApiParameterSource
|
||||||
|
{
|
||||||
|
Body,
|
||||||
|
Query,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a possible format for the body of a response.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiResponseFormat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The formatter used to output this response.
|
||||||
|
/// </summary>
|
||||||
|
public IOutputFormatter Formatter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The media type of the response.
|
||||||
|
/// </summary>
|
||||||
|
public MediaTypeHeaderValue MediaType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements a provider of <see cref="ApiDescription"/> for actions represented
|
||||||
|
/// by <see cref="ReflectedActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultApiDescriptionProvider : INestedProvider<ApiDescriptionProviderContext>
|
||||||
|
{
|
||||||
|
private readonly IOutputFormattersProvider _formattersProvider;
|
||||||
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="DefaultApiDescriptionProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="formattersProvider">The <see cref="IOutputFormattersProvider"/>.</param>
|
||||||
|
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||||
|
public DefaultApiDescriptionProvider(
|
||||||
|
IOutputFormattersProvider formattersProvider,
|
||||||
|
IModelMetadataProvider modelMetadataProvider)
|
||||||
|
{
|
||||||
|
_formattersProvider = formattersProvider;
|
||||||
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Order { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Invoke(ApiDescriptionProviderContext context, Action callNext)
|
||||||
|
{
|
||||||
|
foreach (var action in context.Actions.OfType<ReflectedActionDescriptor>())
|
||||||
|
{
|
||||||
|
var extensionData = action.GetProperty<ApiDescriptionActionData>();
|
||||||
|
if (extensionData != null)
|
||||||
|
{
|
||||||
|
var httpMethods = GetHttpMethods(action);
|
||||||
|
foreach (var httpMethod in httpMethods)
|
||||||
|
{
|
||||||
|
context.Results.Add(CreateApiDescription(action, httpMethod, extensionData.GroupName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiDescription CreateApiDescription(
|
||||||
|
ReflectedActionDescriptor action,
|
||||||
|
string httpMethod,
|
||||||
|
string groupName)
|
||||||
|
{
|
||||||
|
var apiDescription = new ApiDescription()
|
||||||
|
{
|
||||||
|
ActionDescriptor = action,
|
||||||
|
GroupName = groupName,
|
||||||
|
HttpMethod = httpMethod,
|
||||||
|
RelativePath = GetRelativePath(action),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (action.Parameters != null)
|
||||||
|
{
|
||||||
|
foreach (var parameter in action.Parameters)
|
||||||
|
{
|
||||||
|
apiDescription.ParameterDescriptions.Add(GetParameter(parameter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
||||||
|
|
||||||
|
// We only provide response info if we can figure out a type that is a user-data type.
|
||||||
|
// Void /Task object/IActionResult will result in no data.
|
||||||
|
var declaredReturnType = GetDeclaredReturnType(action);
|
||||||
|
|
||||||
|
// Now 'simulate' an action execution. This attempts to figure out to the best of our knowledge
|
||||||
|
// what the logical data type is using filters.
|
||||||
|
var runtimeReturnType = GetRuntimeReturnType(declaredReturnType, responseMetadataAttributes);
|
||||||
|
|
||||||
|
// We might not be able to figure out a good runtime return type. If that's the case we don't
|
||||||
|
// provide any information about outputs. The workaround is to attribute the action.
|
||||||
|
if (runtimeReturnType == typeof(void))
|
||||||
|
{
|
||||||
|
// As a special case, if the return type is void - we want to surface that information
|
||||||
|
// specifically, but nothing else. This can be overridden with a filter/attribute.
|
||||||
|
apiDescription.ResponseType = runtimeReturnType;
|
||||||
|
}
|
||||||
|
else if (runtimeReturnType != null)
|
||||||
|
{
|
||||||
|
apiDescription.ResponseType = runtimeReturnType;
|
||||||
|
|
||||||
|
apiDescription.ResponseModelMetadata = _modelMetadataProvider.GetMetadataForType(
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: runtimeReturnType);
|
||||||
|
|
||||||
|
var formats = GetResponseFormats(
|
||||||
|
action,
|
||||||
|
responseMetadataAttributes,
|
||||||
|
declaredReturnType,
|
||||||
|
runtimeReturnType);
|
||||||
|
|
||||||
|
foreach (var format in formats)
|
||||||
|
{
|
||||||
|
apiDescription.SupportedResponseFormats.Add(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetHttpMethods(ReflectedActionDescriptor action)
|
||||||
|
{
|
||||||
|
if (action.MethodConstraints != null && action.MethodConstraints.Count > 0)
|
||||||
|
{
|
||||||
|
return action.MethodConstraints.SelectMany(c => c.HttpMethods);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new string[] { null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRelativePath(ReflectedActionDescriptor action)
|
||||||
|
{
|
||||||
|
// This is a placeholder for functionality which will correctly generate the relative path
|
||||||
|
// stub of an action. See: #885
|
||||||
|
if (action.AttributeRouteInfo != null &&
|
||||||
|
action.AttributeRouteInfo.Template != null)
|
||||||
|
{
|
||||||
|
return action.AttributeRouteInfo.Template;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiParameterDescription GetParameter(ParameterDescriptor parameter)
|
||||||
|
{
|
||||||
|
// This is a placeholder based on currently available functionality for parameters. See #886.
|
||||||
|
var resourceParameter = new ApiParameterDescription()
|
||||||
|
{
|
||||||
|
IsOptional = parameter.IsOptional,
|
||||||
|
Name = parameter.Name,
|
||||||
|
ParameterDescriptor = parameter,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parameter.ParameterBindingInfo != null)
|
||||||
|
{
|
||||||
|
resourceParameter.Type = parameter.ParameterBindingInfo.ParameterType;
|
||||||
|
resourceParameter.Source = ApiParameterSource.Query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.BodyParameterInfo != null)
|
||||||
|
{
|
||||||
|
resourceParameter.Type = parameter.BodyParameterInfo.ParameterType;
|
||||||
|
resourceParameter.Source = ApiParameterSource.Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceParameter.Type != null)
|
||||||
|
{
|
||||||
|
resourceParameter.ModelMetadata = _modelMetadataProvider.GetMetadataForType(
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: resourceParameter.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<ApiResponseFormat> GetResponseFormats(
|
||||||
|
ReflectedActionDescriptor action,
|
||||||
|
IApiResponseMetadataProvider[] responseMetadataAttributes,
|
||||||
|
Type declaredType,
|
||||||
|
Type runtimeType)
|
||||||
|
{
|
||||||
|
var results = new List<ApiResponseFormat>();
|
||||||
|
|
||||||
|
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||||
|
// the results of the previous ones. This is similar to the execution path for content-negotiation.
|
||||||
|
var contentTypes = new List<MediaTypeHeaderValue>();
|
||||||
|
if (responseMetadataAttributes != null)
|
||||||
|
{
|
||||||
|
foreach (var metadataAttribute in responseMetadataAttributes)
|
||||||
|
{
|
||||||
|
metadataAttribute.SetContentTypes(contentTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentTypes.Count == 0)
|
||||||
|
{
|
||||||
|
contentTypes.Add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatters = _formattersProvider.OutputFormatters;
|
||||||
|
foreach (var contentType in contentTypes)
|
||||||
|
{
|
||||||
|
foreach (var formatter in formatters)
|
||||||
|
{
|
||||||
|
var supportedTypes = formatter.GetSupportedContentTypes(declaredType, runtimeType, contentType);
|
||||||
|
if (supportedTypes != null)
|
||||||
|
{
|
||||||
|
foreach (var supportedType in supportedTypes)
|
||||||
|
{
|
||||||
|
results.Add(new ApiResponseFormat()
|
||||||
|
{
|
||||||
|
Formatter = formatter,
|
||||||
|
MediaType = supportedType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type GetDeclaredReturnType(ReflectedActionDescriptor action)
|
||||||
|
{
|
||||||
|
var declaredReturnType = action.MethodInfo.ReturnType;
|
||||||
|
if (declaredReturnType == typeof(void) ||
|
||||||
|
declaredReturnType == typeof(Task))
|
||||||
|
{
|
||||||
|
return typeof(void);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
|
||||||
|
var unwrappedType = TypeHelper.GetTaskInnerTypeOrNull(declaredReturnType) ?? declaredReturnType;
|
||||||
|
|
||||||
|
// If the method is declared to return IActionResult or a derived class, that information
|
||||||
|
// isn't valuable to the formatter.
|
||||||
|
if (typeof(IActionResult).IsAssignableFrom(unwrappedType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return unwrappedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type GetRuntimeReturnType(Type declaredReturnType, IApiResponseMetadataProvider[] metadataAttributes)
|
||||||
|
{
|
||||||
|
// Walk through all of the filter attributes and allow them to set the type. This will execute them
|
||||||
|
// in filter-order allowing the desired behavior for overriding.
|
||||||
|
if (metadataAttributes != null)
|
||||||
|
{
|
||||||
|
Type typeSetByAttribute = null;
|
||||||
|
foreach (var metadataAttribute in metadataAttributes)
|
||||||
|
{
|
||||||
|
if (metadataAttribute.Type != null)
|
||||||
|
{
|
||||||
|
typeSetByAttribute = metadataAttribute.Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one of the filters set a type, then trust it.
|
||||||
|
if (typeSetByAttribute != null)
|
||||||
|
{
|
||||||
|
return typeSetByAttribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, then a filter didn't give us an answer, so we need to figure out if we
|
||||||
|
// want to use the declared return type.
|
||||||
|
//
|
||||||
|
// We've already excluded Task, void, and IActionResult at this point.
|
||||||
|
//
|
||||||
|
// If the action might return any object, then assume we don't know anything about it.
|
||||||
|
if (declaredReturnType == typeof(object))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return declaredReturnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ReflectedActionDescriptor action)
|
||||||
|
{
|
||||||
|
if (action.FilterDescriptors == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
|
||||||
|
// for a filter that implements IApiResponseMetadataProvider.
|
||||||
|
//
|
||||||
|
// The workaround for that is to implement the metadata interface on the IFilterFactory.
|
||||||
|
return action.FilterDescriptors
|
||||||
|
.Select(fd => fd.Filter)
|
||||||
|
.OfType<IApiResponseMetadataProvider>()
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to a collection of <see cref="ApiDescriptionGroup"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IApiDescriptionGroupCollectionProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a collection of <see cref="ApiDescriptionGroup"/>.
|
||||||
|
/// </summary>
|
||||||
|
ApiDescriptionGroupCollection ApiDescriptionGroups { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents group name metadata for an <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IApiDescriptionGroupNameProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The group name for the <see cref="ApiDescription"/> of the associated action or controller.
|
||||||
|
/// </summary>
|
||||||
|
string GroupName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents visibility metadata for an <see cref="ApiDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IApiDescriptionVisibilityProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If <c>false</c> then no <see cref="ApiDescription"/> objects will be created for the associated controller
|
||||||
|
/// or action.
|
||||||
|
/// </summary>
|
||||||
|
bool IgnoreApi { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,21 +5,21 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a return type and a set of possible content types returned by a successful execution of the action.
|
/// Provides a return type and a set of possible content types returned by a successful execution of the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IProducesMetadataProvider
|
public interface IApiResponseMetadataProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimistic return type of the action.
|
/// Optimistic return type of the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Type Type { get; set; }
|
Type Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of allowed content types which can be produced by the action.
|
/// Configures a collection of allowed content types which can be produced by the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IList<MediaTypeHeaderValue> ContentTypes { get; set; }
|
void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
|
||||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
|
@ -14,7 +13,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// which can be used to select a formatter while executing <see cref="ObjectResult"/>.
|
/// which can be used to select a formatter while executing <see cref="ObjectResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
public class ProducesAttribute : ResultFilterAttribute, IProducesMetadataProvider
|
public class ProducesAttribute : ResultFilterAttribute, IApiResponseMetadataProvider
|
||||||
{
|
{
|
||||||
public ProducesAttribute(string contentType, params string[] additionalContentTypes)
|
public ProducesAttribute(string contentType, params string[] additionalContentTypes)
|
||||||
{
|
{
|
||||||
|
|
@ -32,7 +31,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
if (objectResult != null)
|
if (objectResult != null)
|
||||||
{
|
{
|
||||||
objectResult.ContentTypes = ContentTypes;
|
SetContentTypes(objectResult.ContentTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,5 +47,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
return contentTypes;
|
return contentTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes)
|
||||||
|
{
|
||||||
|
contentTypes.Clear();
|
||||||
|
foreach (var contentType in ContentTypes)
|
||||||
|
{
|
||||||
|
contentTypes.Add(contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Filters;
|
using Microsoft.AspNet.Mvc.Filters;
|
||||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
|
@ -143,6 +144,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
foreach (var actionDescriptor in actionDescriptors)
|
foreach (var actionDescriptor in actionDescriptors)
|
||||||
{
|
{
|
||||||
|
AddApiExplorerInfo(actionDescriptor, action, controller);
|
||||||
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, model.Filters);
|
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, model.Filters);
|
||||||
AddActionConstraints(actionDescriptor, action, controller);
|
AddActionConstraints(actionDescriptor, action, controller);
|
||||||
AddControllerRouteConstraints(
|
AddControllerRouteConstraints(
|
||||||
|
|
@ -347,6 +349,23 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return actionDescriptor;
|
return actionDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddApiExplorerInfo(
|
||||||
|
ReflectedActionDescriptor actionDescriptor,
|
||||||
|
ReflectedActionModel action,
|
||||||
|
ReflectedControllerModel controller)
|
||||||
|
{
|
||||||
|
var apiExplorerIsVisible = action.ApiExplorerIsVisible ?? controller.ApiExplorerIsVisible ?? false;
|
||||||
|
if (apiExplorerIsVisible)
|
||||||
|
{
|
||||||
|
var apiExplorerActionData = new ApiDescriptionActionData()
|
||||||
|
{
|
||||||
|
GroupName = action.ApiExplorerGroupName ?? controller.ApiExplorerGroupName,
|
||||||
|
};
|
||||||
|
|
||||||
|
actionDescriptor.SetProperty(apiExplorerActionData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddActionFilters(
|
private static void AddActionFilters(
|
||||||
ReflectedActionDescriptor actionDescriptor,
|
ReflectedActionDescriptor actionDescriptor,
|
||||||
IEnumerable<IFilter> actionFilters,
|
IEnumerable<IFilter> actionFilters,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
|
|
@ -28,6 +29,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
AttributeRouteModel = new ReflectedAttributeRouteModel(routeTemplateAttribute);
|
AttributeRouteModel = new ReflectedAttributeRouteModel(routeTemplateAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var apiExplorerNameAttribute = Attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||||
|
if (apiExplorerNameAttribute != null)
|
||||||
|
{
|
||||||
|
ApiExplorerGroupName = apiExplorerNameAttribute.GroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiExplorerVisibilityAttribute = Attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
|
||||||
|
if (apiExplorerVisibilityAttribute != null)
|
||||||
|
{
|
||||||
|
ApiExplorerIsVisible = !apiExplorerVisibilityAttribute.IgnoreApi;
|
||||||
|
}
|
||||||
|
|
||||||
HttpMethods = new List<string>();
|
HttpMethods = new List<string>();
|
||||||
Parameters = new List<ReflectedParameterModel>();
|
Parameters = new List<ReflectedParameterModel>();
|
||||||
}
|
}
|
||||||
|
|
@ -47,5 +60,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
public List<ReflectedParameterModel> Parameters { get; private set; }
|
public List<ReflectedParameterModel> Parameters { get; private set; }
|
||||||
|
|
||||||
public ReflectedAttributeRouteModel AttributeRouteModel { get; set; }
|
public ReflectedAttributeRouteModel AttributeRouteModel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If <c>true</c>, <see cref="ApiDescription"/> objects will be created for this action. If <c>null</c>
|
||||||
|
/// then the value of <see cref="ReflectedControllerModel.ApiExplorerIsVisible"/> will be used.
|
||||||
|
/// </summary>
|
||||||
|
public bool? ApiExplorerIsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value for <see cref="ApiDescription.GroupName"/> of <see cref="ApiDescription"/> objects created
|
||||||
|
/// for actions defined by this controller. If <c>null</c> then the value of
|
||||||
|
/// <see cref="ReflectedControllerModel.ApiExplorerGroupName"/> will be used.
|
||||||
|
/// </summary>
|
||||||
|
public string ApiExplorerGroupName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
|
|
@ -31,6 +32,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
.Select(rtp => new ReflectedAttributeRouteModel(rtp))
|
.Select(rtp => new ReflectedAttributeRouteModel(rtp))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var apiExplorerNameAttribute = Attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||||
|
if (apiExplorerNameAttribute != null)
|
||||||
|
{
|
||||||
|
ApiExplorerGroupName = apiExplorerNameAttribute.GroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiExplorerVisibilityAttribute = Attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
|
||||||
|
if (apiExplorerVisibilityAttribute != null)
|
||||||
|
{
|
||||||
|
ApiExplorerIsVisible = !apiExplorerVisibilityAttribute.IgnoreApi;
|
||||||
|
}
|
||||||
|
|
||||||
ControllerName = controllerType.Name.EndsWith("Controller", StringComparison.Ordinal)
|
ControllerName = controllerType.Name.EndsWith("Controller", StringComparison.Ordinal)
|
||||||
? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
|
? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
|
||||||
: controllerType.Name;
|
: controllerType.Name;
|
||||||
|
|
@ -49,5 +62,17 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||||
public List<RouteConstraintAttribute> RouteConstraints { get; private set; }
|
public List<RouteConstraintAttribute> RouteConstraints { get; private set; }
|
||||||
|
|
||||||
public List<ReflectedAttributeRouteModel> AttributeRoutes { get; private set; }
|
public List<ReflectedAttributeRouteModel> AttributeRoutes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If <c>true</c>, <see cref="ApiDescription"/> objects will be created for actions defined by this
|
||||||
|
/// controller.
|
||||||
|
/// </summary>
|
||||||
|
public bool? ApiExplorerIsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value for <see cref="ApiDescription.GroupName"/> of <see cref="ApiDescription"/> objects created
|
||||||
|
/// for actions defined by this controller.
|
||||||
|
/// </summary>
|
||||||
|
public string ApiExplorerGroupName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Filters;
|
using Microsoft.AspNet.Mvc.Filters;
|
||||||
using Microsoft.AspNet.Mvc.Internal;
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -103,6 +104,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
yield return describe.Singleton<IAntiForgeryAdditionalDataProvider,
|
yield return describe.Singleton<IAntiForgeryAdditionalDataProvider,
|
||||||
DefaultAntiForgeryAdditionalDataProvider>();
|
DefaultAntiForgeryAdditionalDataProvider>();
|
||||||
|
|
||||||
|
yield return describe.Singleton<IApiDescriptionGroupCollectionProvider,
|
||||||
|
ApiDescriptionGroupCollectionProvider>();
|
||||||
|
yield return describe.Transient<INestedProvider<ApiDescriptionProviderContext>,
|
||||||
|
DefaultApiDescriptionProvider>();
|
||||||
|
|
||||||
yield return
|
yield return
|
||||||
describe.Describe(
|
describe.Describe(
|
||||||
typeof(INestedProviderManager<>),
|
typeof(INestedProviderManager<>),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,508 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
public class DefaultApiDescriptionProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_IgnoresNonReflectedActionDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = new ActionDescriptor();
|
||||||
|
action.SetProperty(new ApiDescriptionActionData());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_IgnoresActionWithoutApiExplorerData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = new ReflectedActionDescriptor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopulatesActionDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Same(action, description.ActionDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopulatesGroupName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
action.GetProperty<ApiDescriptionActionData>().GroupName = "Customers";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal("Customers", description.GroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_HttpMethodIsNullWithoutConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Null(description.HttpMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_CreatesMultipleDescriptionsForMultipleHttpMethods()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
action.MethodConstraints = new List<HttpMethodConstraint>()
|
||||||
|
{
|
||||||
|
new HttpMethodConstraint(new string[] { "PUT", "POST" }),
|
||||||
|
new HttpMethodConstraint(new string[] { "GET" }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, descriptions.Count);
|
||||||
|
|
||||||
|
Assert.Single(descriptions, d => d.HttpMethod == "PUT");
|
||||||
|
Assert.Single(descriptions, d => d.HttpMethod == "POST");
|
||||||
|
Assert.Single(descriptions, d => d.HttpMethod == "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a test for the placeholder behavior - see #886
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopulatesParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
action.Parameters = new List<ParameterDescriptor>()
|
||||||
|
{
|
||||||
|
new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "id",
|
||||||
|
IsOptional = true,
|
||||||
|
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||||
|
},
|
||||||
|
new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "username",
|
||||||
|
BodyParameterInfo = new BodyParameterInfo(typeof(string)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(2, description.ParameterDescriptions.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id");
|
||||||
|
Assert.NotNull(id.ModelMetadata);
|
||||||
|
Assert.True(id.IsOptional);
|
||||||
|
Assert.Same(action.Parameters[0], id.ParameterDescriptor);
|
||||||
|
Assert.Equal(ApiParameterSource.Query, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
|
||||||
|
var username = Assert.Single(description.ParameterDescriptions, p => p.Name == "username");
|
||||||
|
Assert.NotNull(username.ModelMetadata);
|
||||||
|
Assert.False(username.IsOptional);
|
||||||
|
Assert.Same(action.Parameters[1], username.ParameterDescriptor);
|
||||||
|
Assert.Equal(ApiParameterSource.Body, username.Source);
|
||||||
|
Assert.Equal(typeof(string), username.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a placeholder based on current functionality - see #885
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopluatesRelativePath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor();
|
||||||
|
action.AttributeRouteInfo = new AttributeRouteInfo();
|
||||||
|
action.AttributeRouteInfo.Template = "api/Products/{id}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal("api/Products/{id}", description.RelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopluatesResponseType_WithProduct()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(ReturnsProduct));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(typeof(Product), description.ResponseType);
|
||||||
|
Assert.NotNull(description.ResponseModelMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_PopluatesResponseType_WithTaskOfProduct()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(ReturnsTaskOfProduct));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(typeof(Product), description.ResponseType);
|
||||||
|
Assert.NotNull(description.ResponseModelMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsObject))]
|
||||||
|
[InlineData(nameof(ReturnsActionResult))]
|
||||||
|
[InlineData(nameof(ReturnsJsonResult))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfObject))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfActionResult))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfJsonResult))]
|
||||||
|
public void GetApiDescription_DoesNotPopluatesResponseInformation_WhenUnknown(string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Null(description.ResponseType);
|
||||||
|
Assert.Null(description.ResponseModelMetadata);
|
||||||
|
Assert.Empty(description.SupportedResponseFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsVoid))]
|
||||||
|
[InlineData(nameof(ReturnsTask))]
|
||||||
|
public void GetApiDescription_DoesNotPopluatesResponseInformation_WhenVoid(string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(typeof(void), description.ResponseType);
|
||||||
|
Assert.Null(description.ResponseModelMetadata);
|
||||||
|
Assert.Empty(description.SupportedResponseFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(ReturnsObject))]
|
||||||
|
[InlineData(nameof(ReturnsVoid))]
|
||||||
|
[InlineData(nameof(ReturnsActionResult))]
|
||||||
|
[InlineData(nameof(ReturnsJsonResult))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfObject))]
|
||||||
|
[InlineData(nameof(ReturnsTask))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfActionResult))]
|
||||||
|
[InlineData(nameof(ReturnsTaskOfJsonResult))]
|
||||||
|
public void GetApiDescription_PopluatesResponseInformation_WhenSetByFilter(string methodName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
var filter = new ContentTypeAttribute("text/*")
|
||||||
|
{
|
||||||
|
Type = typeof(Order)
|
||||||
|
};
|
||||||
|
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>();
|
||||||
|
action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(typeof(Order), description.ResponseType);
|
||||||
|
Assert.NotNull(description.ResponseModelMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_IncludesResponseFormats()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(ReturnsProduct));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.SupportedResponseFormats.Count);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "text/json");
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "application/json");
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "text/xml");
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "application/xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_IncludesResponseFormats_FilteredByAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(ReturnsProduct));
|
||||||
|
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>();
|
||||||
|
action.FilterDescriptors.Add(new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(2, description.SupportedResponseFormats.Count);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "text/json");
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "text/xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_IncludesResponseFormats_FilteredByType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(ReturnsObject));
|
||||||
|
var filter = new ContentTypeAttribute("text/*")
|
||||||
|
{
|
||||||
|
Type = typeof(Order)
|
||||||
|
};
|
||||||
|
|
||||||
|
action.FilterDescriptors = new List<FilterDescriptor>();
|
||||||
|
action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
|
||||||
|
|
||||||
|
var formatters = CreateFormatters();
|
||||||
|
|
||||||
|
// This will just format Order
|
||||||
|
formatters[0].SupportedTypes.Add(typeof(Order));
|
||||||
|
|
||||||
|
// This will just format Product
|
||||||
|
formatters[1].SupportedTypes.Add(typeof(Product));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action, formatters);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(1, description.SupportedResponseFormats.Count);
|
||||||
|
Assert.Equal(typeof(Order), description.ResponseType);
|
||||||
|
Assert.NotNull(description.ResponseModelMetadata);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Single(formats, f => f.MediaType.RawValue == "text/json");
|
||||||
|
Assert.Same(formatters[0], formats[0].Formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<ApiDescription> GetApiDescriptions(ActionDescriptor action)
|
||||||
|
{
|
||||||
|
return GetApiDescriptions(action, CreateFormatters());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<ApiDescription> GetApiDescriptions(ActionDescriptor action, List<MockFormatter> formatters)
|
||||||
|
{
|
||||||
|
var context = new ApiDescriptionProviderContext(new ActionDescriptor[] { action });
|
||||||
|
|
||||||
|
var formattersProvider = new Mock<IOutputFormattersProvider>(MockBehavior.Strict);
|
||||||
|
formattersProvider.Setup(fp => fp.OutputFormatters).Returns(formatters);
|
||||||
|
|
||||||
|
var modelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
||||||
|
modelMetadataProvider
|
||||||
|
.Setup(mmp => mmp.GetMetadataForType(null, It.IsAny<Type>()))
|
||||||
|
.Returns((Func<object> accessor, Type type) =>
|
||||||
|
{
|
||||||
|
return new ModelMetadata(modelMetadataProvider.Object, null, accessor, type, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
var provider = new DefaultApiDescriptionProvider(formattersProvider.Object, modelMetadataProvider.Object);
|
||||||
|
provider.Invoke(context, () => { });
|
||||||
|
return context.Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MockFormatter> CreateFormatters()
|
||||||
|
{
|
||||||
|
// Include some default formatters that look reasonable, some tests will override this.
|
||||||
|
var formatters = new List<MockFormatter>()
|
||||||
|
{
|
||||||
|
new MockFormatter(),
|
||||||
|
new MockFormatter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
formatters[0].SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||||
|
formatters[0].SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
|
||||||
|
|
||||||
|
formatters[1].SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
|
||||||
|
formatters[1].SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
|
||||||
|
|
||||||
|
return formatters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReflectedActionDescriptor CreateActionDescriptor(string methodName = null)
|
||||||
|
{
|
||||||
|
var action = new ReflectedActionDescriptor();
|
||||||
|
action.SetProperty(new ApiDescriptionActionData());
|
||||||
|
|
||||||
|
action.MethodInfo = GetType().GetMethod(
|
||||||
|
methodName ?? "ReturnsObject",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object ReturnsObject()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReturnsVoid()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private IActionResult ReturnsActionResult()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonResult ReturnsJsonResult()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<Product> ReturnsTaskOfProduct()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<object> ReturnsTaskOfObject()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ReturnsTask()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<IActionResult> ReturnsTaskOfActionResult()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<JsonResult> ReturnsTaskOfJsonResult()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Product ReturnsProduct()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Product
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Order
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockFormatter : OutputFormatter
|
||||||
|
{
|
||||||
|
public List<Type> SupportedTypes { get; } = new List<Type>();
|
||||||
|
|
||||||
|
public override Task WriteResponseBodyAsync(OutputFormatterContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanWriteType(Type declaredType, Type actualType)
|
||||||
|
{
|
||||||
|
if (SupportedTypes.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ((actualType ?? declaredType) == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return SupportedTypes.Contains(actualType ?? declaredType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ContentTypeAttribute : Attribute, IFilter, IApiResponseMetadataProvider
|
||||||
|
{
|
||||||
|
public ContentTypeAttribute(string mediaType)
|
||||||
|
{
|
||||||
|
ContentTypes.Add(MediaTypeHeaderValue.Parse(mediaType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MediaTypeHeaderValue> ContentTypes { get; } = new List<MediaTypeHeaderValue>();
|
||||||
|
|
||||||
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
public void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes)
|
||||||
|
{
|
||||||
|
contentTypes.Clear();
|
||||||
|
foreach (var contentType in ContentTypes)
|
||||||
|
{
|
||||||
|
contentTypes.Add(contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -1016,6 +1017,119 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.Equal("stub/{controller}/{action}", action.AttributeRouteInfo.Template);
|
Assert.Equal("stub/{controller}/{action}", action.AttributeRouteInfo.Template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsExtensionData_WhenVisible()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerVisibleController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var action = Assert.Single(actions);
|
||||||
|
Assert.NotNull(action.GetProperty<ApiDescriptionActionData>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsExtensionData_WhenVisible_CanOverrideControllerOnAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerVisibilityOverrideController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, actions.Count());
|
||||||
|
|
||||||
|
var action = Assert.Single(actions, a => a.Name == "Edit");
|
||||||
|
Assert.NotNull(action.GetProperty<ApiDescriptionActionData>());
|
||||||
|
|
||||||
|
action = Assert.Single(actions, a => a.Name == "Create");
|
||||||
|
Assert.Null(action.GetProperty<ApiDescriptionActionData>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(ApiExplorerNotVisibleController))]
|
||||||
|
[InlineData(typeof(ApiExplorerExplicitlyNotVisibleController))]
|
||||||
|
[InlineData(typeof(ApiExplorerExplicitlyNotVisibleOnActionController))]
|
||||||
|
public void ApiExplorer_DoesNotSetExtensionData_WhenNotVisible(Type controllerType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(controllerType.GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var action = Assert.Single(actions);
|
||||||
|
Assert.Null(action.GetProperty<ApiDescriptionActionData>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsName_DefaultToNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerNoNameController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
var action = Assert.Single(actions);
|
||||||
|
Assert.Null(action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsName_OnController()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerNameOnControllerController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
var action = Assert.Single(actions);
|
||||||
|
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsName_OnAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerNameOnActionController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var action = Assert.Single(actions);
|
||||||
|
Assert.Equal("Blog", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApiExplorer_SetsName_CanOverrideControllerOnAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(typeof(ApiExplorerNameOverrideController).GetTypeInfo());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actions = provider.GetDescriptors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, actions.Count());
|
||||||
|
|
||||||
|
var action = Assert.Single(actions, a => a.Name == "Edit");
|
||||||
|
Assert.Equal("Blog", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
|
|
||||||
|
action = Assert.Single(actions, a => a.Name == "Create");
|
||||||
|
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
|
}
|
||||||
|
|
||||||
private ReflectedActionDescriptorProvider GetProvider(
|
private ReflectedActionDescriptorProvider GetProvider(
|
||||||
TypeInfo controllerTypeInfo,
|
TypeInfo controllerTypeInfo,
|
||||||
IEnumerable<IFilter> filters = null)
|
IEnumerable<IFilter> filters = null)
|
||||||
|
|
@ -1404,5 +1518,65 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int Name { get; set; }
|
public int Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ApiExplorerNotVisibleController
|
||||||
|
{
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings()]
|
||||||
|
private class ApiExplorerVisibleController
|
||||||
|
{
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
private class ApiExplorerExplicitlyNotVisibleController
|
||||||
|
{
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ApiExplorerExplicitlyNotVisibleOnActionController
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
private class ApiExplorerVisibilityOverrideController
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(IgnoreApi = false)]
|
||||||
|
public void Edit() { }
|
||||||
|
|
||||||
|
public void Create() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(GroupName = "Store")]
|
||||||
|
private class ApiExplorerNameOnControllerController
|
||||||
|
{
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class ApiExplorerNameOnActionController
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "Blog")]
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings()]
|
||||||
|
private class ApiExplorerNoNameController
|
||||||
|
{
|
||||||
|
public void Edit() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(GroupName = "Store")]
|
||||||
|
private class ApiExplorerNameOverrideController
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "Blog")]
|
||||||
|
public void Edit() { }
|
||||||
|
|
||||||
|
public void Create() { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,34 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
|
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReflectedActionModel_PopulatesApiExplorerInfo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionMethod = typeof(BlogController).GetMethod("Create");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = new ReflectedActionModel(actionMethod);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(false, model.ApiExplorerIsVisible);
|
||||||
|
Assert.Equal("Blog", model.ApiExplorerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReflectedActionModel_PopulatesApiExplorerInfo_NoAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionMethod = typeof(BlogController).GetMethod("Edit");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = new ReflectedActionModel(actionMethod);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(model.ApiExplorerIsVisible);
|
||||||
|
Assert.Null(model.ApiExplorerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
private class BlogController
|
private class BlogController
|
||||||
{
|
{
|
||||||
[MyOther]
|
[MyOther]
|
||||||
|
|
@ -46,6 +74,12 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
public void Edit()
|
public void Edit()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true, GroupName = "Blog")]
|
||||||
|
public void Create()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MyFilterAttribute : Attribute, IFilter
|
private class MyFilterAttribute : Attribute, IFilter
|
||||||
|
|
@ -56,4 +90,4 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(5, model.Attributes.Count);
|
Assert.Equal(6, model.Attributes.Count);
|
||||||
|
|
||||||
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
|
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
|
||||||
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
|
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
|
||||||
Assert.Single(model.Attributes, a => a is MyRouteConstraintAttribute);
|
Assert.Single(model.Attributes, a => a is MyRouteConstraintAttribute);
|
||||||
|
Assert.Single(model.Attributes, a => a is ApiExplorerSettingsAttribute);
|
||||||
|
|
||||||
var routes = model.Attributes.OfType<RouteAttribute>().ToList();
|
var routes = model.Attributes.OfType<RouteAttribute>().ToList();
|
||||||
Assert.Equal(2, routes.Count());
|
Assert.Equal(2, routes.Count());
|
||||||
|
|
@ -102,11 +103,54 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
Assert.Single(model.AttributeRoutes, r => r.Template.Equals("Microblog"));
|
Assert.Single(model.AttributeRoutes, r => r.Template.Equals("Microblog"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReflectedControllerModel_PopulatesApiExplorerInfo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var controllerType = typeof(BlogController);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(true, model.ApiExplorerIsVisible);
|
||||||
|
Assert.Equal("Blog", model.ApiExplorerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReflectedControllerModel_PopulatesApiExplorerInfo_Inherited()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var controllerType = typeof(DerivedController);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(true, model.ApiExplorerIsVisible);
|
||||||
|
Assert.Equal("API", model.ApiExplorerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReflectedControllerModel_PopulatesApiExplorerInfo_NoAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var controllerType = typeof(Store);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(model.ApiExplorerIsVisible);
|
||||||
|
Assert.Null(model.ApiExplorerGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
[MyOther]
|
[MyOther]
|
||||||
[MyFilter]
|
[MyFilter]
|
||||||
[MyRouteConstraint]
|
[MyRouteConstraint]
|
||||||
[Route("Blog")]
|
[Route("Blog")]
|
||||||
[Route("Microblog")]
|
[Route("Microblog")]
|
||||||
|
[ApiExplorerSettings(GroupName = "Blog")]
|
||||||
private class BlogController
|
private class BlogController
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +159,16 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DerivedController : BaseController
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(GroupName = "API")]
|
||||||
|
private class BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class MyRouteConstraintAttribute : RouteConstraintAttribute
|
private class MyRouteConstraintAttribute : RouteConstraintAttribute
|
||||||
{
|
{
|
||||||
public MyRouteConstraintAttribute()
|
public MyRouteConstraintAttribute()
|
||||||
|
|
@ -131,4 +185,4 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,513 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Xunit;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ApiExplorerTest
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider = TestHelper.CreateServices("ApiExplorerWebSite");
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new ApiExplorer.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_IsVisible_EnabledWithConvention()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerVisbilityEnabledByConvention");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_IsVisible_DisabledWithConvention()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerVisbilityDisabledByConvention");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_IsVisible_DisabledWithAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerVisibilitySetExplicitly/Disabled");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_IsVisible_EnabledWithAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerVisibilitySetExplicitly/Enabled");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_GroupName_SetByConvention()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerNameSetByConvention");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(description.GroupName, "ApiExplorerNameSetByConvention");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_GroupName_SetByAttributeOnController()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerNameSetExplicitly/SetOnController");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(description.GroupName, "SetOnController");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_GroupName_SetByAttributeOnAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerNameSetExplicitly/SetOnAction");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(description.GroupName, "SetOnAction");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_HttpMethod_All()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerHttpMethod/All");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Null(description.HttpMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_HttpMethod_Single()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerHttpMethod/Get");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal("GET", description.HttpMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is hitting one action with two allowed methods (using [AcceptVerbs]). This should
|
||||||
|
// return two api descriptions.
|
||||||
|
[Theory]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("POST")]
|
||||||
|
public async Task ApiExplorer_HttpMethod_Single(string httpMethod)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
new HttpMethod(httpMethod),
|
||||||
|
"http://localhost/ApiExplorerHttpMethod/Single");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
|
||||||
|
Assert.Single(result, d => d.HttpMethod == "PUT");
|
||||||
|
Assert.Single(result, d => d.HttpMethod == "POST");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GetVoid")]
|
||||||
|
[InlineData("GetTask")]
|
||||||
|
public async Task ApiExplorer_ResponseType_VoidWithoutAttribute(string action)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseTypeWithoutAttribute/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(typeof(void).FullName, description.ResponseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GetObject")]
|
||||||
|
[InlineData("GetIActionResult")]
|
||||||
|
[InlineData("GetDerivedActionResult")]
|
||||||
|
[InlineData("GetTaskOfObject")]
|
||||||
|
[InlineData("GetTaskOfIActionResult")]
|
||||||
|
[InlineData("GetTaskOfDerivedActionResult")]
|
||||||
|
public async Task ApiExplorer_ResponseType_UnknownWithoutAttribute(string action)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseTypeWithoutAttribute/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Null(description.ResponseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GetProduct", "ApiExplorer.Product")]
|
||||||
|
[InlineData("GetInt", "System.Int32")]
|
||||||
|
[InlineData("GetTaskOfProduct", "ApiExplorer.Product")]
|
||||||
|
[InlineData("GetTaskOfInt", "System.Int32")]
|
||||||
|
public async Task ApiExplorer_ResponseType_KnownWithoutAttribute(string action, string type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseTypeWithoutAttribute/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(type, description.ResponseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GetVoid", "ApiExplorer.Customer")]
|
||||||
|
[InlineData("GetObject", "ApiExplorer.Product")]
|
||||||
|
[InlineData("GetIActionResult", "System.String")]
|
||||||
|
[InlineData("GetProduct", "ApiExplorer.Customer")]
|
||||||
|
[InlineData("GetTask", "System.Int32")]
|
||||||
|
public async Task ApiExplorer_ResponseType_KnownWithAttribute(string action, string type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseTypeWithAttribute/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(type, description.ResponseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Controller", "ApiExplorer.Product")]
|
||||||
|
[InlineData("Action", "ApiExplorer.Customer")]
|
||||||
|
public async Task ApiExplorer_ResponseType_OverrideOnAction(string action, string type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseTypeOverrideOnAction/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
Assert.Equal(type, description.ResponseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_Unset()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerResponseContentType/Unset");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Equal(4, formats.Count);
|
||||||
|
|
||||||
|
var textXml = Assert.Single(formats, f => f.MediaType == "text/xml");
|
||||||
|
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, textXml.FormatterType);
|
||||||
|
var applicationXml = Assert.Single(formats, f => f.MediaType == "application/xml");
|
||||||
|
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, applicationXml.FormatterType);
|
||||||
|
|
||||||
|
var textJson = Assert.Single(formats, f => f.MediaType == "text/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
|
||||||
|
var applicationJson = Assert.Single(formats, f => f.MediaType == "application/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, applicationJson.FormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// uses [Produces("*/*")]
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_AllTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerResponseContentType/AllTypes");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Equal(4, formats.Count);
|
||||||
|
|
||||||
|
var textXml = Assert.Single(formats, f => f.MediaType == "text/xml");
|
||||||
|
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, textXml.FormatterType);
|
||||||
|
var applicationXml = Assert.Single(formats, f => f.MediaType == "application/xml");
|
||||||
|
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, applicationXml.FormatterType);
|
||||||
|
|
||||||
|
var textJson = Assert.Single(formats, f => f.MediaType == "text/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
|
||||||
|
var applicationJson = Assert.Single(formats, f => f.MediaType == "application/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, applicationJson.FormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_Range()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerResponseContentType/Range");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Equal(2, formats.Count);
|
||||||
|
|
||||||
|
var textXml = Assert.Single(formats, f => f.MediaType == "text/xml");
|
||||||
|
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, textXml.FormatterType);
|
||||||
|
|
||||||
|
var textJson = Assert.Single(formats, f => f.MediaType == "text/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_Specific()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerResponseContentType/Specific");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Equal(1, formats.Count);
|
||||||
|
|
||||||
|
var applicationJson = Assert.Single(formats, f => f.MediaType == "application/json");
|
||||||
|
Assert.Equal(typeof(JsonOutputFormatter).FullName, applicationJson.FormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_NoMatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerResponseContentType/NoMatch");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var formats = description.SupportedResponseFormats;
|
||||||
|
Assert.Empty(formats);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Controller", "text/xml", "Microsoft.AspNet.Mvc.XmlDataContractSerializerOutputFormatter")]
|
||||||
|
[InlineData("Action", "application/json", "Microsoft.AspNet.Mvc.JsonOutputFormatter")]
|
||||||
|
public async Task ApiExplorer_ResponseContentType_OverrideOnAction(
|
||||||
|
string action,
|
||||||
|
string contentType,
|
||||||
|
string formatterType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(
|
||||||
|
"http://localhost/ApiExplorerResponseContentTypeOverrideOnAction/" + action);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
|
||||||
|
var format = Assert.Single(description.SupportedResponseFormats);
|
||||||
|
Assert.Equal(contentType, format.MediaType);
|
||||||
|
Assert.Equal(formatterType, format.FormatterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerData
|
||||||
|
{
|
||||||
|
public string GroupName { get; set; }
|
||||||
|
|
||||||
|
public string HttpMethod { get; set; }
|
||||||
|
|
||||||
|
public List<ApiExplorerParameterData> ParameterDescriptions { get; } = new List<ApiExplorerParameterData>();
|
||||||
|
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
|
||||||
|
public string ResponseType { get; set; }
|
||||||
|
|
||||||
|
public List<ApiExplorerResponseData> SupportedResponseFormats { get; } = new List<ApiExplorerResponseData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerParameterData
|
||||||
|
{
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerResponseData
|
||||||
|
{
|
||||||
|
public string MediaType { get; set; }
|
||||||
|
|
||||||
|
public string FormatterType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"ActivatorWebSite": "",
|
"ActivatorWebSite": "",
|
||||||
"AddServicesWebSite": "",
|
"AddServicesWebSite": "",
|
||||||
"AntiForgeryWebSite": "",
|
"AntiForgeryWebSite": "",
|
||||||
|
"ApiExplorerWebSite": "",
|
||||||
"BasicWebSite": "",
|
"BasicWebSite": "",
|
||||||
"CompositeViewEngine": "",
|
"CompositeViewEngine": "",
|
||||||
"ConnegWebsite": "",
|
"ConnegWebsite": "",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An action filter that looks up and serializes Api Explorer data for the action.
|
||||||
|
///
|
||||||
|
/// This replaces the 'actual' output of the action.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiExplorerDataFilter : ActionFilterAttribute
|
||||||
|
{
|
||||||
|
private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;
|
||||||
|
|
||||||
|
public ApiExplorerDataFilter(IApiDescriptionGroupCollectionProvider descriptionProvider)
|
||||||
|
{
|
||||||
|
_descriptionProvider = descriptionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnActionExecuted(ActionExecutedContext context)
|
||||||
|
{
|
||||||
|
var descriptions = new List<ApiExplorerData>();
|
||||||
|
foreach (var group in _descriptionProvider.ApiDescriptionGroups.Items)
|
||||||
|
{
|
||||||
|
foreach (var description in group.Items)
|
||||||
|
{
|
||||||
|
if (context.ActionDescriptor == description.ActionDescriptor)
|
||||||
|
{
|
||||||
|
descriptions.Add(CreateSerializableData(description));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Result = new JsonResult(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiExplorerData CreateSerializableData(ApiDescription description)
|
||||||
|
{
|
||||||
|
var data = new ApiExplorerData()
|
||||||
|
{
|
||||||
|
GroupName = description.GroupName,
|
||||||
|
HttpMethod = description.HttpMethod,
|
||||||
|
RelativePath = description.RelativePath,
|
||||||
|
ResponseType = description.ResponseType?.FullName,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var parameter in description.ParameterDescriptions)
|
||||||
|
{
|
||||||
|
var parameterData = new ApiExplorerParameterData()
|
||||||
|
{
|
||||||
|
IsOptional = parameter.IsOptional,
|
||||||
|
Name = parameter.Name,
|
||||||
|
Source = parameter.Source.ToString(),
|
||||||
|
Type = parameter.Type.FullName,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.ParameterDescriptions.Add(parameterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var response in description.SupportedResponseFormats)
|
||||||
|
{
|
||||||
|
var responseData = new ApiExplorerResponseData()
|
||||||
|
{
|
||||||
|
FormatterType = response.Formatter.GetType().FullName,
|
||||||
|
MediaType = response.MediaType.RawValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.SupportedResponseFormats.Add(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerData
|
||||||
|
{
|
||||||
|
public string GroupName { get; set; }
|
||||||
|
|
||||||
|
public string HttpMethod { get; set; }
|
||||||
|
|
||||||
|
public List<ApiExplorerParameterData> ParameterDescriptions { get; } = new List<ApiExplorerParameterData>();
|
||||||
|
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
|
||||||
|
public string ResponseType { get; set; }
|
||||||
|
|
||||||
|
public List<ApiExplorerResponseData> SupportedResponseFormats { get; } = new List<ApiExplorerResponseData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerParameterData
|
||||||
|
{
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerResponseData
|
||||||
|
{
|
||||||
|
public string MediaType { get; set; }
|
||||||
|
|
||||||
|
public string FormatterType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
// Disables ApiExplorer for a specific controller type.
|
||||||
|
// This is part of the test that validates that ApiExplorer can be configured via
|
||||||
|
// convention
|
||||||
|
public class ApiExplorerVisibilityDisabledConvention : IReflectedApplicationModelConvention
|
||||||
|
{
|
||||||
|
private readonly TypeInfo _type;
|
||||||
|
|
||||||
|
public ApiExplorerVisibilityDisabledConvention(Type type)
|
||||||
|
{
|
||||||
|
_type = type.GetTypeInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnModelCreated(ReflectedApplicationModel model)
|
||||||
|
{
|
||||||
|
foreach (var controller in model.Controllers)
|
||||||
|
{
|
||||||
|
if (controller.ControllerType == _type)
|
||||||
|
{
|
||||||
|
controller.ApiExplorerIsVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
// Enables ApiExplorer for controllers that haven't explicitly configured it.
|
||||||
|
// This is part of the test that validates that ApiExplorer can be configured via
|
||||||
|
// convention
|
||||||
|
public class ApiExplorerVisibilityEnabledConvention : IReflectedApplicationModelConvention
|
||||||
|
{
|
||||||
|
public void OnModelCreated(ReflectedApplicationModel model)
|
||||||
|
{
|
||||||
|
foreach (var controller in model.Controllers)
|
||||||
|
{
|
||||||
|
if (controller.ApiExplorerIsVisible == null)
|
||||||
|
{
|
||||||
|
controller.ApiExplorerIsVisible = true;
|
||||||
|
controller.ApiExplorerGroupName = controller.ControllerName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>61061528-071e-424e-965a-07bcc2f02672</ProjectGuid>
|
||||||
|
<OutputType>Web</OutputType>
|
||||||
|
<RootNamespace>ApiExplorer</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Console'">
|
||||||
|
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Web'">
|
||||||
|
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<DevelopmentServerPort>7591</DevelopmentServerPort>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerHttpMethod")]
|
||||||
|
public class ApiExplorerHttpMethodController : Controller
|
||||||
|
{
|
||||||
|
[Route("All")]
|
||||||
|
public void All()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Get")]
|
||||||
|
public void Get()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[AcceptVerbs("PUT", "POST", Route = "Single")]
|
||||||
|
public void PutOrPost()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("MultipleActions")]
|
||||||
|
[HttpPut("MultipleActions")]
|
||||||
|
public void MultipleActions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerNameSetByConvention")]
|
||||||
|
public class ApiExplorerNameSetByConventionController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public void Get()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "SetOnController")]
|
||||||
|
[Route("ApiExplorerNameSetExplicitly")]
|
||||||
|
public class ApiExplorerNameSetExplicitlyController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("SetOnController")]
|
||||||
|
public void SetOnController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(GroupName = "SetOnAction")]
|
||||||
|
[HttpGet("SetOnAction")]
|
||||||
|
public void SetOnAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerResponseContentType/[Action]")]
|
||||||
|
public class ApiExplorerResponseContentTypeController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public Product Unset()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("*/*")]
|
||||||
|
public Product AllTypes()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("text/*")]
|
||||||
|
public Product Range()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public Product Specific()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/hal+json")]
|
||||||
|
public Product NoMatch()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Produces("text/xml")]
|
||||||
|
[Route("ApiExplorerResponseContentTypeOverrideOnAction")]
|
||||||
|
public class ApiExplorerResponseContentTypeOverrideOnActionController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("Controller")]
|
||||||
|
public Product GetController()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Action")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public Product GetAction()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Produces("*/*", Type = typeof(Product))]
|
||||||
|
[Route("ApiExplorerResponseTypeOverrideOnAction")]
|
||||||
|
public class ApiExplorerResponseTypeOverrideOnActionController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("Controller")]
|
||||||
|
public void GetController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Action")]
|
||||||
|
[ProducesType(typeof(Customer))]
|
||||||
|
public object GetAction()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerResponseTypeWithAttribute/[Action]")]
|
||||||
|
public class ApiExplorerResponseTypeWithAttributeController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesType(typeof(Customer))]
|
||||||
|
public void GetVoid()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("*/*", Type = typeof(Product))]
|
||||||
|
public object GetObject()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json", Type = typeof(string))]
|
||||||
|
public IActionResult GetIActionResult()
|
||||||
|
{
|
||||||
|
return new EmptyResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json", Type = typeof(int))]
|
||||||
|
public Task GetTask()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesType(typeof(Customer))] // It's possible to lie about what type you return
|
||||||
|
public Product GetProduct()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerResponseTypeWithoutAttribute/[Action]")]
|
||||||
|
public class ApiExplorerResponseTypeWithoutAttributeController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public void GetVoid()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public object GetObject()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetIActionResult()
|
||||||
|
{
|
||||||
|
return new EmptyResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public ObjectResult GetDerivedActionResult()
|
||||||
|
{
|
||||||
|
return new ObjectResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Product GetProduct()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public int GetInt()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task GetTask()
|
||||||
|
{
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task<object> GetTaskOfObject()
|
||||||
|
{
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task<IActionResult> GetTaskOfIActionResult()
|
||||||
|
{
|
||||||
|
return Task.FromResult<IActionResult>(new EmptyResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task<ObjectResult> GetTaskOfDerivedActionResult()
|
||||||
|
{
|
||||||
|
return Task.FromResult<ObjectResult>(new ObjectResult(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task<Product> GetTaskOfProduct()
|
||||||
|
{
|
||||||
|
return Task.FromResult<Product>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public Task<int> GetTaskOfInt()
|
||||||
|
{
|
||||||
|
return Task.FromResult<int>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerVisbilityDisabledByConvention")]
|
||||||
|
public class ApiExplorerVisbilityDisabledByConventionController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public void Get()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerVisbilityEnabledByConvention")]
|
||||||
|
public class ApiExplorerVisbilityEnabledByConventionController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public void Get()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
[Route("ApiExplorerVisibilitySetExplicitly")]
|
||||||
|
public class ApiExplorerVisibilitySetExplicitlyController : Controller
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(IgnoreApi = false)]
|
||||||
|
[HttpGet("Enabled")]
|
||||||
|
public void Enabled()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Disabled")]
|
||||||
|
public void Disabled()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
public class Customer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
public class ProducesTypeAttribute : ResultFilterAttribute, IApiResponseMetadataProvider
|
||||||
|
{
|
||||||
|
public ProducesTypeAttribute(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type Type { get; private set; }
|
||||||
|
|
||||||
|
public void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace ApiExplorer
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = app.GetTestConfiguration();
|
||||||
|
|
||||||
|
app.UseServices(services =>
|
||||||
|
{
|
||||||
|
services.AddMvc(configuration);
|
||||||
|
services.AddSingleton<ApiExplorerDataFilter>();
|
||||||
|
|
||||||
|
services.SetupOptions<MvcOptions>(options =>
|
||||||
|
{
|
||||||
|
options.Filters.AddService(typeof(ApiExplorerDataFilter));
|
||||||
|
|
||||||
|
options.ApplicationModelConventions.Add(new ApiExplorerVisibilityEnabledConvention());
|
||||||
|
options.ApplicationModelConventions.Add(new ApiExplorerVisibilityDisabledConvention(
|
||||||
|
typeof(ApiExplorerVisbilityDisabledByConventionController)));
|
||||||
|
|
||||||
|
options.OutputFormatters.Clear();
|
||||||
|
options.OutputFormatters.Add(new JsonOutputFormatter(
|
||||||
|
JsonOutputFormatter.CreateDefaultSettings(),
|
||||||
|
indent: false));
|
||||||
|
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseMvc(routes =>
|
||||||
|
{
|
||||||
|
routes.MapRoute("default", "{controller}/{action}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Mvc": "",
|
||||||
|
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc.TestConfiguration": ""
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"aspnet50": { },
|
||||||
|
"aspnetcore50": { }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue