Merge branch 'release' into dev
This commit is contained in:
commit
7fbe0ce307
|
|
@ -12,13 +12,17 @@ namespace MvcSample.Web.ApiExplorerSamples
|
||||||
public class ProductsAdminController : Controller
|
public class ProductsAdminController : Controller
|
||||||
{
|
{
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
public void AddProduct([FromBody] Product product)
|
[Produces("application/json", Type = typeof(Product))]
|
||||||
|
public IActionResult AddProduct([FromBody] Product product)
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}")]
|
[HttpPost("{id?}")]
|
||||||
public void UpdateProduct([FromBody] Product product)
|
[Produces("application/json", Type = typeof(Product))]
|
||||||
|
public IActionResult UpdateProduct(UpdateProductDTO dto)
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/Stock")]
|
[HttpPost("{id}/Stock")]
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,12 @@ namespace MvcSample.Web.ApiExplorerSamples
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Produces("application/json", Type = typeof(ProductOrderConfirmation))]
|
||||||
|
[HttpPut("{order.acountId:int}/PlaceOrder")]
|
||||||
|
public IActionResult PlaceOrder(Order order)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
public class Order
|
||||||
|
{
|
||||||
|
[FromRoute]
|
||||||
|
public int AccountId { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public List<OrderItem> Items { get; set; }
|
||||||
|
|
||||||
|
[FromQuery]
|
||||||
|
public bool? IncludeWarranty { get; set; }
|
||||||
|
|
||||||
|
public class OrderItem
|
||||||
|
{
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,8 @@ namespace MvcSample.Web.ApiExplorerSamples
|
||||||
{
|
{
|
||||||
public class Product
|
public class Product
|
||||||
{
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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 MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
public class ProductChangeDTO
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 MvcSample.Web.ApiExplorerSamples
|
||||||
|
{
|
||||||
|
public class UpdateProductDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Product Product { get; set; }
|
||||||
|
|
||||||
|
[FromHeader(Name = "Admin-User")]
|
||||||
|
public string AdminId { get; set; }
|
||||||
|
|
||||||
|
public string Comments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,38 +2,58 @@
|
||||||
@model ApiDescription
|
@model ApiDescription
|
||||||
|
|
||||||
<div class="api-description">
|
<div class="api-description">
|
||||||
<h4>@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")</h4>
|
<h4>
|
||||||
|
<span title="@Model.ActionDescriptor.DisplayName">
|
||||||
|
@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
<hr />
|
<hr />
|
||||||
<h5>For action: @Model.ActionDescriptor.DisplayName</h5>
|
|
||||||
<p>Return Type: @(Model.ResponseType?.FullName ?? "Unknown Type")</p>
|
|
||||||
|
|
||||||
|
<h5>Parameters:</h5>
|
||||||
@if (Model.ParameterDescriptions.Count > 0)
|
@if (Model.ParameterDescriptions.Count > 0)
|
||||||
{
|
{
|
||||||
<p>Parameters:</p>
|
<table>
|
||||||
<ul>
|
<thead>
|
||||||
@foreach (var parameter in Model.ParameterDescriptions)
|
<tr>
|
||||||
{
|
<td>Name</td>
|
||||||
<li>
|
<td>Data Type</td>
|
||||||
@parameter.Name - @(parameter?.Type?.FullName ?? "Unknown") - @parameter.Source.ToString()
|
<td>Source</td>
|
||||||
@if (parameter.Constraints != null && parameter.Constraints.Any())
|
</tr>
|
||||||
{
|
</thead>
|
||||||
var constraints = parameter.Constraints;
|
<tbody>
|
||||||
Write("-Constraint:" + string.Join(", ", constraints.Select(c => c.GetType()?.Name?.Replace("RouteConstraint", ""))));
|
@foreach (var parameter in Model.ParameterDescriptions)
|
||||||
}
|
{
|
||||||
- Default value: @(parameter?.DefaultValue ?? " none")
|
<tr>
|
||||||
</li>
|
<td>@parameter.Name</td>
|
||||||
}
|
<td>@(parameter.Type?.FullName ?? "Unknown Type")</td>
|
||||||
</ul>
|
<td>@parameter.Source.Id</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<h5>Response Formats</h5>
|
||||||
@if (Model.SupportedResponseFormats.Count > 0)
|
@if (Model.SupportedResponseFormats.Count > 0)
|
||||||
{
|
{
|
||||||
<p>Response Formats:</p>
|
<table>
|
||||||
<ul>
|
<thead>
|
||||||
@foreach (var response in Model.SupportedResponseFormats)
|
<tr>
|
||||||
{
|
<td>Data Type</td>
|
||||||
<li>@response.MediaType.ToString() - @response.Formatter.GetType().Name</li>
|
<td>Media Type</td>
|
||||||
}
|
<td>Formatter</td>
|
||||||
</ul>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var response in Model.SupportedResponseFormats)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@Model.ResponseType.FullName</td>
|
||||||
|
<td>@response.MediaType.ToString()</td>
|
||||||
|
<td>@response.Formatter.GetType().Name</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3,8 +3,30 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.api-description h5 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.api-description hr {
|
.api-description hr {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
border-top: 1px solid #D44B4B;
|
border-top: 1px solid #D44B4B;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description thead {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description td {
|
||||||
|
border-bottom: 1px solid #CCCCCC;
|
||||||
|
color: #666666;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.api-description tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
@ -290,6 +290,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false;
|
var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false;
|
||||||
if (apiExplorerIsVisible)
|
if (apiExplorerIsVisible)
|
||||||
{
|
{
|
||||||
|
if (!IsAttributeRoutedAction(actionDescriptor))
|
||||||
|
{
|
||||||
|
// ApiExplorer is only supported on attribute routed actions.
|
||||||
|
throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
|
||||||
|
actionDescriptor.DisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
var apiExplorerActionData = new ApiDescriptionActionData()
|
var apiExplorerActionData = new ApiDescriptionActionData()
|
||||||
{
|
{
|
||||||
GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
|
GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,38 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Routing;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Description
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A metadata description of an input to an API.
|
||||||
|
/// </summary>
|
||||||
public class ApiParameterDescription
|
public class ApiParameterDescription
|
||||||
{
|
{
|
||||||
public bool IsOptional { get; set; }
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ModelMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
public ModelMetadata ModelMetadata { get; set; }
|
public ModelMetadata ModelMetadata { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public ParameterDescriptor ParameterDescriptor { get; set; }
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ApiParameterRouteInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApiParameterRouteInfo RouteInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ApiParameterSource"/>.
|
||||||
|
/// </summary>
|
||||||
public ApiParameterSource Source { get; set; }
|
public ApiParameterSource Source { get; set; }
|
||||||
|
|
||||||
public IEnumerable<IRouteConstraint> Constraints { get; set; }
|
/// <summary>
|
||||||
|
/// Gets or sets the parameter type.
|
||||||
public object DefaultValue { get; set; }
|
/// </summary>
|
||||||
|
|
||||||
public Type Type { get; set; }
|
public Type Type { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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.Routing;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A metadata description of routing information for an <see cref="ApiParameterDescription"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ApiParameterRouteInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the set of <see cref="IRouteConstraint"/> objects for the parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Route constraints are only applied when a value is bound from a URL's path. See
|
||||||
|
/// <see cref="ApiParameterDescription.Source"/> for the data source considered.
|
||||||
|
/// </remarks>
|
||||||
|
public IEnumerable<IRouteConstraint> Constraints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default value for the parameter.
|
||||||
|
/// </summary>
|
||||||
|
public object DefaultValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether not a parameter is considered optional by routing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// An optional parameter is considered optional by the routing system. This does not imply
|
||||||
|
/// that the parameter is considered optional by the action.
|
||||||
|
///
|
||||||
|
/// If the parameter uses <see cref="ApiParameterSource.ModelBinding"/> for the value of
|
||||||
|
/// <see cref="ApiParameterDescription.Source"/> then the value may also come from the
|
||||||
|
/// URL query string or form data.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,130 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Description
|
namespace Microsoft.AspNet.Mvc.Description
|
||||||
{
|
{
|
||||||
// This is a placeholder - see #886
|
/// <summary>
|
||||||
public enum ApiParameterSource
|
/// A metadata description of the source of an <see cref="ApiParameterDescription"/> for an HTTP request.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("Source: {DisplayName}")]
|
||||||
|
public class ApiParameterSource : IEquatable<ApiParameterSource>
|
||||||
{
|
{
|
||||||
Body,
|
/// <summary>
|
||||||
Query,
|
/// An <see cref="ApiParameterSource"/> for the request body.
|
||||||
Path
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Body = new ApiParameterSource(
|
||||||
|
"Body",
|
||||||
|
Resources.ApiParameterSource_Body);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for a custom model binder (unknown data source).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Custom = new ApiParameterSource(
|
||||||
|
"Custom",
|
||||||
|
Resources.ApiParameterSource_Custom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for the request form-data.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Form = new ApiParameterSource(
|
||||||
|
"Form",
|
||||||
|
Resources.ApiParameterSource_Form);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for the request headers.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Header = new ApiParameterSource(
|
||||||
|
"Header",
|
||||||
|
Resources.ApiParameterSource_Header);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for a parameter that should be hidden. Used when
|
||||||
|
/// a parameter cannot be set with user input.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Hidden = new ApiParameterSource(
|
||||||
|
"Hidden",
|
||||||
|
Resources.ApiParameterSource_Hidden);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for model binding. Includes form-data, query-string
|
||||||
|
/// and headers from the request.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource ModelBinding = new ApiParameterSource(
|
||||||
|
"ModelBinding",
|
||||||
|
Resources.ApiParameterSource_ModelBinding);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for the request url path.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Path = new ApiParameterSource(
|
||||||
|
"Path",
|
||||||
|
Resources.ApiParameterSource_Path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApiParameterSource"/> for the request query-string.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ApiParameterSource Query = new ApiParameterSource(
|
||||||
|
"Query",
|
||||||
|
Resources.ApiParameterSource_Query);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ApiParameterSource"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The id. Used for comparison.</param>
|
||||||
|
/// <param name="displayName"> The display name.</param>
|
||||||
|
public ApiParameterSource([NotNull] string id, string displayName)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
DisplayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the display name.
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the id.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(ApiParameterSource other)
|
||||||
|
{
|
||||||
|
return other == null ? false : string.Equals(other.Id, Id, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as ApiParameterSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static bool operator ==(ApiParameterSource s1, ApiParameterSource s2)
|
||||||
|
{
|
||||||
|
if (object.ReferenceEquals(s1, null))
|
||||||
|
{
|
||||||
|
return object.ReferenceEquals(s2, null); ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s1.Equals(s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static bool operator !=(ApiParameterSource s1, ApiParameterSource s2)
|
||||||
|
{
|
||||||
|
return !(s1 == s2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -35,8 +34,8 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
IModelMetadataProvider modelMetadataProvider)
|
IModelMetadataProvider modelMetadataProvider)
|
||||||
{
|
{
|
||||||
_formattersProvider = formattersProvider;
|
_formattersProvider = formattersProvider;
|
||||||
_modelMetadataProvider = modelMetadataProvider;
|
|
||||||
_constraintResolver = constraintResolver;
|
_constraintResolver = constraintResolver;
|
||||||
|
_modelMetadataProvider = modelMetadataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -81,7 +80,8 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
|
||||||
var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List<TemplatePart>();
|
var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List<TemplatePart>();
|
||||||
|
|
||||||
GetParameters(apiDescription, action.Parameters, templateParameters);
|
var parameterContext = new ApiParameterContext(_modelMetadataProvider, action, templateParameters);
|
||||||
|
apiDescription.ParameterDescriptions.AddRange(GetParameters(parameterContext));
|
||||||
|
|
||||||
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
|
||||||
|
|
||||||
|
|
@ -124,43 +124,90 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
return apiDescription;
|
return apiDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetParameters(
|
private IList<ApiParameterDescription> GetParameters(ApiParameterContext context)
|
||||||
ApiDescription apiDescription,
|
|
||||||
IList<ParameterDescriptor> parameterDescriptors,
|
|
||||||
IList<TemplatePart> templateParameters)
|
|
||||||
{
|
{
|
||||||
if (parameterDescriptors != null)
|
// First, get parameters from the model-binding/parameter-binding side of the world.
|
||||||
|
if (context.ActionDescriptor.Parameters != null)
|
||||||
{
|
{
|
||||||
foreach (var parameter in parameterDescriptors)
|
foreach (var actionParameter in context.ActionDescriptor.Parameters)
|
||||||
{
|
{
|
||||||
// Process together parameters that appear on the path template and on the
|
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
|
||||||
// action descriptor and do not come from the body.
|
visitor.WalkParameter();
|
||||||
TemplatePart templateParameter = null;
|
}
|
||||||
if (parameter.BinderMetadata as IFormatterBinderMetadata == null)
|
}
|
||||||
{
|
|
||||||
templateParameter = templateParameters
|
|
||||||
.FirstOrDefault(p => p.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (templateParameter != null)
|
for (var i = context.Results.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
// Remove any 'hidden' parameters. These are things that can't come from user input,
|
||||||
|
// so they aren't worth showing.
|
||||||
|
if (context.Results[i].Source == ApiParameterSource.Hidden)
|
||||||
|
{
|
||||||
|
context.Results.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we want to join up any route parameters with those discovered from the action's parameters.
|
||||||
|
var routeParameters = new Dictionary<string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var routeParameter in context.RouteParameters)
|
||||||
|
{
|
||||||
|
routeParameters.Add(routeParameter.Name, CreateRouteInfo(routeParameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var parameter in context.Results)
|
||||||
|
{
|
||||||
|
if (parameter.Source == ApiParameterSource.Path ||
|
||||||
|
parameter.Source == ApiParameterSource.ModelBinding ||
|
||||||
|
parameter.Source == ApiParameterSource.Custom)
|
||||||
|
{
|
||||||
|
ApiParameterRouteInfo routeInfo;
|
||||||
|
if (routeParameters.TryGetValue(parameter.Name, out routeInfo))
|
||||||
|
{
|
||||||
|
parameter.RouteInfo = routeInfo;
|
||||||
|
routeParameters.Remove(parameter.Name);
|
||||||
|
|
||||||
|
if (parameter.Source == ApiParameterSource.ModelBinding &&
|
||||||
|
!parameter.RouteInfo.IsOptional)
|
||||||
{
|
{
|
||||||
templateParameters.Remove(templateParameter);
|
// If we didn't see any information about the parameter, but we have
|
||||||
|
// a route parameter that matches, let's switch it to path.
|
||||||
|
parameter.Source = ApiParameterSource.Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apiDescription.ParameterDescriptions.Add(GetParameter(parameter, templateParameter));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (templateParameters.Count > 0)
|
// Lastly, create a parameter representation for each route parameter that did not find
|
||||||
|
// a partner.
|
||||||
|
foreach (var routeParameter in routeParameters)
|
||||||
{
|
{
|
||||||
// Process parameters that only appear on the path template if any.
|
context.Results.Add(new ApiParameterDescription()
|
||||||
foreach (var templateParameter in templateParameters)
|
|
||||||
{
|
{
|
||||||
var parameterDescription =
|
Name = routeParameter.Key,
|
||||||
GetParameter(parameterDescriptor: null, templateParameter: templateParameter);
|
RouteInfo = routeParameter.Value,
|
||||||
apiDescription.ParameterDescriptions.Add(parameterDescription);
|
Source = ApiParameterSource.Path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter)
|
||||||
|
{
|
||||||
|
var constraints = new List<IRouteConstraint>();
|
||||||
|
if (routeParameter.InlineConstraints != null)
|
||||||
|
{
|
||||||
|
foreach (var constraint in routeParameter.InlineConstraints)
|
||||||
|
{
|
||||||
|
constraints.Add(_constraintResolver.ResolveConstraint(constraint.Constraint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new ApiParameterRouteInfo()
|
||||||
|
{
|
||||||
|
Constraints = constraints,
|
||||||
|
DefaultValue = routeParameter.DefaultValue,
|
||||||
|
IsOptional = routeParameter.IsOptional || routeParameter.DefaultValue != null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetHttpMethods(ControllerActionDescriptor action)
|
private IEnumerable<string> GetHttpMethods(ControllerActionDescriptor action)
|
||||||
|
|
@ -216,117 +263,6 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
return string.Join("/", segments);
|
return string.Join("/", segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApiParameterDescription GetParameter(
|
|
||||||
ParameterDescriptor parameterDescriptor,
|
|
||||||
TemplatePart templateParameter)
|
|
||||||
{
|
|
||||||
// This is a placeholder based on currently available functionality for parameters. See #886.
|
|
||||||
ApiParameterDescription parameterDescription = null;
|
|
||||||
|
|
||||||
if (templateParameter != null && parameterDescriptor == null)
|
|
||||||
{
|
|
||||||
// The parameter is part of the route template but not part of the ActionDescriptor.
|
|
||||||
|
|
||||||
// For now if a parameter is part of the template we will asume its value comes from the path.
|
|
||||||
// We will be more accurate when we implement #886.
|
|
||||||
parameterDescription = CreateParameterFromTemplate(templateParameter);
|
|
||||||
}
|
|
||||||
else if (templateParameter != null && parameterDescriptor != null)
|
|
||||||
{
|
|
||||||
// The parameter is part of the route template and part of the ActionDescriptor.
|
|
||||||
parameterDescription = CreateParameterFromTemplateAndParameterDescriptor(
|
|
||||||
templateParameter,
|
|
||||||
parameterDescriptor);
|
|
||||||
}
|
|
||||||
else if (templateParameter == null && parameterDescriptor != null)
|
|
||||||
{
|
|
||||||
// The parameter is part of the ActionDescriptor but is not part of the route template.
|
|
||||||
parameterDescription = CreateParameterFromParameterDescriptor(parameterDescriptor);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We will never call this method with templateParameter == null && parameterDescriptor == null
|
|
||||||
Debug.Assert(parameterDescriptor != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameterDescription.Type != null)
|
|
||||||
{
|
|
||||||
parameterDescription.ModelMetadata = _modelMetadataProvider.GetMetadataForType(
|
|
||||||
modelAccessor: null,
|
|
||||||
modelType: parameterDescription.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameterDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ApiParameterDescription CreateParameterFromParameterDescriptor(ParameterDescriptor parameter)
|
|
||||||
{
|
|
||||||
var resourceParameter = new ApiParameterDescription
|
|
||||||
{
|
|
||||||
Name = parameter.Name,
|
|
||||||
ParameterDescriptor = parameter,
|
|
||||||
Type = parameter.ParameterType,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (parameter.BinderMetadata as IFormatterBinderMetadata != null)
|
|
||||||
{
|
|
||||||
resourceParameter.Source = ApiParameterSource.Body;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resourceParameter.Source = ApiParameterSource.Query;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ApiParameterDescription CreateParameterFromTemplateAndParameterDescriptor(
|
|
||||||
TemplatePart templateParameter,
|
|
||||||
ParameterDescriptor parameter)
|
|
||||||
{
|
|
||||||
var resourceParameter = new ApiParameterDescription
|
|
||||||
{
|
|
||||||
Source = ApiParameterSource.Path,
|
|
||||||
IsOptional = IsOptionalParameter(templateParameter),
|
|
||||||
Name = parameter.Name,
|
|
||||||
ParameterDescriptor = parameter,
|
|
||||||
Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
|
|
||||||
DefaultValue = templateParameter.DefaultValue,
|
|
||||||
Type = parameter.ParameterType,
|
|
||||||
};
|
|
||||||
|
|
||||||
return resourceParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<IRouteConstraint> GetConstraints(
|
|
||||||
IInlineConstraintResolver constraintResolver,
|
|
||||||
IEnumerable<InlineConstraint> constraints)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
constraints
|
|
||||||
.Select(c => constraintResolver.ResolveConstraint(c.Constraint))
|
|
||||||
.Where(c => c != null)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsOptionalParameter(TemplatePart templateParameter)
|
|
||||||
{
|
|
||||||
return templateParameter.IsOptional || templateParameter.DefaultValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ApiParameterDescription CreateParameterFromTemplate(TemplatePart templateParameter)
|
|
||||||
{
|
|
||||||
return new ApiParameterDescription
|
|
||||||
{
|
|
||||||
Source = ApiParameterSource.Path,
|
|
||||||
IsOptional = IsOptionalParameter(templateParameter),
|
|
||||||
Name = templateParameter.Name,
|
|
||||||
ParameterDescriptor = null,
|
|
||||||
Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
|
|
||||||
DefaultValue = templateParameter.DefaultValue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private IReadOnlyList<ApiResponseFormat> GetResponseFormats(
|
private IReadOnlyList<ApiResponseFormat> GetResponseFormats(
|
||||||
ControllerActionDescriptor action,
|
ControllerActionDescriptor action,
|
||||||
IApiResponseMetadataProvider[] responseMetadataAttributes,
|
IApiResponseMetadataProvider[] responseMetadataAttributes,
|
||||||
|
|
@ -442,7 +378,7 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
|
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
|
||||||
// for a filter that implements IApiResponseMetadataProvider.
|
// while searching for a filter that implements IApiResponseMetadataProvider.
|
||||||
//
|
//
|
||||||
// The workaround for that is to implement the metadata interface on the IFilterFactory.
|
// The workaround for that is to implement the metadata interface on the IFilterFactory.
|
||||||
return action.FilterDescriptors
|
return action.FilterDescriptors
|
||||||
|
|
@ -450,5 +386,333 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
.OfType<IApiResponseMetadataProvider>()
|
.OfType<IApiResponseMetadataProvider>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ApiParameterContext
|
||||||
|
{
|
||||||
|
public ApiParameterContext(
|
||||||
|
IModelMetadataProvider metadataProvider,
|
||||||
|
ControllerActionDescriptor actionDescriptor,
|
||||||
|
IReadOnlyList<TemplatePart> routeParameters)
|
||||||
|
{
|
||||||
|
MetadataProvider = metadataProvider;
|
||||||
|
ActionDescriptor = actionDescriptor;
|
||||||
|
RouteParameters = routeParameters;
|
||||||
|
|
||||||
|
Results = new List<ApiParameterDescription>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerActionDescriptor ActionDescriptor { get; }
|
||||||
|
|
||||||
|
public IModelMetadataProvider MetadataProvider { get; }
|
||||||
|
|
||||||
|
public IList<ApiParameterDescription> Results { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<TemplatePart> RouteParameters { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PseudoModelBindingVisitor
|
||||||
|
{
|
||||||
|
public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
Parameter = parameter;
|
||||||
|
|
||||||
|
Visited = new HashSet<PropertyKey>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiParameterContext Context { get; }
|
||||||
|
|
||||||
|
public ParameterDescriptor Parameter { get; }
|
||||||
|
|
||||||
|
// Avoid infinite recursion by tracking properties.
|
||||||
|
private HashSet<PropertyKey> Visited { get; }
|
||||||
|
|
||||||
|
public void WalkParameter()
|
||||||
|
{
|
||||||
|
var modelMetadata = Context.MetadataProvider.GetMetadataForParameter(
|
||||||
|
modelAccessor: null,
|
||||||
|
methodInfo: Context.ActionDescriptor.MethodInfo,
|
||||||
|
parameterName: Parameter.Name);
|
||||||
|
|
||||||
|
var binderMetadata = Parameter.BinderMetadata;
|
||||||
|
if (binderMetadata != null)
|
||||||
|
{
|
||||||
|
modelMetadata.BinderMetadata = binderMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameProvider = binderMetadata as IModelNameProvider;
|
||||||
|
if (nameProvider != null && nameProvider.Name != null)
|
||||||
|
{
|
||||||
|
modelMetadata.BinderModelName = nameProvider.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to find a binding source for the parameter
|
||||||
|
//
|
||||||
|
// The default is ModelBinding (aka all default value providers)
|
||||||
|
var source = ApiParameterSource.ModelBinding;
|
||||||
|
if (!Visit(modelMetadata, source, containerName: string.Empty))
|
||||||
|
{
|
||||||
|
// If we get here, then it means we didn't find a match for any of the model. This means that it's
|
||||||
|
// likely 'model-bound' in the traditional MVC sense (formdata + query string + route data) and
|
||||||
|
// doesn't use any IBinderMetadata.
|
||||||
|
//
|
||||||
|
// Add a single 'default' parameter description for the model.
|
||||||
|
Context.Results.Add(CreateResult(modelMetadata, source, containerName: string.Empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visits a node in a model, and attempts to create <see cref="ApiParameterDescription"/> for any
|
||||||
|
/// model properties where we can definitely compute an answer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelMetadata">The metadata for the model.</param>
|
||||||
|
/// <param name="ambientSource">The <see cref="ApiParameterSource"/> from the ambient context.</param>
|
||||||
|
/// <param name="containerName">The current name prefix (to prepend to property names).</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the set of <see cref="ApiParameterDescription"/> objects were created for the model.
|
||||||
|
/// <c>false</c> if no <see cref="ApiParameterDescription"/> objects were created for the model.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Its the reponsibility of this method to create a parameter description for ALL of the current model
|
||||||
|
/// or NONE of it. If a parameter description is created for ANY sub-properties of the model, then a parameter
|
||||||
|
/// description will be created for ALL of them.
|
||||||
|
/// </remarks>
|
||||||
|
private bool Visit(ModelMetadata modelMetadata, ApiParameterSource ambientSource, string containerName)
|
||||||
|
{
|
||||||
|
ApiParameterSource source;
|
||||||
|
if (GetSource(modelMetadata, out source))
|
||||||
|
{
|
||||||
|
// We have a definite answer for this model. This is a greedy source like
|
||||||
|
// [FromBody] so there's no need to consider properties.
|
||||||
|
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any property which is a leaf node, we don't want to keep traversing:
|
||||||
|
//
|
||||||
|
// 1) Collections - while it's possible to have binder attributes on the inside of a collection,
|
||||||
|
// it hardly seems useful, and would result in some very wierd binding.
|
||||||
|
//
|
||||||
|
// 2) Simple Types - These are generally part of the .net framework - primitives, or types which have a
|
||||||
|
// type converter from string.
|
||||||
|
//
|
||||||
|
// 3) Types with no properties. Obviously nothing to explore there.
|
||||||
|
//
|
||||||
|
if (modelMetadata.IsCollectionType ||
|
||||||
|
!modelMetadata.IsComplexType ||
|
||||||
|
!modelMetadata.Properties.Any())
|
||||||
|
{
|
||||||
|
if (source == null || source == ambientSource)
|
||||||
|
{
|
||||||
|
// If it's a leaf node, and we have no new source then we don't know how to bind this.
|
||||||
|
// Return without creating any parameters, so that this can be included in the parent model.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We found a new source, and this model has no properties. This is probabaly
|
||||||
|
// a simple type with an attribute like [FromQuery].
|
||||||
|
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will come from composite model binding - so investigate what's going on with each property.
|
||||||
|
//
|
||||||
|
// Basically once we find something that we know how to bind, we want to treat all properties at that
|
||||||
|
// level (and higher levels) as separate parameters.
|
||||||
|
//
|
||||||
|
// Ex:
|
||||||
|
//
|
||||||
|
// public IActionResult PlaceOrder(OrderDTO order) {...}
|
||||||
|
//
|
||||||
|
// public class OrderDTO
|
||||||
|
// {
|
||||||
|
// public int AccountId { get; set; }
|
||||||
|
//
|
||||||
|
// [FromBody]
|
||||||
|
// public Order { get; set; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This should result in two parameters:
|
||||||
|
//
|
||||||
|
// AccountId - source: Any
|
||||||
|
// Order - source: Body
|
||||||
|
//
|
||||||
|
|
||||||
|
var propertyCount = 0;
|
||||||
|
var unboundProperties = new HashSet<ModelMetadata>();
|
||||||
|
|
||||||
|
// We don't want to append the **parameter** name when building a model name.
|
||||||
|
var newContainerName = containerName;
|
||||||
|
if (modelMetadata.ContainerType != null)
|
||||||
|
{
|
||||||
|
newContainerName = GetName(containerName, modelMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var propertyMetadata in modelMetadata.Properties)
|
||||||
|
{
|
||||||
|
propertyCount++;
|
||||||
|
var key = new PropertyKey(propertyMetadata, source);
|
||||||
|
|
||||||
|
if (Visited.Add(key))
|
||||||
|
{
|
||||||
|
if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName))
|
||||||
|
{
|
||||||
|
unboundProperties.Add(propertyMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unboundProperties.Add(propertyMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unboundProperties.Count == propertyCount)
|
||||||
|
{
|
||||||
|
if (source == null || source == ambientSource)
|
||||||
|
{
|
||||||
|
// No properties were bound and we didn't find a new source, let the caller handle it.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We found a new source, and didn't create a result for any of the properties yet,
|
||||||
|
// so create a result for the current object.
|
||||||
|
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This model was only partially bound, so create a result for all the other properties
|
||||||
|
foreach (var property in unboundProperties)
|
||||||
|
{
|
||||||
|
// Create a 'default' description for each property
|
||||||
|
Context.Results.Add(CreateResult(property, source ?? ambientSource, newContainerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiParameterDescription CreateResult(
|
||||||
|
ModelMetadata metadata,
|
||||||
|
ApiParameterSource source,
|
||||||
|
string containerName)
|
||||||
|
{
|
||||||
|
return new ApiParameterDescription()
|
||||||
|
{
|
||||||
|
ModelMetadata = metadata,
|
||||||
|
Name = GetName(containerName, metadata),
|
||||||
|
Source = source,
|
||||||
|
Type = metadata.ModelType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetName(string containerName, ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(metadata.BinderModelName))
|
||||||
|
{
|
||||||
|
// Name was explicitly provided
|
||||||
|
return metadata.BinderModelName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ModelBindingHelper.CreatePropertyModelName(containerName, metadata.PropertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This isn't extensible right now.
|
||||||
|
//
|
||||||
|
// Returns true if the source is greedy (means to stop exploring the model)
|
||||||
|
// Returns false if the source in unknown or known but not greedy (like [FromQuery])
|
||||||
|
private static bool GetSource(ModelMetadata metadata, out ApiParameterSource source)
|
||||||
|
{
|
||||||
|
if (metadata.BinderMetadata == null)
|
||||||
|
{
|
||||||
|
// There's nothing we can figure out.
|
||||||
|
source = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata.BinderMetadata is IFormatterBinderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Body;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (metadata.BinderMetadata is IHeaderBinderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Header;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (metadata.BinderMetadata is IServiceActivatorBinderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Hidden;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (metadata.BinderMetadata is IRouteDataValueProviderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (metadata.BinderMetadata is IQueryValueProviderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Query;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (metadata.BinderMetadata is IFormDataValueProviderMetadata)
|
||||||
|
{
|
||||||
|
source = ApiParameterSource.Form;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var binderTypeMetadata = metadata.BinderMetadata as IBinderTypeProviderMetadata;
|
||||||
|
if (binderTypeMetadata != null && binderTypeMetadata.BinderType != null)
|
||||||
|
{
|
||||||
|
// This provides it's own model binder, so we can't really make a good
|
||||||
|
// estimate of where it comes from.
|
||||||
|
source = ApiParameterSource.Custom;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're out of cases we know how to handle.
|
||||||
|
source = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PropertyKey
|
||||||
|
{
|
||||||
|
public readonly Type ContainerType;
|
||||||
|
|
||||||
|
public readonly string PropertyName;
|
||||||
|
|
||||||
|
public readonly ApiParameterSource Source;
|
||||||
|
|
||||||
|
public PropertyKey(ModelMetadata metadata, ApiParameterSource source)
|
||||||
|
{
|
||||||
|
ContainerType = metadata.ContainerType;
|
||||||
|
PropertyName = metadata.PropertyName;
|
||||||
|
Source = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PropertyKeyEqualityComparer : IEqualityComparer<PropertyKey>
|
||||||
|
{
|
||||||
|
public bool Equals(PropertyKey x, PropertyKey y)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
x.ContainerType == y.ContainerType &&
|
||||||
|
x.PropertyName == y.PropertyName &&
|
||||||
|
x.Source == y.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(PropertyKey obj)
|
||||||
|
{
|
||||||
|
return obj.ContainerType.GetHashCode() ^ obj.PropertyName.GetHashCode() ^ obj.Source.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +246,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
StringComparison.OrdinalIgnoreCase);
|
StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string CreatePropertyModelName(string prefix, string propertyName)
|
public static string CreatePropertyModelName(string prefix, string propertyName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(prefix))
|
if (string.IsNullOrEmpty(prefix))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1562,6 +1562,150 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ResponseCache_SpecifyDuration"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ResponseCache_SpecifyDuration"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Body
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Body
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Body"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Body
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Body()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Body");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Custom
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Custom"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Custom()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Custom");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Header
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Header"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Header()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Header");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hidden
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Hidden
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Hidden"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hidden
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Hidden()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ModelBinding
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_ModelBinding
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_ModelBinding"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ModelBinding
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_ModelBinding()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_ModelBinding");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Path
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Path"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Path()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Path");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Query
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Query"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Query()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Query");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Form
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiParameterSource_Form
|
||||||
|
{
|
||||||
|
get { return GetString("ApiParameterSource_Form"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Form
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiParameterSource_Form()
|
||||||
|
{
|
||||||
|
return GetString("ApiParameterSource_Form");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiExplorer_UnsupportedAction
|
||||||
|
{
|
||||||
|
get { return GetString("ApiExplorer_UnsupportedAction"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiExplorer_UnsupportedAction(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ApiExplorer_UnsupportedAction"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -418,4 +418,31 @@
|
||||||
<data name="ResponseCache_SpecifyDuration" xml:space="preserve">
|
<data name="ResponseCache_SpecifyDuration" xml:space="preserve">
|
||||||
<value>If the '{0}' property is not set to true, '{1}' property must be specified.</value>
|
<value>If the '{0}' property is not set to true, '{1}' property must be specified.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ApiParameterSource_Body" xml:space="preserve">
|
||||||
|
<value>Body</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Custom" xml:space="preserve">
|
||||||
|
<value>Custom</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Header" xml:space="preserve">
|
||||||
|
<value>Header</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Hidden" xml:space="preserve">
|
||||||
|
<value>Hidden</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_ModelBinding" xml:space="preserve">
|
||||||
|
<value>ModelBinding</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Path" xml:space="preserve">
|
||||||
|
<value>Path</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Query" xml:space="preserve">
|
||||||
|
<value>Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiParameterSource_Form" xml:space="preserve">
|
||||||
|
<value>Form</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiExplorer_UnsupportedAction" xml:space="preserve">
|
||||||
|
<value>The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
|
@ -24,5 +26,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
|
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool IsCollectionType(Type type)
|
||||||
|
{
|
||||||
|
if (type == typeof(string))
|
||||||
|
{
|
||||||
|
// Even though string implements IEnumerable, we don't really think of it
|
||||||
|
// as a collection for the purposes of model binding.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need to look for IEnumerable, because IEnumerable<T> extends it.
|
||||||
|
return typeof(IEnumerable).IsAssignableFrom(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +137,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual bool HideSurroundingHtml { get; set; }
|
public virtual bool HideSurroundingHtml { get; set; }
|
||||||
|
|
||||||
|
public virtual bool IsCollectionType
|
||||||
|
{
|
||||||
|
get { return TypeHelper.IsCollectionType(ModelType); }
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool IsComplexType
|
public virtual bool IsComplexType
|
||||||
{
|
{
|
||||||
get { return !TypeHelper.HasStringConverter(ModelType); }
|
get { return !TypeHelper.HasStringConverter(ModelType); }
|
||||||
|
|
|
||||||
|
|
@ -1092,6 +1092,24 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))]
|
||||||
|
[InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))]
|
||||||
|
public void ApiExplorer_ThrowsForContentionalRouting(string actionName, Type type)
|
||||||
|
{
|
||||||
|
var expected = string.Format(
|
||||||
|
"The action '{0}.{1}' has ApiExplorer enabled, but is using conventional routing. " +
|
||||||
|
"Only actions which use attribute routing support ApiExplorer.",
|
||||||
|
type.FullName, actionName);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider(type.GetTypeInfo());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetDescriptors());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies the sequence of conventions running
|
// Verifies the sequence of conventions running
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ApplyConventions_RunsInOrderOfDecreasingScope()
|
public void ApplyConventions_RunsInOrderOfDecreasingScope()
|
||||||
|
|
@ -1743,29 +1761,34 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
public int Name { get; set; }
|
public int Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
private class ApiExplorerNotVisibleController
|
private class ApiExplorerNotVisibleController
|
||||||
{
|
{
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings()]
|
[ApiExplorerSettings()]
|
||||||
private class ApiExplorerVisibleController
|
private class ApiExplorerVisibleController
|
||||||
{
|
{
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
private class ApiExplorerExplicitlyNotVisibleController
|
private class ApiExplorerExplicitlyNotVisibleController
|
||||||
{
|
{
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
private class ApiExplorerExplicitlyNotVisibleOnActionController
|
private class ApiExplorerExplicitlyNotVisibleOnActionController
|
||||||
{
|
{
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
private class ApiExplorerVisibilityOverrideController
|
private class ApiExplorerVisibilityOverrideController
|
||||||
{
|
{
|
||||||
|
|
@ -1775,25 +1798,28 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
public void Create() { }
|
public void Create() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings(GroupName = "Store")]
|
[ApiExplorerSettings(GroupName = "Store")]
|
||||||
private class ApiExplorerNameOnControllerController
|
private class ApiExplorerNameOnControllerController
|
||||||
{
|
{
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
private class ApiExplorerNameOnActionController
|
private class ApiExplorerNameOnActionController
|
||||||
{
|
{
|
||||||
[ApiExplorerSettings(GroupName = "Blog")]
|
[ApiExplorerSettings(GroupName = "Blog")]
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings()]
|
[ApiExplorerSettings()]
|
||||||
private class ApiExplorerNoNameController
|
private class ApiExplorerNoNameController
|
||||||
{
|
{
|
||||||
public void Edit() { }
|
public void Edit() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("AttributeRouting/IsRequired/ForApiExplorer")]
|
||||||
[ApiExplorerSettings(GroupName = "Store")]
|
[ApiExplorerSettings(GroupName = "Store")]
|
||||||
private class ApiExplorerNameOverrideController
|
private class ApiExplorerNameOverrideController
|
||||||
{
|
{
|
||||||
|
|
@ -1851,5 +1877,22 @@ namespace Microsoft.AspNet.Mvc.Test
|
||||||
private class ConstraintAttribute : Attribute, IActionConstraintMetadata
|
private class ConstraintAttribute : Attribute, IActionConstraintMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(GroupName = "Default")]
|
||||||
|
private class ApiExplorerEnabledConventionalRoutedController : Controller
|
||||||
|
{
|
||||||
|
public void A()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
private class ApiExplorerEnabledActionConventionalRoutedController : Controller
|
||||||
|
{
|
||||||
|
[ApiExplorerSettings(GroupName = "Default")]
|
||||||
|
public void A()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -110,49 +111,6 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
Assert.Single(descriptions, d => d.HttpMethod == "GET");
|
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",
|
|
||||||
ParameterType = typeof(int),
|
|
||||||
},
|
|
||||||
new ParameterDescriptor()
|
|
||||||
{
|
|
||||||
BinderMetadata = new FromBodyAttribute(),
|
|
||||||
Name = "username",
|
|
||||||
ParameterType = 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.False(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("api/products/{id}", false, null, null)]
|
[InlineData("api/products/{id}", false, null, null)]
|
||||||
[InlineData("api/products/{id?}", true, null, null)]
|
[InlineData("api/products/{id?}", true, null, null)]
|
||||||
|
|
@ -181,22 +139,21 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal(ApiParameterSource.Path, parameter.Source);
|
Assert.Equal(ApiParameterSource.Path, parameter.Source);
|
||||||
Assert.Equal(isOptional, parameter.IsOptional);
|
Assert.Equal(isOptional, parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("id", parameter.Name);
|
Assert.Equal("id", parameter.Name);
|
||||||
Assert.Null(parameter.ParameterDescriptor);
|
|
||||||
|
|
||||||
if (constraintType != null)
|
if (constraintType != null)
|
||||||
{
|
{
|
||||||
Assert.IsType(constraintType, Assert.Single(parameter.Constraints));
|
Assert.IsType(constraintType, Assert.Single(parameter.RouteInfo.Constraints));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue != null)
|
if (defaultValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(defaultValue, parameter.DefaultValue);
|
Assert.Equal(defaultValue, parameter.RouteInfo.DefaultValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Null(parameter.DefaultValue);
|
Assert.Null(parameter.RouteInfo.DefaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,15 +174,10 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
object defaultValue)
|
object defaultValue)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action = CreateActionDescriptor();
|
var action = CreateActionDescriptor(nameof(FromRouting));
|
||||||
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
||||||
|
|
||||||
var parameterDescriptor = new ParameterDescriptor
|
var parameterDescriptor = action.Parameters[0];
|
||||||
{
|
|
||||||
Name = "id",
|
|
||||||
ParameterType = typeof(int),
|
|
||||||
};
|
|
||||||
action.Parameters = new List<ParameterDescriptor> { parameterDescriptor };
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptions = GetApiDescriptions(action);
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
@ -235,82 +187,76 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal(ApiParameterSource.Path, parameter.Source);
|
Assert.Equal(ApiParameterSource.Path, parameter.Source);
|
||||||
Assert.Equal(isOptional, parameter.IsOptional);
|
Assert.Equal(isOptional, parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("id", parameter.Name);
|
Assert.Equal("id", parameter.Name);
|
||||||
Assert.Equal(parameterDescriptor, parameter.ParameterDescriptor);
|
|
||||||
|
|
||||||
if (constraintType != null)
|
if (constraintType != null)
|
||||||
{
|
{
|
||||||
Assert.IsType(constraintType, Assert.Single(parameter.Constraints));
|
Assert.IsType(constraintType, Assert.Single(parameter.RouteInfo.Constraints));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue != null)
|
if (defaultValue != null)
|
||||||
{
|
{
|
||||||
Assert.Equal(defaultValue, parameter.DefaultValue);
|
Assert.Equal(defaultValue, parameter.RouteInfo.DefaultValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Null(parameter.DefaultValue);
|
Assert.Null(parameter.RouteInfo.DefaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only a parameter which comes from a route or model binding or unknown should
|
||||||
|
// include route info.
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("api/products/{id}", false, null, null)]
|
[InlineData("api/products/{id}", nameof(FromBody), "Body")]
|
||||||
[InlineData("api/products/{id?}", true, null, null)]
|
[InlineData("api/products/{id}", nameof(FromHeader), "Header")]
|
||||||
[InlineData("api/products/{id=5}", true, null, "5")]
|
public void GetApiDescription_ParameterDescription_DoesNotIncludeRouteInfo(
|
||||||
[InlineData("api/products/{id:int}", false, typeof(IntRouteConstraint), null)]
|
|
||||||
[InlineData("api/products/{id:int?}", true, typeof(IntRouteConstraint), null)]
|
|
||||||
[InlineData("api/products/{id:int=5}", true, typeof(IntRouteConstraint), "5")]
|
|
||||||
[InlineData("api/products/{*id}", false, null, null)]
|
|
||||||
[InlineData("api/products/{*id:int}", false, typeof(IntRouteConstraint), null)]
|
|
||||||
[InlineData("api/products/{*id:int=5}", true, typeof(IntRouteConstraint), "5")]
|
|
||||||
public void GetApiDescription_CreatesDifferentParameters_IfParameterDescriptorIsFromBody(
|
|
||||||
string template,
|
string template,
|
||||||
bool isOptional,
|
string methodName,
|
||||||
Type constraintType,
|
string source)
|
||||||
object defaultValue)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action = CreateActionDescriptor();
|
var action = CreateActionDescriptor(methodName);
|
||||||
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
||||||
|
|
||||||
var parameterDescriptor = new ParameterDescriptor
|
|
||||||
{
|
|
||||||
BinderMetadata = new FromBodyAttribute(),
|
|
||||||
Name = "id",
|
|
||||||
ParameterType = typeof(int),
|
|
||||||
};
|
|
||||||
action.Parameters = new List<ParameterDescriptor> { parameterDescriptor };
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptions = GetApiDescriptions(action);
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var description = Assert.Single(descriptions);
|
var description = Assert.Single(descriptions);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
var bodyParameter = Assert.Single(description.ParameterDescriptions, p => p.Source == ApiParameterSource.Body);
|
var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null));
|
||||||
Assert.False(bodyParameter.IsOptional);
|
Assert.Null(id.RouteInfo);
|
||||||
Assert.Equal("id", bodyParameter.Name);
|
}
|
||||||
Assert.Equal(parameterDescriptor, bodyParameter.ParameterDescriptor);
|
|
||||||
|
|
||||||
var pathParameter = Assert.Single(description.ParameterDescriptions, p => p.Source == ApiParameterSource.Path);
|
// Only a parameter which comes from a route or model binding or unknown should
|
||||||
Assert.Equal(isOptional, pathParameter.IsOptional);
|
// include route info. If the source is model binding, we also check if it's an optional
|
||||||
Assert.Equal("id", pathParameter.Name);
|
// parameter, and only change the source if it's a match.
|
||||||
Assert.Null(pathParameter.ParameterDescriptor);
|
[Theory]
|
||||||
|
[InlineData("api/products/{id}", nameof(FromRouting), "Path")]
|
||||||
|
[InlineData("api/products/{id}", nameof(FromModelBinding), "Path")]
|
||||||
|
[InlineData("api/products/{id?}", nameof(FromModelBinding), "ModelBinding")]
|
||||||
|
[InlineData("api/products/{id=5}", nameof(FromModelBinding), "ModelBinding")]
|
||||||
|
[InlineData("api/products/{id}", nameof(FromCustom), "Custom")]
|
||||||
|
public void GetApiDescription_ParameterDescription_IncludesRouteInfo(
|
||||||
|
string template,
|
||||||
|
string methodName,
|
||||||
|
string source)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(methodName);
|
||||||
|
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
||||||
|
|
||||||
if (constraintType != null)
|
// Act
|
||||||
{
|
var descriptions = GetApiDescriptions(action);
|
||||||
Assert.IsType(constraintType, Assert.Single(pathParameter.Constraints));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultValue != null)
|
// Assert
|
||||||
{
|
var description = Assert.Single(descriptions);
|
||||||
Assert.Equal(defaultValue, pathParameter.DefaultValue);
|
var parameters = description.ParameterDescriptions;
|
||||||
}
|
|
||||||
else
|
var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null));
|
||||||
{
|
Assert.NotNull(id.RouteInfo);
|
||||||
Assert.Null(pathParameter.DefaultValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -322,23 +268,16 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
bool expectedOptional)
|
bool expectedOptional)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var action = CreateActionDescriptor();
|
var action = CreateActionDescriptor(nameof(FromRouting));
|
||||||
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
|
||||||
|
|
||||||
var parameterDescriptor = new ParameterDescriptor
|
|
||||||
{
|
|
||||||
Name = "id",
|
|
||||||
ParameterType = typeof(int),
|
|
||||||
};
|
|
||||||
action.Parameters = new List<ParameterDescriptor> { parameterDescriptor };
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptions = GetApiDescriptions(action);
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var description = Assert.Single(descriptions);
|
var description = Assert.Single(descriptions);
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal(expectedOptional, parameter.IsOptional);
|
Assert.Equal(expectedOptional, parameter.RouteInfo.IsOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -380,11 +319,11 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
var description = Assert.Single(descriptions);
|
var description = Assert.Single(descriptions);
|
||||||
var id1 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id1");
|
var id1 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id1");
|
||||||
Assert.Equal(ApiParameterSource.Path, id1.Source);
|
Assert.Equal(ApiParameterSource.Path, id1.Source);
|
||||||
Assert.Empty(id1.Constraints);
|
Assert.Empty(id1.RouteInfo.Constraints);
|
||||||
|
|
||||||
var id2 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id2");
|
var id2 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id2");
|
||||||
Assert.Equal(ApiParameterSource.Path, id2.Source);
|
Assert.Equal(ApiParameterSource.Path, id2.Source);
|
||||||
Assert.IsType<IntRouteConstraint>(Assert.Single(id2.Constraints));
|
Assert.IsType<IntRouteConstraint>(Assert.Single(id2.RouteInfo.Constraints));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -584,6 +523,378 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
Assert.Same(formatters[0], formats[0].Formatter);
|
Assert.Same(formatters[0], formats[0].Formatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_ModelBoundParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProduct));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("product", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromRouteData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsId_Route));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("id", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Path, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromQueryString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsId_Query));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("id", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Query, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProduct_Body));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("product", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Body, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromForm()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProduct_Form));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("product", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Form, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromHeader()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsId_Header));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("id", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Header, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Hidden' parameters are hidden (not returned).
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromServices()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsFormatters_Services));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Empty(description.ParameterDescriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromCustomModelBinder()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProduct_Custom));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("product", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.Custom, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_SourceFromDefault_ModelBinderAttribute_WithoutBinderType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProduct_Default));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Equal("product", parameter.Name);
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, parameter.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_ComplexDTO()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProductChangeDTO));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.ParameterDescriptions.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
|
||||||
|
Assert.Same(ApiParameterSource.Path, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
|
||||||
|
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
|
||||||
|
Assert.Same(ApiParameterSource.Body, product.Source);
|
||||||
|
Assert.Equal(typeof(Product), product.Type);
|
||||||
|
|
||||||
|
var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId");
|
||||||
|
Assert.Same(ApiParameterSource.Header, userId.Source);
|
||||||
|
Assert.Equal(typeof(string), userId.Type);
|
||||||
|
|
||||||
|
var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments");
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, comments.Source);
|
||||||
|
Assert.Equal(typeof(string), comments.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The method under test uses an attribute on the parameter to set a 'default' source
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_ComplexDTO_AmbientValueProviderMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsProductChangeDTO_Query));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.ParameterDescriptions.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
|
||||||
|
Assert.Same(ApiParameterSource.Path, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
|
||||||
|
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
|
||||||
|
Assert.Same(ApiParameterSource.Body, product.Source);
|
||||||
|
Assert.Equal(typeof(Product), product.Type);
|
||||||
|
|
||||||
|
var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId");
|
||||||
|
Assert.Same(ApiParameterSource.Header, userId.Source);
|
||||||
|
Assert.Equal(typeof(string), userId.Type);
|
||||||
|
|
||||||
|
var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments");
|
||||||
|
Assert.Same(ApiParameterSource.Query, comments.Source);
|
||||||
|
Assert.Equal(typeof(string), comments.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_ComplexDTO_AnotherLevel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsOrderDTO));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(4, description.ParameterDescriptions.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
|
||||||
|
Assert.Same(ApiParameterSource.Path, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
|
||||||
|
var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity");
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, quantity.Source);
|
||||||
|
Assert.Equal(typeof(int), quantity.Type);
|
||||||
|
|
||||||
|
var productId = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Id");
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, productId.Source);
|
||||||
|
Assert.Equal(typeof(int), productId.Type);
|
||||||
|
|
||||||
|
var price = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Price");
|
||||||
|
Assert.Same(ApiParameterSource.Query, price.Source);
|
||||||
|
Assert.Equal(typeof(decimal), price.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The method under test uses an attribute on the parameter to set a 'default' source
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_ComplexDTO_AnotherLevel_AmbientValueProviderMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsOrderDTO_Query));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
Assert.Equal(3, description.ParameterDescriptions.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
|
||||||
|
Assert.Same(ApiParameterSource.Path, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
|
||||||
|
var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity");
|
||||||
|
Assert.Same(ApiParameterSource.Query, quantity.Source);
|
||||||
|
Assert.Equal(typeof(int), quantity.Type);
|
||||||
|
|
||||||
|
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
|
||||||
|
Assert.Same(ApiParameterSource.Query, product.Source);
|
||||||
|
Assert.Equal(typeof(OrderProductDTO), product.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_BreaksCycles()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsCycle));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var c = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Same(ApiParameterSource.Query, c.Source);
|
||||||
|
Assert.Equal("C.C", c.Name);
|
||||||
|
Assert.Equal(typeof(Cycle1), c.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_DTOWithCollection()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsHasCollection));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var products = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Same(ApiParameterSource.Query, products.Source);
|
||||||
|
Assert.Equal("Products", products.Name);
|
||||||
|
Assert.Equal(typeof(Product[]), products.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a property/parameter is a collection, we automatically treat it as a leaf-node.
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_DTOWithCollection_ElementsWithBinderMetadataIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsHasCollection_Complex));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var c = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Same(ApiParameterSource.ModelBinding, c.Source);
|
||||||
|
Assert.Equal("c", c.Name);
|
||||||
|
Assert.Equal(typeof(HasCollection_Complex), c.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_RedundentMetadataMergedWithParent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsRedundentMetadata));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var r = Assert.Single(description.ParameterDescriptions);
|
||||||
|
Assert.Same(ApiParameterSource.Query, r.Source);
|
||||||
|
Assert.Equal("r", r.Name);
|
||||||
|
Assert.Equal(typeof(RedundentMetadata), r.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiDescription_ParameterDescription_RedundentMetadata_WithParameterMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = CreateActionDescriptor(nameof(AcceptsPerson));
|
||||||
|
var parameterDescriptor = action.Parameters.Single();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptions = GetApiDescriptions(action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(descriptions);
|
||||||
|
|
||||||
|
var name = Assert.Single(description.ParameterDescriptions, p => p.Name == "Name");
|
||||||
|
Assert.Same(ApiParameterSource.Header, name.Source);
|
||||||
|
Assert.Equal(typeof(string), name.Type);
|
||||||
|
|
||||||
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
|
||||||
|
Assert.Same(ApiParameterSource.Form, id.Source);
|
||||||
|
Assert.Equal(typeof(int), id.Type);
|
||||||
|
}
|
||||||
|
|
||||||
private IReadOnlyList<ApiDescription> GetApiDescriptions(ActionDescriptor action)
|
private IReadOnlyList<ApiDescription> GetApiDescriptions(ActionDescriptor action)
|
||||||
{
|
{
|
||||||
return GetApiDescriptions(action, CreateFormatters());
|
return GetApiDescriptions(action, CreateFormatters());
|
||||||
|
|
@ -602,18 +913,12 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
constraintResolver.Setup(c => c.ResolveConstraint("int"))
|
constraintResolver.Setup(c => c.ResolveConstraint("int"))
|
||||||
.Returns(new IntRouteConstraint());
|
.Returns(new IntRouteConstraint());
|
||||||
|
|
||||||
var modelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
var modelMetadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
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(
|
var provider = new DefaultApiDescriptionProvider(
|
||||||
formattersProvider.Object,
|
formattersProvider.Object,
|
||||||
constraintResolver.Object,
|
constraintResolver.Object,
|
||||||
modelMetadataProvider.Object);
|
modelMetadataProvider);
|
||||||
|
|
||||||
provider.Invoke(context, () => { });
|
provider.Invoke(context, () => { });
|
||||||
return context.Results;
|
return context.Results;
|
||||||
|
|
@ -646,6 +951,18 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
methodName ?? "ReturnsObject",
|
methodName ?? "ReturnsObject",
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
action.Parameters = new List<ParameterDescriptor>();
|
||||||
|
|
||||||
|
foreach (var parameter in action.MethodInfo.GetParameters())
|
||||||
|
{
|
||||||
|
action.Parameters.Add(new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
BinderMetadata = parameter.GetCustomAttributes().OfType<IBinderMetadata>().FirstOrDefault(),
|
||||||
|
Name = parameter.Name,
|
||||||
|
ParameterType = parameter.ParameterType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -699,12 +1016,198 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AcceptsProduct(Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsProduct_Body([FromBody] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsProduct_Form([FromForm] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will show up as source = model binding
|
||||||
|
private void AcceptsProduct_Default([ModelBinder] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will show up as source = unknown
|
||||||
|
private void AcceptsProduct_Custom([ModelBinder(BinderType = typeof(BodyModelBinder))] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsId_Route([FromRoute] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsId_Query([FromQuery] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsId_Header([FromHeader] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsFormatters_Services([FromServices] IOutputFormattersProvider formatters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsProductChangeDTO(ProductChangeDTO dto)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsProductChangeDTO_Query([FromQuery] ProductChangeDTO dto)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsOrderDTO(OrderDTO dto)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsOrderDTO_Query([FromQuery] OrderDTO dto)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsCycle(Cycle1 c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsHasCollection(HasCollection c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsHasCollection_Complex(HasCollection_Complex c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsRedundentMetadata([FromQuery] RedundentMetadata r)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptsPerson([FromForm] Person person)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FromRouting([FromRoute] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FromModelBinding(int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FromCustom([ModelBinder(BinderType = typeof(BodyModelBinder))] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FromHeader([FromHeader] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FromBody([FromBody] int id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private class Product
|
private class Product
|
||||||
{
|
{
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Order
|
private class Order
|
||||||
{
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProductChangeDTO
|
||||||
|
{
|
||||||
|
[FromRoute]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Product Product { get; set; }
|
||||||
|
|
||||||
|
[FromHeader]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
public string Comments { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OrderDTO
|
||||||
|
{
|
||||||
|
[FromRoute]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
public OrderProductDTO Product { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OrderProductDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromQuery]
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Cycle1
|
||||||
|
{
|
||||||
|
public Cycle2 C { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Cycle2
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public Cycle1 C { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HasCollection
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public Product[] Products { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HasCollection_Complex
|
||||||
|
{
|
||||||
|
public Child[] Items { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Child
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RedundentMetadata
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromQuery]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
[FromHeader(Name = "Name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[FromForm]
|
||||||
|
public int Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockFormatter : OutputFormatter
|
private class MockFormatter : OutputFormatter
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.TestHost;
|
using Microsoft.AspNet.TestHost;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -176,9 +177,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal("id", parameter.Name);
|
Assert.Equal("id", parameter.Name);
|
||||||
Assert.False(parameter.IsOptional);
|
Assert.False(parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", parameter.Source);
|
Assert.Equal("Path", parameter.Source);
|
||||||
Assert.Empty(parameter.ConstraintTypes);
|
Assert.Empty(parameter.RouteInfo.ConstraintTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -201,9 +202,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal("integer", parameter.Name);
|
Assert.Equal("integer", parameter.Name);
|
||||||
Assert.False(parameter.IsOptional);
|
Assert.False(parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", parameter.Source);
|
Assert.Equal("Path", parameter.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(parameter.RouteInfo.ConstraintTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -226,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal("parameter", parameter.Name);
|
Assert.Equal("parameter", parameter.Name);
|
||||||
Assert.False(parameter.IsOptional);
|
Assert.False(parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", parameter.Source);
|
Assert.Equal("Path", parameter.Source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,9 +253,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
var parameter = Assert.Single(description.ParameterDescriptions);
|
var parameter = Assert.Single(description.ParameterDescriptions);
|
||||||
Assert.Equal("integer", parameter.Name);
|
Assert.Equal("integer", parameter.Name);
|
||||||
Assert.False(parameter.IsOptional);
|
Assert.False(parameter.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", parameter.Source);
|
Assert.Equal("Path", parameter.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(parameter.RouteInfo.ConstraintTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -281,19 +282,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedRelativePath, description.RelativePath);
|
Assert.Equal(expectedRelativePath, description.RelativePath);
|
||||||
|
|
||||||
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
|
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
|
||||||
Assert.False(month.IsOptional);
|
Assert.False(month.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", month.Source);
|
Assert.Equal("Path", month.Source);
|
||||||
Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes));
|
Assert.Equal("RangeRouteConstraint", Assert.Single(month.RouteInfo.ConstraintTypes));
|
||||||
|
|
||||||
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
|
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
|
||||||
Assert.False(day.IsOptional);
|
Assert.False(day.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", day.Source);
|
Assert.Equal("Path", day.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(day.RouteInfo.ConstraintTypes));
|
||||||
|
|
||||||
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
|
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
|
||||||
Assert.False(year.IsOptional);
|
Assert.False(year.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", year.Source);
|
Assert.Equal("Path", year.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(year.RouteInfo.ConstraintTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -319,19 +320,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedRelativePath, description.RelativePath);
|
Assert.Equal(expectedRelativePath, description.RelativePath);
|
||||||
|
|
||||||
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
|
var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month");
|
||||||
Assert.False(month.IsOptional);
|
Assert.False(month.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", month.Source);
|
Assert.Equal("Path", month.Source);
|
||||||
Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes));
|
Assert.Equal("RangeRouteConstraint", Assert.Single(month.RouteInfo.ConstraintTypes));
|
||||||
|
|
||||||
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
|
var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day");
|
||||||
Assert.True(day.IsOptional);
|
Assert.True(day.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", day.Source);
|
Assert.Equal("ModelBinding", day.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(day.RouteInfo.ConstraintTypes));
|
||||||
|
|
||||||
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
|
var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year");
|
||||||
Assert.True(year.IsOptional);
|
Assert.True(year.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", year.Source);
|
Assert.Equal("ModelBinding", year.Source);
|
||||||
Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes));
|
Assert.Equal("IntRouteConstraint", Assert.Single(year.RouteInfo.ConstraintTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -383,8 +384,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal("ApiExplorerRouteAndPathParametersInformation/Optional/{id}", description.RelativePath);
|
Assert.Equal("ApiExplorerRouteAndPathParametersInformation/Optional/{id}", description.RelativePath);
|
||||||
|
|
||||||
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id");
|
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id");
|
||||||
Assert.True(id.IsOptional);
|
Assert.True(id.RouteInfo.IsOptional);
|
||||||
Assert.Equal("Path", id.Source);
|
Assert.Equal("ModelBinding", id.Source);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -722,6 +723,158 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(formatterType, format.FormatterType);
|
Assert.Equal(formatterType, format.FormatterType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_Parameters_SimpleTypes_Default()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleParameters");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
|
Assert.Equal(2, parameters.Count);
|
||||||
|
|
||||||
|
var i = Assert.Single(parameters, p => p.Name == "i");
|
||||||
|
Assert.Equal(ApiParameterSource.ModelBinding.Id, i.Source);
|
||||||
|
Assert.Equal(typeof(int).FullName, i.Type);
|
||||||
|
|
||||||
|
var s = Assert.Single(parameters, p => p.Name == "s");
|
||||||
|
Assert.Equal(ApiParameterSource.ModelBinding.Id, s.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, s.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_Parameters_SimpleTypes_BinderMetadataOnParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleParametersWithBinderMetadata");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
|
Assert.Equal(2, parameters.Count);
|
||||||
|
|
||||||
|
var i = Assert.Single(parameters, p => p.Name == "i");
|
||||||
|
Assert.Equal(ApiParameterSource.Query.Id, i.Source);
|
||||||
|
Assert.Equal(typeof(int).FullName, i.Type);
|
||||||
|
|
||||||
|
var s = Assert.Single(parameters, p => p.Name == "s");
|
||||||
|
Assert.Equal(ApiParameterSource.Path.Id, s.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, s.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_ParametersSimpleModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleModel");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
|
Assert.Equal(1, parameters.Count);
|
||||||
|
|
||||||
|
var product = Assert.Single(parameters, p => p.Name == "product");
|
||||||
|
Assert.Equal(ApiParameterSource.ModelBinding.Id, product.Source);
|
||||||
|
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_Parameters_SimpleTypes_SimpleModel_FromBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleModelFromBody/5");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
|
Assert.Equal(2, parameters.Count);
|
||||||
|
|
||||||
|
var id = Assert.Single(parameters, p => p.Name == "id");
|
||||||
|
Assert.Equal(ApiParameterSource.Path.Id, id.Source);
|
||||||
|
Assert.Equal(typeof(int).FullName, id.Type);
|
||||||
|
|
||||||
|
var product = Assert.Single(parameters, p => p.Name == "product");
|
||||||
|
Assert.Equal(ApiParameterSource.Body.Id, product.Source);
|
||||||
|
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApiExplorer_Parameters_SimpleTypes_ComplexModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ApiExplorerParameters/ComplexModel");
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var description = Assert.Single(result);
|
||||||
|
var parameters = description.ParameterDescriptions;
|
||||||
|
|
||||||
|
Assert.Equal(6, parameters.Count);
|
||||||
|
|
||||||
|
var customerId = Assert.Single(parameters, p => p.Name == "CustomerId");
|
||||||
|
Assert.Equal(ApiParameterSource.Query.Id, customerId.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, customerId.Type);
|
||||||
|
|
||||||
|
var referrer = Assert.Single(parameters, p => p.Name == "Referrer");
|
||||||
|
Assert.Equal(ApiParameterSource.Header.Id, referrer.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, referrer.Type);
|
||||||
|
|
||||||
|
var quantity = Assert.Single(parameters, p => p.Name == "Details.Quantity");
|
||||||
|
Assert.Equal(ApiParameterSource.Form.Id, quantity.Source);
|
||||||
|
Assert.Equal(typeof(int).FullName, quantity.Type);
|
||||||
|
|
||||||
|
var product = Assert.Single(parameters, p => p.Name == "Details.Product");
|
||||||
|
Assert.Equal(ApiParameterSource.Form.Id, product.Source);
|
||||||
|
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
|
||||||
|
|
||||||
|
var shippingInstructions = Assert.Single(parameters, p => p.Name == "Comments.ShippingInstructions");
|
||||||
|
Assert.Equal(ApiParameterSource.Query.Id, shippingInstructions.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, shippingInstructions.Type);
|
||||||
|
|
||||||
|
var feedback = Assert.Single(parameters, p => p.Name == "Comments.Feedback");
|
||||||
|
Assert.Equal(ApiParameterSource.Form.Id, feedback.Source);
|
||||||
|
Assert.Equal(typeof(string).FullName, feedback.Type);
|
||||||
|
}
|
||||||
|
|
||||||
// Used to serialize data between client and server
|
// Used to serialize data between client and server
|
||||||
private class ApiExplorerData
|
private class ApiExplorerData
|
||||||
{
|
{
|
||||||
|
|
@ -741,15 +894,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
// Used to serialize data between client and server
|
// Used to serialize data between client and server
|
||||||
private class ApiExplorerParameterData
|
private class ApiExplorerParameterData
|
||||||
{
|
{
|
||||||
public bool IsOptional { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ApiExplorerParameterRouteInfo RouteInfo { get; set; }
|
||||||
|
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerParameterRouteInfo
|
||||||
|
{
|
||||||
public string[] ConstraintTypes { get; set; }
|
public string[] ConstraintTypes { get; set; }
|
||||||
|
|
||||||
|
public object DefaultValue { get; set; }
|
||||||
|
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to serialize data between client and server
|
// Used to serialize data between client and server
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if ASPNET50
|
#if ASPNET50
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -84,6 +87,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.False(metadata.HideSurroundingHtml);
|
Assert.False(metadata.HideSurroundingHtml);
|
||||||
Assert.True(metadata.HtmlEncode);
|
Assert.True(metadata.HtmlEncode);
|
||||||
Assert.False(metadata.IsComplexType);
|
Assert.False(metadata.IsComplexType);
|
||||||
|
Assert.False(metadata.IsCollectionType);
|
||||||
Assert.False(metadata.IsNullableValueType);
|
Assert.False(metadata.IsNullableValueType);
|
||||||
Assert.False(metadata.IsReadOnly);
|
Assert.False(metadata.IsReadOnly);
|
||||||
Assert.False(metadata.IsRequired);
|
Assert.False(metadata.IsRequired);
|
||||||
|
|
@ -128,7 +132,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var provider = new EmptyModelMetadataProvider();
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
|
var modelMetadata = new ModelMetadata(
|
||||||
|
provider,
|
||||||
|
containerType: null,
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: type,
|
||||||
|
propertyName: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(modelMetadata.IsComplexType);
|
Assert.False(modelMetadata.IsComplexType);
|
||||||
|
|
@ -145,12 +154,72 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var provider = new EmptyModelMetadataProvider();
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var modelMetadata = new ModelMetadata(provider, null, null, type, null);
|
var modelMetadata = new ModelMetadata(
|
||||||
|
provider,
|
||||||
|
containerType: null,
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: type,
|
||||||
|
propertyName: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(modelMetadata.IsComplexType);
|
Assert.True(modelMetadata.IsComplexType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(object))]
|
||||||
|
[InlineData(typeof(int))]
|
||||||
|
[InlineData(typeof(NonCollectionType))]
|
||||||
|
[InlineData(typeof(string))]
|
||||||
|
public void IsCollectionType_NonCollectionTypes(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelMetadata = new ModelMetadata(
|
||||||
|
provider,
|
||||||
|
containerType: null,
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: type,
|
||||||
|
propertyName: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(modelMetadata.IsCollectionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(int[]))]
|
||||||
|
[InlineData(typeof(List<string>))]
|
||||||
|
[InlineData(typeof(DerivedList))]
|
||||||
|
[InlineData(typeof(IEnumerable))]
|
||||||
|
[InlineData(typeof(IEnumerable<string>))]
|
||||||
|
[InlineData(typeof(Collection<int>))]
|
||||||
|
[InlineData(typeof(Dictionary<object, object>))]
|
||||||
|
public void IsCollectionType_CollectionTypes(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelMetadata = new ModelMetadata(
|
||||||
|
provider,
|
||||||
|
containerType: null,
|
||||||
|
modelAccessor: null,
|
||||||
|
modelType: type,
|
||||||
|
propertyName: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelMetadata.IsCollectionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonCollectionType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedList : List<int>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// IsNullableValueType
|
// IsNullableValueType
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
@ -9,11 +10,11 @@ using Microsoft.AspNet.Mvc.Description;
|
||||||
namespace ApiExplorerWebSite
|
namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action filter that looks up and serializes Api Explorer data for the action.
|
/// A resource filter that looks up and serializes Api Explorer data for the action.
|
||||||
///
|
///
|
||||||
/// This replaces the 'actual' output of the action.
|
/// This replaces the 'actual' output of the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiExplorerDataFilter : ActionFilterAttribute
|
public class ApiExplorerDataFilter : IResourceFilter
|
||||||
{
|
{
|
||||||
private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;
|
private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ namespace ApiExplorerWebSite
|
||||||
_descriptionProvider = descriptionProvider;
|
_descriptionProvider = descriptionProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnActionExecuted(ActionExecutedContext context)
|
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||||
{
|
{
|
||||||
var descriptions = new List<ApiExplorerData>();
|
var descriptions = new List<ApiExplorerData>();
|
||||||
foreach (var group in _descriptionProvider.ApiDescriptionGroups.Items)
|
foreach (var group in _descriptionProvider.ApiDescriptionGroups.Items)
|
||||||
|
|
@ -39,6 +40,11 @@ namespace ApiExplorerWebSite
|
||||||
context.Result = new JsonResult(descriptions);
|
context.Result = new JsonResult(descriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private ApiExplorerData CreateSerializableData(ApiDescription description)
|
private ApiExplorerData CreateSerializableData(ApiDescription description)
|
||||||
{
|
{
|
||||||
var data = new ApiExplorerData()
|
var data = new ApiExplorerData()
|
||||||
|
|
@ -53,13 +59,21 @@ namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
var parameterData = new ApiExplorerParameterData()
|
var parameterData = new ApiExplorerParameterData()
|
||||||
{
|
{
|
||||||
IsOptional = parameter.IsOptional,
|
|
||||||
Name = parameter.Name,
|
Name = parameter.Name,
|
||||||
Source = parameter.Source.ToString(),
|
Source = parameter.Source.Id,
|
||||||
Type = parameter?.Type?.FullName,
|
Type = parameter.Type?.FullName,
|
||||||
ConstraintTypes = parameter?.Constraints?.Select(c => c.GetType().Name).ToArray(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (parameter.RouteInfo != null)
|
||||||
|
{
|
||||||
|
parameterData.RouteInfo = new ApiExplorerParameterRouteInfo()
|
||||||
|
{
|
||||||
|
ConstraintTypes = parameter.RouteInfo.Constraints?.Select(c => c.GetType().Name).ToArray(),
|
||||||
|
DefaultValue = parameter.RouteInfo.DefaultValue,
|
||||||
|
IsOptional = parameter.RouteInfo.IsOptional,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
data.ParameterDescriptions.Add(parameterData);
|
data.ParameterDescriptions.Add(parameterData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,15 +110,23 @@ namespace ApiExplorerWebSite
|
||||||
// Used to serialize data between client and server
|
// Used to serialize data between client and server
|
||||||
private class ApiExplorerParameterData
|
private class ApiExplorerParameterData
|
||||||
{
|
{
|
||||||
public bool IsOptional { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ApiExplorerParameterRouteInfo RouteInfo { get; set; }
|
||||||
|
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
|
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to serialize data between client and server
|
||||||
|
private class ApiExplorerParameterRouteInfo
|
||||||
|
{
|
||||||
public string[] ConstraintTypes { get; set; }
|
public string[] ConstraintTypes { get; set; }
|
||||||
|
|
||||||
|
public object DefaultValue { get; set; }
|
||||||
|
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to serialize data between client and server
|
// Used to serialize data between client and server
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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 ApiExplorerWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("ApiExplorerParameters/[action]")]
|
||||||
|
public class ApiExplorerParametersController : Controller
|
||||||
|
{
|
||||||
|
public void SimpleParameters(int i, string s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SimpleParametersWithBinderMetadata([FromQuery] int i, [FromRoute] string s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SimpleModel(Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("{id}")]
|
||||||
|
public void SimpleModelFromBody(int id, [FromBody] Product product)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ComplexModel([FromQuery] OrderDTO order)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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 ApiExplorerWebSite
|
||||||
|
{
|
||||||
|
public class CustomerCommentsDTO
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public string ShippingInstructions { get; set; }
|
||||||
|
|
||||||
|
public string Feedback { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 ApiExplorerWebSite
|
||||||
|
{
|
||||||
|
public interface IOrderRepository
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 ApiExplorerWebSite
|
||||||
|
{
|
||||||
|
public class OrderDTO
|
||||||
|
{
|
||||||
|
[FromServices]
|
||||||
|
public IOrderRepository Repository { get; set; }
|
||||||
|
|
||||||
|
public string CustomerId { get; set; }
|
||||||
|
|
||||||
|
[FromHeader(Name = "Referrer")]
|
||||||
|
public string ReferrerId { get; set; }
|
||||||
|
|
||||||
|
public OrderDetailsDTO Details { get; set; }
|
||||||
|
|
||||||
|
[FromForm]
|
||||||
|
public CustomerCommentsDTO Comments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 ApiExplorerWebSite
|
||||||
|
{
|
||||||
|
public class OrderDetailsDTO
|
||||||
|
{
|
||||||
|
[FromForm]
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
[FromForm]
|
||||||
|
public Product Product { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,5 +5,8 @@ namespace ApiExplorerWebSite
|
||||||
{
|
{
|
||||||
public class Product
|
public class Product
|
||||||
{
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue