diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionProviderContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionProviderContext.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterDescription.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs similarity index 84% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterDescription.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs index 272fb69e95..1f07d2b7ab 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterDescription.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc.ApiExplorer @@ -35,5 +36,10 @@ public class ApiParameterDescription /// Gets or sets the parameter type. /// public Type Type { get; set; } + + /// + /// Gets or sets the parameter descriptor. + /// + public ParameterDescriptor ParameterDescriptor { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterRouteInfo.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterRouteInfo.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiRequestFormat.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiRequestFormat.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs similarity index 91% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs index 37d34b9af6..4e3307c0b5 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs @@ -30,7 +30,7 @@ public class ApiResponseType /// /// /// Will be null if the action returns no response, or if the response type is unclear. Use - /// or on an action method + /// Microsoft.AspNetCore.Mvc.ProducesAttribute or Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute on an action method /// to specify a response type. /// public Type Type { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs similarity index 77% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs index 5129b7d5ec..3f3ab9c595 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs @@ -26,8 +26,16 @@ public interface IApiDescriptionProvider /// int Order { get; } + /// + /// Creates or modifies s. + /// + /// The . void OnProvidersExecuting(ApiDescriptionProviderContext context); + /// + /// Called after implementations with higher values have been called. + /// + /// The . void OnProvidersExecuted(ApiDescriptionProviderContext context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 54eb9a4c3a..6997bd16ae 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -688,6 +688,7 @@ private ApiParameterDescription CreateResult( Name = GetName(containerName, bindingContext), Source = source, Type = bindingContext.ModelMetadata.ModelType, + ParameterDescriptor = Parameter, }; } diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs index 76feceeff0..1e28eb8ed4 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs @@ -3,9 +3,18 @@ using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: NeutralResourcesLanguage("en-us")] [assembly: AssemblyCompany("Microsoft Corporation.")] [assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] [assembly: AssemblyProduct("Microsoft ASP.NET Core")] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType))] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.net45.json b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.net45.json new file mode 100644 index 0000000000..3e84534357 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.net45.json @@ -0,0 +1,34 @@ +[ + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType", + "Kind": "Removal" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Kind": "Removal" + } +] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.netcore.json b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.netcore.json new file mode 100644 index 0000000000..3e84534357 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/exceptions.netcore.json @@ -0,0 +1,34 @@ +[ + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat", + "Kind": "Removal" + }, + { + "OldTypeId": "public class Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType", + "Kind": "Removal" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Kind": "Removal" + } +] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs index 2471c1af9c..86349d0807 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs @@ -3,6 +3,8 @@ using System; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters.Json; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -73,6 +75,8 @@ internal static void AddJsonFormatterServices(IServiceCollection services) { services.TryAddEnumerable( ServiceDescriptor.Transient, MvcJsonMvcOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); services.TryAddSingleton(); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs new file mode 100644 index 0000000000..49e0ff4003 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.JsonPatch.Operations; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json +{ + /// + /// Implements a provider of to change parameters of + /// type to an array of . + /// + public class JsonPatchOperationsArrayProvider : IApiDescriptionProvider + { + private readonly IModelMetadataProvider _modelMetadataProvider; + + /// + /// Creates a new instance of . + /// + /// The . + public JsonPatchOperationsArrayProvider(IModelMetadataProvider modelMetadataProvider) + { + _modelMetadataProvider = modelMetadataProvider; + } + + /// + /// + /// The order -999 ensures that this provider is executed right after the Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider. + /// + public int Order + { + get { return -999; } + } + + /// + public void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var result in context.Results) + { + foreach (var parameterDescription in result.ParameterDescriptions) + { + if (typeof(IJsonPatchDocument).GetTypeInfo().IsAssignableFrom(parameterDescription.Type)) + { + parameterDescription.Type = typeof(Operation[]); + parameterDescription.ModelMetadata = _modelMetadataProvider.GetMetadataForType(typeof(Operation[])); + } + } + } + } + + /// + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 07a84637a7..5fee3d347d 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -216,6 +216,22 @@ public void GetApiDescription_PopulatesParametersThatAppearOnRouteTemplate_AndHa } } + [Fact] + public void GetApiDescription_ParameterDescription_IncludesParameterDescriptor() + { + // Arrange + var action = CreateActionDescriptor(nameof(FromBody)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + var parameterDescription = Assert.Single(description.ParameterDescriptions); + var actionParameterDescriptor = Assert.Single(action.Parameters); + Assert.Equal(actionParameterDescriptor, parameterDescription.ParameterDescriptor); + } + // Only a parameter which comes from a route or model binding or unknown should // include route info. [Theory] diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs new file mode 100644 index 0000000000..e3c9c4215b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.JsonPatch.Operations; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json +{ + public class JsonPatchOperationsArrayProviderTests + { + [Fact] + public void OnProvidersExecuting_FindsJsonPatchDocuments_ProvidesOperationsArray() + { + // Arrange + var metadataprovider = new TestModelMetadataProvider(); + var provider = new JsonPatchOperationsArrayProvider(metadataprovider); + var jsonPatchParameterDescription = new ApiParameterDescription + { + Type = typeof(JsonPatchDocument) + }; + + var stringParameterDescription = new ApiParameterDescription + { + Type = typeof(string), + }; + + var apiDescription = new ApiDescription(); + apiDescription.ParameterDescriptions.Add(jsonPatchParameterDescription); + apiDescription.ParameterDescriptions.Add(stringParameterDescription); + + var actionDescriptorList = new List(); + var apiDescriptionProviderContext = new ApiDescriptionProviderContext(actionDescriptorList); + apiDescriptionProviderContext.Results.Add(apiDescription); + + // Act + provider.OnProvidersExecuting(apiDescriptionProviderContext); + + // Assert + Assert.Collection(apiDescription.ParameterDescriptions, + description => + { + Assert.Equal(typeof(Operation[]), description.Type); + Assert.Equal(typeof(Operation[]), description.ModelMetadata.ModelType); + }, + description => + { + Assert.Equal(typeof(string), description.Type); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index db7f76d6e9..9ab606e2a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.Cors.Internal; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters.Json; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Razor; @@ -411,6 +412,7 @@ private Dictionary MutliRegistrationServiceTypes new Type[] { typeof(DefaultApiDescriptionProvider), + typeof(JsonPatchOperationsArrayProvider), } }, };