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
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22115.0
|
||||
VisualStudioVersion = 14.0.22013.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
|
@ -80,6 +80,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "XmlSerializerWebSite", "tes
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\WebSites\UrlHelperWebSite\UrlHelperWebSite.kproj", "{A192E504-2881-41DC-90D1-B7F1DD1134E8}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.kproj", "{61061528-071E-424E-965A-07BCC2F02672}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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.Build.0 = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -448,5 +460,6 @@ Global
|
|||
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {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}
|
||||
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
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()
|
||||
{
|
||||
Properties = new Dictionary<object, object>();
|
||||
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
|
@ -34,5 +35,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// A friendly name for this action.
|
||||
/// </summary>
|
||||
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 Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
namespace Microsoft.AspNet.Mvc.Description
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a return type and a set of possible content types returned by a successful execution of the action.
|
||||
/// </summary>
|
||||
public interface IProducesMetadataProvider
|
||||
public interface IApiResponseMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Optimistic return type of the action.
|
||||
/// </summary>
|
||||
Type Type { get; set; }
|
||||
Type Type { get; }
|
||||
|
||||
/// <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>
|
||||
IList<MediaTypeHeaderValue> ContentTypes { get; set; }
|
||||
void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
|
||||
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"/>.
|
||||
/// </summary>
|
||||
[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)
|
||||
{
|
||||
|
|
@ -32,7 +31,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
if (objectResult != null)
|
||||
{
|
||||
objectResult.ContentTypes = ContentTypes;
|
||||
SetContentTypes(objectResult.ContentTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,5 +47,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
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.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
|
@ -143,6 +144,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var actionDescriptor in actionDescriptors)
|
||||
{
|
||||
AddApiExplorerInfo(actionDescriptor, action, controller);
|
||||
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, model.Filters);
|
||||
AddActionConstraints(actionDescriptor, action, controller);
|
||||
AddControllerRouteConstraints(
|
||||
|
|
@ -347,6 +349,23 @@ namespace Microsoft.AspNet.Mvc
|
|||
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(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
IEnumerable<IFilter> actionFilters,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
|
|
@ -28,6 +29,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
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>();
|
||||
Parameters = new List<ReflectedParameterModel>();
|
||||
}
|
||||
|
|
@ -47,5 +60,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
public List<ReflectedParameterModel> Parameters { get; private 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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
|
|
@ -31,6 +32,18 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
.Select(rtp => new ReflectedAttributeRouteModel(rtp))
|
||||
.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)
|
||||
? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
|
||||
: controllerType.Name;
|
||||
|
|
@ -49,5 +62,17 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
public List<RouteConstraintAttribute> RouteConstraints { 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 Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
@ -103,6 +104,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Singleton<IAntiForgeryAdditionalDataProvider,
|
||||
DefaultAntiForgeryAdditionalDataProvider>();
|
||||
|
||||
yield return describe.Singleton<IApiDescriptionGroupCollectionProvider,
|
||||
ApiDescriptionGroupCollectionProvider>();
|
||||
yield return describe.Transient<INestedProvider<ApiDescriptionProviderContext>,
|
||||
DefaultApiDescriptionProvider>();
|
||||
|
||||
yield return
|
||||
describe.Describe(
|
||||
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.Reflection;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -1016,6 +1017,119 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
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(
|
||||
TypeInfo controllerTypeInfo,
|
||||
IEnumerable<IFilter> filters = null)
|
||||
|
|
@ -1404,5 +1518,65 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
public int Id { 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]);
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
[MyOther]
|
||||
|
|
@ -46,6 +74,12 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
|||
public void Edit()
|
||||
{
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true, GroupName = "Blog")]
|
||||
public void Create()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// 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 MyFilterAttribute);
|
||||
Assert.Single(model.Attributes, a => a is MyRouteConstraintAttribute);
|
||||
Assert.Single(model.Attributes, a => a is ApiExplorerSettingsAttribute);
|
||||
|
||||
var routes = model.Attributes.OfType<RouteAttribute>().ToList();
|
||||
Assert.Equal(2, routes.Count());
|
||||
|
|
@ -102,11 +103,54 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
|||
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]
|
||||
[MyFilter]
|
||||
[MyRouteConstraint]
|
||||
[Route("Blog")]
|
||||
[Route("Microblog")]
|
||||
[ApiExplorerSettings(GroupName = "Blog")]
|
||||
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
|
||||
{
|
||||
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": "",
|
||||
"AddServicesWebSite": "",
|
||||
"AntiForgeryWebSite": "",
|
||||
"ApiExplorerWebSite": "",
|
||||
"BasicWebSite": "",
|
||||
"CompositeViewEngine": "",
|
||||
"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